2020-10-19 19:25:58 +00:00
|
|
|
|
<html> <!-- TODO If I write DOCTYPE HTML something changes but I don't know what. -->
|
2020-10-19 18:29:51 +00:00
|
|
|
|
<head>
|
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
|
<title>ClickHouse Query</title>
|
|
|
|
|
|
2020-11-04 22:17:19 +00:00
|
|
|
|
<!-- Code Style:
|
2020-10-19 19:25:58 +00:00
|
|
|
|
|
|
|
|
|
Do not use any JavaScript or CSS frameworks or preprocessors.
|
|
|
|
|
This HTML page should not require any build systems (node.js, npm, gulp, etc.)
|
|
|
|
|
This HTML page should not be minified, instead it should be reasonably minimalistic by itself.
|
|
|
|
|
This HTML page should not load any external resources
|
|
|
|
|
(CSS and JavaScript must be embedded directly to the page. No external fonts or images should be loaded).
|
|
|
|
|
This UI should look as lightweight, clean and fast as possible.
|
|
|
|
|
All UI elements must be aligned in pixel-perfect way.
|
|
|
|
|
There should not be any animations.
|
2020-10-19 23:37:10 +00:00
|
|
|
|
No unexpected changes in positions of elements while the page is loading.
|
|
|
|
|
Navigation by keyboard should work.
|
|
|
|
|
64-bit numbers must display correctly.
|
2020-10-19 19:25:58 +00:00
|
|
|
|
|
|
|
|
|
-->
|
|
|
|
|
|
2020-11-04 22:17:19 +00:00
|
|
|
|
<!-- Development Roadmap:
|
|
|
|
|
|
|
|
|
|
1. Add indication that the query was sent and when the query has been finished.
|
|
|
|
|
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.
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
-->
|
|
|
|
|
|
2020-10-19 18:29:51 +00:00
|
|
|
|
<style type="text/css">
|
2020-10-19 23:37:10 +00:00
|
|
|
|
:root {
|
|
|
|
|
--background-color: #DDF8FF; /* Or #FFFBEF; actually many pastel colors look great for light theme. */
|
|
|
|
|
--element-background-color: #FFF;
|
|
|
|
|
--border-color: #EEE;
|
|
|
|
|
--shadow-color: rgba(0, 0, 0, 0.1);
|
2020-10-20 01:05:29 +00:00
|
|
|
|
--button-color: #FFAA00; /* Orange on light-cyan is especially good. */
|
2020-10-19 23:37:10 +00:00
|
|
|
|
--text-color: #000;
|
|
|
|
|
--button-active-color: #F00;
|
|
|
|
|
--button-active-text-color: #FFF;
|
|
|
|
|
--misc-text-color: #888;
|
|
|
|
|
--error-color: #FEE; /* Light-pink on light-cyan is so neat, I even want to trigger errors to see this cool combination of colors. */
|
|
|
|
|
--table-header-color: #F8F8F8;
|
|
|
|
|
--table-hover-color: #FFF8EF;
|
|
|
|
|
--null-color: #A88;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[data-theme="dark"] {
|
|
|
|
|
--background-color: #000;
|
|
|
|
|
--element-background-color: #102030;
|
|
|
|
|
--border-color: #111;
|
|
|
|
|
--shadow-color: rgba(255, 255, 255, 0.1);
|
|
|
|
|
--text-color: #CCC;
|
|
|
|
|
--button-color: #FFAA00;
|
|
|
|
|
--button-text-color: #000;
|
|
|
|
|
--button-active-color: #F00;
|
|
|
|
|
--button-active-text-color: #FFF;
|
|
|
|
|
--misc-text-color: #888;
|
2020-10-20 22:13:41 +00:00
|
|
|
|
--error-color: #400;
|
2020-10-19 23:37:10 +00:00
|
|
|
|
--table-header-color: #102020;
|
|
|
|
|
--table-hover-color: #003333;
|
|
|
|
|
--null-color: #A88;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-19 18:29:51 +00:00
|
|
|
|
html, body
|
|
|
|
|
{
|
2020-10-19 19:25:58 +00:00
|
|
|
|
/* Personal choice. */
|
2020-10-19 18:29:51 +00:00
|
|
|
|
font-family: Sans-Serif;
|
2020-10-19 23:37:10 +00:00
|
|
|
|
background: var(--background-color);
|
|
|
|
|
color: var(--text-color);
|
2020-10-19 19:25:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Otherwise Webkit based browsers will display ugly border on focus. */
|
2020-10-19 23:37:10 +00:00
|
|
|
|
textarea, input, button
|
2020-10-19 19:25:58 +00:00
|
|
|
|
{
|
|
|
|
|
outline: none;
|
2020-10-19 23:37:10 +00:00
|
|
|
|
border: none;
|
|
|
|
|
color: var(--text-color);
|
2020-10-19 18:29:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-10-19 19:25:58 +00:00
|
|
|
|
/* Otherwise scrollbar may appear dynamically and it will alter viewport height,
|
|
|
|
|
then relative heights of elements will change suddenly, and it will break overall impression. */
|
|
|
|
|
/* html
|
2020-10-19 18:29:51 +00:00
|
|
|
|
{
|
|
|
|
|
overflow-x: scroll;
|
|
|
|
|
}*/
|
|
|
|
|
|
|
|
|
|
div
|
|
|
|
|
{
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.monospace
|
|
|
|
|
{
|
2020-10-19 19:25:58 +00:00
|
|
|
|
/* Prefer fonts that have full hinting info. This is important for non-retina displays.
|
|
|
|
|
Also I personally dislike "Ubuntu" font due to the similarity of 'r' and 'г' (it looks very ignorant).
|
|
|
|
|
*/
|
2020-10-19 18:29:51 +00:00
|
|
|
|
font-family: Liberation Mono, DejaVu Sans Mono, MonoLisa, Consolas, Monospace;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.shadow
|
|
|
|
|
{
|
2020-10-19 23:37:10 +00:00
|
|
|
|
box-shadow: 0 0 1rem var(--shadow-color);
|
2020-10-19 18:29:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
input, textarea
|
|
|
|
|
{
|
2020-10-19 23:37:10 +00:00
|
|
|
|
border: 1px solid var(--border-color);
|
2020-10-19 19:25:58 +00:00
|
|
|
|
/* The font must be not too small (to be inclusive) and not too large (it's less practical and make general feel of insecurity) */
|
2020-10-19 18:29:51 +00:00
|
|
|
|
font-size: 11pt;
|
|
|
|
|
padding: 0.25rem;
|
2020-10-19 23:37:10 +00:00
|
|
|
|
background-color: var(--element-background-color);
|
2020-10-19 18:29:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#query
|
|
|
|
|
{
|
2020-10-19 19:25:58 +00:00
|
|
|
|
/* Make enough space for even huge queries. */
|
2020-10-19 18:29:51 +00:00
|
|
|
|
height: 20%;
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#inputs
|
|
|
|
|
{
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#url
|
|
|
|
|
{
|
|
|
|
|
width: 70%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#user
|
|
|
|
|
{
|
|
|
|
|
width: 15%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#password
|
|
|
|
|
{
|
|
|
|
|
width: 15%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#run_div
|
|
|
|
|
{
|
|
|
|
|
margin-top: 1rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#run
|
|
|
|
|
{
|
2020-10-19 23:37:10 +00:00
|
|
|
|
color: var(--button-text-color);
|
|
|
|
|
background-color: var(--button-color);
|
2020-10-19 18:29:51 +00:00
|
|
|
|
padding: 0.25rem 1rem;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
font-weight: bold;
|
2020-10-19 23:37:10 +00:00
|
|
|
|
font-size: 100%; /* Otherwise button element will have lower font size. */
|
2020-10-19 18:29:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-10-19 23:37:10 +00:00
|
|
|
|
#run:hover, #run:focus
|
2020-10-19 18:29:51 +00:00
|
|
|
|
{
|
2020-10-19 23:37:10 +00:00
|
|
|
|
color: var(--button-active-text-color);
|
|
|
|
|
background-color: var(--button-active-color);
|
2020-10-19 18:29:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#stats
|
|
|
|
|
{
|
|
|
|
|
float: right;
|
2020-10-19 23:37:10 +00:00
|
|
|
|
color: var(--misc-text-color);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#toggle-light, #toggle-dark
|
|
|
|
|
{
|
|
|
|
|
float: right;
|
|
|
|
|
padding-right: 0.5rem;
|
|
|
|
|
cursor: pointer;
|
2020-10-19 18:29:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.hint
|
|
|
|
|
{
|
2020-10-19 23:37:10 +00:00
|
|
|
|
color: var(--misc-text-color);
|
2020-10-19 18:29:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#data_div
|
|
|
|
|
{
|
|
|
|
|
margin-top: 1rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#data-table
|
|
|
|
|
{
|
|
|
|
|
border-collapse: collapse;
|
|
|
|
|
border-spacing: 0;
|
2020-10-19 19:25:58 +00:00
|
|
|
|
/* I need pixel-perfect alignment but not sure the following is correct, please help */
|
2020-10-19 18:29:51 +00:00
|
|
|
|
min-width: calc(100vw - 2rem);
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-19 23:37:10 +00:00
|
|
|
|
/* Will be displayed when user specified custom format. */
|
|
|
|
|
#data-unparsed
|
|
|
|
|
{
|
|
|
|
|
background-color: var(--element-background-color);
|
|
|
|
|
margin-top: 0rem;
|
|
|
|
|
padding: 0.25rem 0.5rem;
|
|
|
|
|
display: none;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-19 18:29:51 +00:00
|
|
|
|
td
|
|
|
|
|
{
|
2020-10-19 23:37:10 +00:00
|
|
|
|
background-color: var(--element-background-color);
|
2020-10-19 18:29:51 +00:00
|
|
|
|
white-space: nowrap;
|
2020-10-19 19:25:58 +00:00
|
|
|
|
/* For wide tables any individual column will be no more than 50% of page width. */
|
2020-10-19 18:29:51 +00:00
|
|
|
|
max-width: 50vw;
|
2020-10-19 19:25:58 +00:00
|
|
|
|
/* The content is cut unless you hover. */
|
2020-10-19 18:29:51 +00:00
|
|
|
|
overflow: hidden;
|
|
|
|
|
padding: 0.25rem 0.5rem;
|
2020-10-19 23:37:10 +00:00
|
|
|
|
border: 1px solid var(--border-color);
|
2020-10-19 18:29:51 +00:00
|
|
|
|
white-space: pre;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
td.right
|
|
|
|
|
{
|
|
|
|
|
text-align: right;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
th
|
|
|
|
|
{
|
|
|
|
|
padding: 0.25rem 0.5rem;
|
|
|
|
|
text-align: middle;
|
2020-10-19 23:37:10 +00:00
|
|
|
|
background-color: var(--table-header-color);
|
|
|
|
|
border: 1px solid var(--border-color);
|
2020-10-19 18:29:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-10-19 19:25:58 +00:00
|
|
|
|
/* The row under mouse pointer is highlight for better legibility. */
|
2020-10-19 18:29:51 +00:00
|
|
|
|
tr:hover, tr:hover td
|
|
|
|
|
{
|
2020-10-19 23:37:10 +00:00
|
|
|
|
background-color: var(--table-hover-color);
|
2020-10-19 18:29:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tr:hover
|
|
|
|
|
{
|
|
|
|
|
box-shadow: 0 0 1rem rgba(0, 0, 0, 0.1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#error
|
|
|
|
|
{
|
2020-10-19 23:37:10 +00:00
|
|
|
|
background: var(--error-color);
|
2020-10-19 18:29:51 +00:00
|
|
|
|
white-space: pre-wrap;
|
|
|
|
|
padding: 0.5rem 1rem;
|
|
|
|
|
display: none;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-19 19:25:58 +00:00
|
|
|
|
/* When mouse pointer is over table cell, will display full text (with wrap) instead of cut.
|
|
|
|
|
TODO Find a way to make it work on touch devices. */
|
2020-10-19 18:29:51 +00:00
|
|
|
|
td.left:hover
|
|
|
|
|
{
|
|
|
|
|
white-space: pre-wrap;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-19 19:25:58 +00:00
|
|
|
|
/* The style for SQL NULL */
|
2020-10-19 18:29:51 +00:00
|
|
|
|
.null
|
|
|
|
|
{
|
2020-10-19 23:37:10 +00:00
|
|
|
|
color: var(--null-color);
|
2020-10-19 18:29:51 +00:00
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
</head>
|
|
|
|
|
|
|
|
|
|
<body>
|
|
|
|
|
<div id="inputs">
|
|
|
|
|
<input class="monospace shadow" id="url" type="text" value="http://localhost:8123/" /><input class="monospace shadow" id="user" type="text" value="default" /><input class="monospace shadow" id="password" type="password" />
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
2020-10-19 23:37:10 +00:00
|
|
|
|
<textarea autofocus spellcheck="false" class="monospace shadow" id="query"></textarea>
|
2020-10-19 18:29:51 +00:00
|
|
|
|
</div>
|
|
|
|
|
<div id="run_div">
|
2020-10-19 23:37:10 +00:00
|
|
|
|
<button class="shadow" id="run">Run</button>
|
2020-10-19 18:29:51 +00:00
|
|
|
|
<span class="hint"> (Ctrl+Enter)</span>
|
|
|
|
|
<span id="stats"></span>
|
2020-10-19 23:37:10 +00:00
|
|
|
|
<span id="toggle-dark">🌑</span><span id="toggle-light">🌞</span>
|
2020-10-19 18:29:51 +00:00
|
|
|
|
</div>
|
|
|
|
|
<div id="data_div">
|
2020-10-19 23:37:10 +00:00
|
|
|
|
<table class="monospace shadow" id="data-table"></table>
|
|
|
|
|
<pre class="monospace shadow" id="data-unparsed"></pre>
|
2020-10-19 18:29:51 +00:00
|
|
|
|
</div>
|
|
|
|
|
<p id="error" class="monospace shadow">
|
|
|
|
|
</p>
|
|
|
|
|
</body>
|
|
|
|
|
|
|
|
|
|
<script type="text/javascript">
|
2020-10-19 23:37:10 +00:00
|
|
|
|
|
|
|
|
|
/// Substitute the address of the server where the page is served.
|
|
|
|
|
if (location.protocol != 'file:') {
|
|
|
|
|
document.getElementById('url').value = location.origin;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-19 18:29:51 +00:00
|
|
|
|
function post()
|
|
|
|
|
{
|
2020-10-20 22:13:41 +00:00
|
|
|
|
/// TODO: Avoid race condition on subsequent requests when responses may come out of order.
|
|
|
|
|
/// TODO: Check if URL already contains query string (append parameters).
|
|
|
|
|
|
2020-10-19 18:29:51 +00:00
|
|
|
|
var url = document.getElementById('url').value +
|
2020-10-19 23:37:10 +00:00
|
|
|
|
/// Ask server to allow cross-domain requests.
|
2020-10-19 18:29:51 +00:00
|
|
|
|
'?add_http_cors_header=1' +
|
|
|
|
|
'&user=' + encodeURIComponent(document.getElementById('user').value) +
|
|
|
|
|
'&password=' + encodeURIComponent(document.getElementById('password').value) +
|
|
|
|
|
'&default_format=JSONCompact' +
|
2020-10-19 23:37:10 +00:00
|
|
|
|
/// Safety settings to prevent results that browser cannot display.
|
2020-10-19 18:29:51 +00:00
|
|
|
|
'&max_result_rows=1000&max_result_bytes=10000000&result_overflow_mode=break';
|
|
|
|
|
|
|
|
|
|
var query = document.getElementById('query').value;
|
|
|
|
|
var xhr = new XMLHttpRequest;
|
|
|
|
|
|
|
|
|
|
xhr.open('POST', url, true);
|
|
|
|
|
xhr.send(query);
|
|
|
|
|
|
|
|
|
|
xhr.onreadystatechange = function()
|
|
|
|
|
{
|
|
|
|
|
if (this.readyState === XMLHttpRequest.DONE) {
|
|
|
|
|
if (this.status === 200) {
|
2020-10-19 23:37:10 +00:00
|
|
|
|
var json;
|
|
|
|
|
try { json = JSON.parse(this.response); } catch (e) {}
|
|
|
|
|
if (json !== undefined && json.statistics !== undefined) {
|
|
|
|
|
renderResult(json);
|
|
|
|
|
} else {
|
|
|
|
|
renderUnparsedResult(this.response);
|
|
|
|
|
}
|
2020-10-19 18:29:51 +00:00
|
|
|
|
} else {
|
2020-10-20 22:13:41 +00:00
|
|
|
|
/// TODO: Proper rendering of network errors.
|
2020-10-19 18:29:51 +00:00
|
|
|
|
renderError(this.response);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2020-10-19 23:37:10 +00:00
|
|
|
|
//console.log(this);
|
2020-10-19 18:29:51 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
document.getElementById('run').onclick = function()
|
|
|
|
|
{
|
|
|
|
|
post();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
document.getElementById('query').onkeypress = function(event)
|
|
|
|
|
{
|
2020-10-20 01:08:30 +00:00
|
|
|
|
/// Firefox has code 13 for Enter and Chromium has code 10.
|
2020-10-19 19:25:58 +00:00
|
|
|
|
if (event.ctrlKey && (event.charCode == 13 || event.charCode == 10)) {
|
2020-10-19 18:29:51 +00:00
|
|
|
|
post();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function clear()
|
|
|
|
|
{
|
|
|
|
|
var table = document.getElementById('data-table');
|
|
|
|
|
while (table.firstChild) {
|
|
|
|
|
table.removeChild(table.lastChild);
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-19 23:37:10 +00:00
|
|
|
|
document.getElementById('data-unparsed').innerText = '';
|
|
|
|
|
document.getElementById('data-unparsed').style.display = 'none';
|
|
|
|
|
|
2020-10-19 18:29:51 +00:00
|
|
|
|
document.getElementById('error').innerText = '';
|
|
|
|
|
document.getElementById('error').style.display = 'none';
|
2020-10-19 23:37:10 +00:00
|
|
|
|
|
|
|
|
|
document.getElementById('stats').innerText = '';
|
2020-10-19 18:29:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function renderResult(response)
|
|
|
|
|
{
|
|
|
|
|
//console.log(response);
|
|
|
|
|
clear();
|
|
|
|
|
|
|
|
|
|
var stats = document.getElementById('stats');
|
|
|
|
|
stats.innerText = 'Elapsed: ' + response.statistics.elapsed.toFixed(3) + " sec, read " + response.statistics.rows_read + " rows.";
|
|
|
|
|
|
|
|
|
|
var thead = document.createElement('thead');
|
|
|
|
|
for (var idx in response.meta) {
|
|
|
|
|
var th = document.createElement('th');
|
|
|
|
|
var name = document.createTextNode(response.meta[idx].name);
|
|
|
|
|
th.appendChild(name);
|
|
|
|
|
thead.appendChild(th);
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-19 23:37:10 +00:00
|
|
|
|
/// To prevent hanging the browser, limit the number of cells in a table.
|
|
|
|
|
/// It's important to have the limit on number of cells, not just rows, because tables may be wide or narrow.
|
2020-10-19 18:29:51 +00:00
|
|
|
|
var max_rows = 10000 / response.meta.length;
|
|
|
|
|
var row_num = 0;
|
|
|
|
|
|
|
|
|
|
var tbody = document.createElement('tbody');
|
|
|
|
|
for (var row_idx in response.data) {
|
|
|
|
|
var tr = document.createElement('tr');
|
|
|
|
|
for (var col_idx in response.data[row_idx]) {
|
|
|
|
|
var td = document.createElement('td');
|
|
|
|
|
var cell = response.data[row_idx][col_idx];
|
|
|
|
|
var is_null = (cell === null);
|
|
|
|
|
var content = document.createTextNode(is_null ? 'ᴺᵁᴸᴸ' : cell);
|
|
|
|
|
td.appendChild(content);
|
2020-10-20 22:13:41 +00:00
|
|
|
|
/// TODO: Execute regexp only once for each column.
|
2020-10-19 18:29:51 +00:00
|
|
|
|
td.className = response.meta[col_idx].type.match(/^(U?Int|Decimal|Float)/) ? 'right' : 'left';
|
|
|
|
|
if (is_null) {
|
|
|
|
|
td.className += ' null';
|
|
|
|
|
}
|
|
|
|
|
tr.appendChild(td);
|
|
|
|
|
}
|
|
|
|
|
tbody.appendChild(tr);
|
|
|
|
|
|
|
|
|
|
++row_num;
|
|
|
|
|
if (row_num >= max_rows) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var table = document.getElementById('data-table');
|
|
|
|
|
table.appendChild(thead);
|
|
|
|
|
table.appendChild(tbody);
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-20 01:08:30 +00:00
|
|
|
|
/// A function to render raw data when non-default format is specified.
|
2020-10-19 23:37:10 +00:00
|
|
|
|
function renderUnparsedResult(response)
|
|
|
|
|
{
|
|
|
|
|
clear();
|
|
|
|
|
var data = document.getElementById('data-unparsed')
|
2020-10-20 22:13:41 +00:00
|
|
|
|
|
|
|
|
|
if (response === '') {
|
|
|
|
|
/// TODO: Fade or remove previous result when new request will be performed.
|
|
|
|
|
response = 'Ok.';
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-19 23:37:10 +00:00
|
|
|
|
data.innerText = response;
|
|
|
|
|
/// inline-block make width adjust to the size of content.
|
|
|
|
|
data.style.display = 'inline-block';
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-19 18:29:51 +00:00
|
|
|
|
function renderError(response)
|
|
|
|
|
{
|
|
|
|
|
clear();
|
|
|
|
|
document.getElementById('error').innerText = response;
|
|
|
|
|
document.getElementById('error').style.display = 'block';
|
|
|
|
|
}
|
2020-10-19 23:37:10 +00:00
|
|
|
|
|
2020-10-20 00:32:50 +00:00
|
|
|
|
function setColorTheme(theme)
|
2020-10-19 23:37:10 +00:00
|
|
|
|
{
|
2020-10-20 00:32:50 +00:00
|
|
|
|
window.localStorage.setItem('theme', theme);
|
|
|
|
|
document.documentElement.setAttribute('data-theme', theme);
|
2020-10-19 23:37:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-10-20 01:08:30 +00:00
|
|
|
|
/// The choice of color theme is saved in browser.
|
2020-10-20 00:32:50 +00:00
|
|
|
|
var theme = window.localStorage.getItem('theme');
|
|
|
|
|
if (theme) {
|
|
|
|
|
setColorTheme(theme);
|
2020-10-19 23:37:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
document.getElementById('toggle-light').onclick = function()
|
|
|
|
|
{
|
2020-10-20 00:32:50 +00:00
|
|
|
|
setColorTheme('light');
|
2020-10-19 23:37:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
document.getElementById('toggle-dark').onclick = function()
|
|
|
|
|
{
|
2020-10-20 00:32:50 +00:00
|
|
|
|
setColorTheme('dark');
|
2020-10-19 23:37:10 +00:00
|
|
|
|
}
|
2020-10-19 18:29:51 +00:00
|
|
|
|
</script>
|
|
|
|
|
</html>
|