Merge pull request #17293 from ClickHouse/play-improvement

Web UI improvement
This commit is contained in:
alexey-milovidov 2020-11-23 11:58:15 +03:00 committed by GitHub
commit e82e645305
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -1,6 +1,7 @@
<html> <!-- TODO If I write DOCTYPE HTML something changes but I don't know what. --> <html> <!-- TODO If I write DOCTYPE HTML something changes but I don't know what. -->
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="icon" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDkgOCI+PHN0eWxlPi5ve2ZpbGw6I2ZjMH0ucntmaWxsOnJlZH08L3N0eWxlPjxwYXRoIGQ9Ik0wLDcgaDEgdjEgaC0xIHoiIGNsYXNzPSJyIi8+PHBhdGggZD0iTTAsMCBoMSB2NyBoLTEgeiIgY2xhc3M9Im8iLz48cGF0aCBkPSJNMiwwIGgxIHY4IGgtMSB6IiBjbGFzcz0ibyIvPjxwYXRoIGQ9Ik00LDAgaDEgdjggaC0xIHoiIGNsYXNzPSJvIi8+PHBhdGggZD0iTTYsMCBoMSB2OCBoLTEgeiIgY2xhc3M9Im8iLz48cGF0aCBkPSJNOCwzLjI1IGgxIHYxLjUgaC0xIHoiIGNsYXNzPSJvIi8+PC9zdmc+">
<title>ClickHouse Query</title> <title>ClickHouse Query</title>
<!-- Code Style: <!-- Code Style:
@ -21,26 +22,11 @@
<!-- Development Roadmap: <!-- Development Roadmap:
1. Add indication that the query was sent and when the query has been finished. 1. Support readonly servers.
Do not use any animated spinners. Just a text or check mark.
Eliminate race conditions (results from the previous query should be ignored on arrival, the previous request should be cancelled).
2. Support readonly servers.
Check if readonly = 1 (with SELECT FROM system.settings) to avoid sending settings. It can be done once on address/credentials change. Check if readonly = 1 (with SELECT FROM system.settings) to avoid sending settings. It can be done once on address/credentials change.
It can be done in background, e.g. wait 100 ms after address/credentials change and do the check. It can be done in background, e.g. wait 100 ms after address/credentials change and do the check.
Also it can provide visual indication that credentials are correct. Also it can provide visual indication that credentials are correct.
3. Add history in localstorage. Integrate with history API.
There can be a counter in localstorage, that will be appended to location #fragment.
The 'back', 'forward' buttons in browser should work.
Also there should be UI element to list all the queries from history and select from the list.
4. Trivial sharing capabilities.
Sharing is only possible when system.query_log is accessible. Read the X-ClickHouse-QueryId from the response.
Share button will: - emit SYSTEM FLUSH LOGS if not readonly; - find the query in the query_log;
- generate an URL with the query id and: server address if not equal to the URL's host; user name if not default;
indication that password should be entered in case of non-empty password.
--> -->
<style type="text/css"> <style type="text/css">
@ -273,6 +259,22 @@
{ {
color: var(--null-color); color: var(--null-color);
} }
#hourglass
{
display: none;
padding-left: 1rem;
font-size: 110%;
color: #888;
}
#check-mark
{
display: none;
padding-left: 1rem;
font-size: 110%;
color: #080;
}
</style> </style>
</head> </head>
@ -286,6 +288,8 @@
<div id="run_div"> <div id="run_div">
<button class="shadow" id="run">Run</button> <button class="shadow" id="run">Run</button>
<span class="hint">&nbsp;(Ctrl+Enter)</span> <span class="hint">&nbsp;(Ctrl+Enter)</span>
<span id="hourglass"></span>
<span id="check-mark"></span>
<span id="stats"></span> <span id="stats"></span>
<span id="toggle-dark">🌑</span><span id="toggle-light">🌞</span> <span id="toggle-dark">🌑</span><span id="toggle-light">🌞</span>
</div> </div>
@ -299,50 +303,117 @@
<script type="text/javascript"> <script type="text/javascript">
/// Incremental request number. When response is received,
/// if it's request number does not equal to the current request number, response will be ignored.
/// This is to avoid race conditions.
var request_num = 0;
/// Save query in history only if it is different.
var previous_query = '';
/// Substitute the address of the server where the page is served. /// Substitute the address of the server where the page is served.
if (location.protocol != 'file:') { if (location.protocol != 'file:') {
document.getElementById('url').value = location.origin; document.getElementById('url').value = location.origin;
} }
function post() /// Substitute user name if it's specified in the query string
var user_from_url = (new URL(window.location)).searchParams.get('user');
if (user_from_url) {
document.getElementById('user').value = user_from_url;
}
function postImpl(posted_request_num, query)
{ {
/// TODO: Avoid race condition on subsequent requests when responses may come out of order.
/// TODO: Check if URL already contains query string (append parameters). /// TODO: Check if URL already contains query string (append parameters).
var user = document.getElementById('user').value;
var password = document.getElementById('password').value;
var url = document.getElementById('url').value + var url = document.getElementById('url').value +
/// Ask server to allow cross-domain requests. /// Ask server to allow cross-domain requests.
'?add_http_cors_header=1' + '?add_http_cors_header=1' +
'&user=' + encodeURIComponent(document.getElementById('user').value) + '&user=' + encodeURIComponent(user) +
'&password=' + encodeURIComponent(document.getElementById('password').value) + '&password=' + encodeURIComponent(password) +
'&default_format=JSONCompact' + '&default_format=JSONCompact' +
/// Safety settings to prevent results that browser cannot display. /// Safety settings to prevent results that browser cannot display.
'&max_result_rows=1000&max_result_bytes=10000000&result_overflow_mode=break'; '&max_result_rows=1000&max_result_bytes=10000000&result_overflow_mode=break';
var query = document.getElementById('query').value;
var xhr = new XMLHttpRequest; var xhr = new XMLHttpRequest;
xhr.open('POST', url, true); xhr.open('POST', url, true);
xhr.send(query);
xhr.onreadystatechange = function() xhr.onreadystatechange = function()
{ {
if (this.readyState === XMLHttpRequest.DONE) { if (posted_request_num != request_num) {
if (this.status === 200) { return;
var json; } else if (this.readyState === XMLHttpRequest.DONE) {
try { json = JSON.parse(this.response); } catch (e) {} renderResponse(this.status, this.response);
if (json !== undefined && json.statistics !== undefined) {
renderResult(json); /// The query is saved in browser history (in state JSON object)
} else { /// as well as in URL fragment identifier.
renderUnparsedResult(this.response); if (query != previous_query) {
} previous_query = query;
} else { var title = "ClickHouse Query: " + query;
/// TODO: Proper rendering of network errors. history.pushState(
renderError(this.response); {
query: query,
status: this.status,
response: this.response.length > 100000 ? null : this.response /// Lower than the browser's limit.
},
title,
window.location.pathname + '?user=' + encodeURIComponent(user) + '#' + window.btoa(query));
document.title = title;
} }
} else { } else {
//console.log(this); //console.log(this);
} }
} }
document.getElementById('check-mark').style.display = 'none';
document.getElementById('hourglass').style.display = 'inline';
xhr.send(query);
}
function renderResponse(status, response) {
document.getElementById('hourglass').style.display = 'none';
if (status === 200) {
var json;
try { json = JSON.parse(response); } catch (e) {}
if (json !== undefined && json.statistics !== undefined) {
renderResult(json);
} else {
renderUnparsedResult(response);
}
document.getElementById('check-mark').style.display = 'inline';
} else {
/// TODO: Proper rendering of network errors.
renderError(response);
}
}
window.onpopstate = function(event) {
if (!event.state) {
return;
}
document.getElementById('query').value = event.state.query;
if (!event.state.response) {
clear();
return;
}
renderResponse(event.state.status, event.state.response);
};
if (window.location.hash) {
document.getElementById('query').value = window.atob(window.location.hash.substr(1));
}
function post()
{
++request_num;
var query = document.getElementById('query').value;
postImpl(request_num, query);
} }
document.getElementById('run').onclick = function() document.getElementById('run').onclick = function()
@ -350,7 +421,7 @@
post(); post();
} }
document.getElementById('query').onkeypress = function(event) document.onkeypress = function(event)
{ {
/// Firefox has code 13 for Enter and Chromium has code 10. /// Firefox has code 13 for Enter and Chromium has code 10.
if (event.ctrlKey && (event.charCode == 13 || event.charCode == 10)) { if (event.ctrlKey && (event.charCode == 13 || event.charCode == 10)) {
@ -372,6 +443,9 @@
document.getElementById('error').style.display = 'none'; document.getElementById('error').style.display = 'none';
document.getElementById('stats').innerText = ''; document.getElementById('stats').innerText = '';
document.getElementById('hourglass').style.display = 'none';
document.getElementById('check-mark').style.display = 'none';
} }
function renderResult(response) function renderResult(response)
@ -443,7 +517,7 @@
function renderError(response) function renderError(response)
{ {
clear(); clear();
document.getElementById('error').innerText = response; document.getElementById('error').innerText = response ? response : "No response.";
document.getElementById('error').style.display = 'block'; document.getElementById('error').style.display = 'block';
} }