mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-09-29 05:00:47 +00:00
Merge pull request #17293 from ClickHouse/play-improvement
Web UI improvement
This commit is contained in:
commit
e82e645305
@ -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"> (Ctrl+Enter)</span>
|
<span class="hint"> (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';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user