mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-09-20 08:40:50 +00:00
Merge pull request #16158 from ClickHouse/minimal-ui
Added minimal web UI
This commit is contained in:
commit
b4f0e08369
@ -4,7 +4,7 @@ set(CLICKHOUSE_SERVER_SOURCES
|
||||
)
|
||||
|
||||
if (OS_LINUX)
|
||||
set (LINK_CONFIG_LIB INTERFACE "-Wl,${WHOLE_ARCHIVE} $<TARGET_FILE:clickhouse_server_configs> -Wl,${NO_WHOLE_ARCHIVE}")
|
||||
set (LINK_RESOURCE_LIB INTERFACE "-Wl,${WHOLE_ARCHIVE} $<TARGET_FILE:clickhouse_server_configs> -Wl,${NO_WHOLE_ARCHIVE}")
|
||||
endif ()
|
||||
|
||||
set (CLICKHOUSE_SERVER_LINK
|
||||
@ -20,7 +20,7 @@ set (CLICKHOUSE_SERVER_LINK
|
||||
clickhouse_table_functions
|
||||
string_utils
|
||||
|
||||
${LINK_CONFIG_LIB}
|
||||
${LINK_RESOURCE_LIB}
|
||||
|
||||
PUBLIC
|
||||
daemon
|
||||
@ -37,20 +37,20 @@ if (OS_LINUX)
|
||||
# 1. Allow to run the binary without download of any other files.
|
||||
# 2. Allow to implement "sudo clickhouse install" tool.
|
||||
|
||||
foreach(CONFIG_FILE config users embedded)
|
||||
set(CONFIG_OBJ ${CONFIG_FILE}.o)
|
||||
set(CONFIG_OBJS ${CONFIG_OBJS} ${CONFIG_OBJ})
|
||||
foreach(RESOURCE_FILE config.xml users.xml embedded.xml play.html)
|
||||
set(RESOURCE_OBJ ${RESOURCE_FILE}.o)
|
||||
set(RESOURCE_OBJS ${RESOURCE_OBJS} ${RESOURCE_OBJ})
|
||||
|
||||
# https://stackoverflow.com/questions/14776463/compile-and-add-an-object-file-from-a-binary-with-cmake
|
||||
add_custom_command(OUTPUT ${CONFIG_OBJ}
|
||||
COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${OBJCOPY_PATH} -I binary ${OBJCOPY_ARCH_OPTIONS} ${CONFIG_FILE}.xml ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_OBJ}
|
||||
add_custom_command(OUTPUT ${RESOURCE_OBJ}
|
||||
COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${OBJCOPY_PATH} -I binary ${OBJCOPY_ARCH_OPTIONS} ${RESOURCE_FILE} ${CMAKE_CURRENT_BINARY_DIR}/${RESOURCE_OBJ}
|
||||
COMMAND ${OBJCOPY_PATH} --rename-section .data=.rodata,alloc,load,readonly,data,contents
|
||||
${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_OBJ} ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_OBJ})
|
||||
${CMAKE_CURRENT_BINARY_DIR}/${RESOURCE_OBJ} ${CMAKE_CURRENT_BINARY_DIR}/${RESOURCE_OBJ})
|
||||
|
||||
set_source_files_properties(${CONFIG_OBJ} PROPERTIES EXTERNAL_OBJECT true GENERATED true)
|
||||
endforeach(CONFIG_FILE)
|
||||
set_source_files_properties(${RESOURCE_OBJ} PROPERTIES EXTERNAL_OBJECT true GENERATED true)
|
||||
endforeach(RESOURCE_FILE)
|
||||
|
||||
add_library(clickhouse_server_configs STATIC ${CONFIG_OBJS})
|
||||
add_library(clickhouse_server_configs STATIC ${RESOURCE_OBJS})
|
||||
set_target_properties(clickhouse_server_configs PROPERTIES LINKER_LANGUAGE C)
|
||||
|
||||
# whole-archive prevents symbols from being discarded for unknown reason
|
||||
|
437
programs/server/play.html
Normal file
437
programs/server/play.html
Normal file
@ -0,0 +1,437 @@
|
||||
<html> <!-- TODO If I write DOCTYPE HTML something changes but I don't know what. -->
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>ClickHouse Query</title>
|
||||
|
||||
<!-- Code style:
|
||||
|
||||
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.
|
||||
No unexpected changes in positions of elements while the page is loading.
|
||||
Navigation by keyboard should work.
|
||||
64-bit numbers must display correctly.
|
||||
|
||||
-->
|
||||
|
||||
<style type="text/css">
|
||||
: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);
|
||||
--button-color: #FFAA00; /* Orange on light-cyan is especially good. */
|
||||
--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;
|
||||
--error-color: #400; /* Light-pink on light-cyan is so neat, I even want to trigger errors to see this cool combination of colors. */
|
||||
--table-header-color: #102020;
|
||||
--table-hover-color: #003333;
|
||||
--null-color: #A88;
|
||||
}
|
||||
|
||||
html, body
|
||||
{
|
||||
/* Personal choice. */
|
||||
font-family: Sans-Serif;
|
||||
background: var(--background-color);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
/* Otherwise Webkit based browsers will display ugly border on focus. */
|
||||
textarea, input, button
|
||||
{
|
||||
outline: none;
|
||||
border: none;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
/* 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
|
||||
{
|
||||
overflow-x: scroll;
|
||||
}*/
|
||||
|
||||
div
|
||||
{
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.monospace
|
||||
{
|
||||
/* 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).
|
||||
*/
|
||||
font-family: Liberation Mono, DejaVu Sans Mono, MonoLisa, Consolas, Monospace;
|
||||
}
|
||||
|
||||
.shadow
|
||||
{
|
||||
box-shadow: 0 0 1rem var(--shadow-color);
|
||||
}
|
||||
|
||||
input, textarea
|
||||
{
|
||||
border: 1px solid var(--border-color);
|
||||
/* The font must be not too small (to be inclusive) and not too large (it's less practical and make general feel of insecurity) */
|
||||
font-size: 11pt;
|
||||
padding: 0.25rem;
|
||||
background-color: var(--element-background-color);
|
||||
}
|
||||
|
||||
#query
|
||||
{
|
||||
/* Make enough space for even huge queries. */
|
||||
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
|
||||
{
|
||||
color: var(--button-text-color);
|
||||
background-color: var(--button-color);
|
||||
padding: 0.25rem 1rem;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
font-size: 100%; /* Otherwise button element will have lower font size. */
|
||||
}
|
||||
|
||||
#run:hover, #run:focus
|
||||
{
|
||||
color: var(--button-active-text-color);
|
||||
background-color: var(--button-active-color);
|
||||
}
|
||||
|
||||
#stats
|
||||
{
|
||||
float: right;
|
||||
color: var(--misc-text-color);
|
||||
}
|
||||
|
||||
#toggle-light, #toggle-dark
|
||||
{
|
||||
float: right;
|
||||
padding-right: 0.5rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.hint
|
||||
{
|
||||
color: var(--misc-text-color);
|
||||
}
|
||||
|
||||
#data_div
|
||||
{
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
#data-table
|
||||
{
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
/* I need pixel-perfect alignment but not sure the following is correct, please help */
|
||||
min-width: calc(100vw - 2rem);
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
td
|
||||
{
|
||||
background-color: var(--element-background-color);
|
||||
white-space: nowrap;
|
||||
/* For wide tables any individual column will be no more than 50% of page width. */
|
||||
max-width: 50vw;
|
||||
/* The content is cut unless you hover. */
|
||||
overflow: hidden;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border: 1px solid var(--border-color);
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
td.right
|
||||
{
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
th
|
||||
{
|
||||
padding: 0.25rem 0.5rem;
|
||||
text-align: middle;
|
||||
background-color: var(--table-header-color);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
/* The row under mouse pointer is highlight for better legibility. */
|
||||
tr:hover, tr:hover td
|
||||
{
|
||||
background-color: var(--table-hover-color);
|
||||
}
|
||||
|
||||
tr:hover
|
||||
{
|
||||
box-shadow: 0 0 1rem rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
#error
|
||||
{
|
||||
background: var(--error-color);
|
||||
white-space: pre-wrap;
|
||||
padding: 0.5rem 1rem;
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 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. */
|
||||
td.left:hover
|
||||
{
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
/* The style for SQL NULL */
|
||||
.null
|
||||
{
|
||||
color: var(--null-color);
|
||||
}
|
||||
</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>
|
||||
<textarea autofocus spellcheck="false" class="monospace shadow" id="query"></textarea>
|
||||
</div>
|
||||
<div id="run_div">
|
||||
<button class="shadow" id="run">Run</button>
|
||||
<span class="hint"> (Ctrl+Enter)</span>
|
||||
<span id="stats"></span>
|
||||
<span id="toggle-dark">🌑</span><span id="toggle-light">🌞</span>
|
||||
</div>
|
||||
<div id="data_div">
|
||||
<table class="monospace shadow" id="data-table"></table>
|
||||
<pre class="monospace shadow" id="data-unparsed"></pre>
|
||||
</div>
|
||||
<p id="error" class="monospace shadow">
|
||||
</p>
|
||||
</body>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
/// Substitute the address of the server where the page is served.
|
||||
if (location.protocol != 'file:') {
|
||||
document.getElementById('url').value = location.origin;
|
||||
}
|
||||
|
||||
function post()
|
||||
{
|
||||
var url = document.getElementById('url').value +
|
||||
/// Ask server to allow cross-domain requests.
|
||||
'?add_http_cors_header=1' +
|
||||
'&user=' + encodeURIComponent(document.getElementById('user').value) +
|
||||
'&password=' + encodeURIComponent(document.getElementById('password').value) +
|
||||
'&default_format=JSONCompact' +
|
||||
/// Safety settings to prevent results that browser cannot display.
|
||||
'&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) {
|
||||
var json;
|
||||
try { json = JSON.parse(this.response); } catch (e) {}
|
||||
if (json !== undefined && json.statistics !== undefined) {
|
||||
renderResult(json);
|
||||
} else {
|
||||
renderUnparsedResult(this.response);
|
||||
}
|
||||
} else {
|
||||
renderError(this.response);
|
||||
}
|
||||
} else {
|
||||
//console.log(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('run').onclick = function()
|
||||
{
|
||||
post();
|
||||
}
|
||||
|
||||
document.getElementById('query').onkeypress = function(event)
|
||||
{
|
||||
/// Firefox has code 13 for Enter and Chromium has code 10.
|
||||
if (event.ctrlKey && (event.charCode == 13 || event.charCode == 10)) {
|
||||
post();
|
||||
}
|
||||
}
|
||||
|
||||
function clear()
|
||||
{
|
||||
var table = document.getElementById('data-table');
|
||||
while (table.firstChild) {
|
||||
table.removeChild(table.lastChild);
|
||||
}
|
||||
|
||||
document.getElementById('data-unparsed').innerText = '';
|
||||
document.getElementById('data-unparsed').style.display = 'none';
|
||||
|
||||
document.getElementById('error').innerText = '';
|
||||
document.getElementById('error').style.display = 'none';
|
||||
|
||||
document.getElementById('stats').innerText = '';
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/// 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.
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
/// A function to render raw data when non-default format is specified.
|
||||
function renderUnparsedResult(response)
|
||||
{
|
||||
clear();
|
||||
var data = document.getElementById('data-unparsed')
|
||||
data.innerText = response;
|
||||
/// inline-block make width adjust to the size of content.
|
||||
data.style.display = 'inline-block';
|
||||
}
|
||||
|
||||
function renderError(response)
|
||||
{
|
||||
clear();
|
||||
document.getElementById('error').innerText = response;
|
||||
document.getElementById('error').style.display = 'block';
|
||||
}
|
||||
|
||||
function setColorTheme(theme)
|
||||
{
|
||||
window.localStorage.setItem('theme', theme);
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
}
|
||||
|
||||
/// The choice of color theme is saved in browser.
|
||||
var theme = window.localStorage.getItem('theme');
|
||||
if (theme) {
|
||||
setColorTheme(theme);
|
||||
}
|
||||
|
||||
document.getElementById('toggle-light').onclick = function()
|
||||
{
|
||||
setColorTheme('light');
|
||||
}
|
||||
|
||||
document.getElementById('toggle-dark').onclick = function()
|
||||
{
|
||||
setColorTheme('dark');
|
||||
}
|
||||
</script>
|
||||
</html>
|
@ -8,6 +8,7 @@
|
||||
#include "ReplicasStatusHandler.h"
|
||||
#include "InterserverIOHTTPHandler.h"
|
||||
#include "PrometheusRequestHandler.h"
|
||||
#include "WebUIRequestHandler.h"
|
||||
|
||||
|
||||
namespace DB
|
||||
@ -78,7 +79,9 @@ static inline auto createHandlersFactoryFromConfig(
|
||||
for (const auto & key : keys)
|
||||
{
|
||||
if (key == "defaults")
|
||||
{
|
||||
addDefaultHandlersFactory(*main_handler_factory, server, async_metrics);
|
||||
}
|
||||
else if (startsWith(key, "rule"))
|
||||
{
|
||||
const auto & handler_type = server.config().getString(prefix + "." + key + ".handler.type", "");
|
||||
@ -112,7 +115,9 @@ static inline auto createHandlersFactoryFromConfig(
|
||||
static inline Poco::Net::HTTPRequestHandlerFactory * createHTTPHandlerFactory(IServer & server, const std::string & name, AsynchronousMetrics & async_metrics)
|
||||
{
|
||||
if (server.config().has("http_handlers"))
|
||||
{
|
||||
return createHandlersFactoryFromConfig(server, name, "http_handlers", async_metrics);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto factory = std::make_unique<HTTPRequestHandlerFactoryMain>(name);
|
||||
@ -168,6 +173,10 @@ void addCommonDefaultHandlersFactory(HTTPRequestHandlerFactoryMain & factory, IS
|
||||
auto replicas_status_handler = std::make_unique<HandlingRuleHTTPHandlerFactory<ReplicasStatusHandler>>(server);
|
||||
replicas_status_handler->attachNonStrictPath("/replicas_status")->allowGetAndHeadRequest();
|
||||
factory.addHandler(replicas_status_handler.release());
|
||||
|
||||
auto web_ui_handler = std::make_unique<HandlingRuleHTTPHandlerFactory<WebUIRequestHandler>>(server, "play.html");
|
||||
web_ui_handler->attachNonStrictPath("/play")->allowGetAndHeadRequest();
|
||||
factory.addHandler(web_ui_handler.release());
|
||||
}
|
||||
|
||||
void addDefaultHandlersFactory(HTTPRequestHandlerFactoryMain & factory, IServer & server, AsynchronousMetrics & async_metrics)
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include "StaticRequestHandler.h"
|
||||
#include "IServer.h"
|
||||
|
||||
#include "HTTPHandlerFactory.h"
|
||||
#include "HTTPHandlerRequestFilter.h"
|
||||
@ -17,6 +18,8 @@
|
||||
#include <Poco/Net/HTTPServerRequest.h>
|
||||
#include <Poco/Net/HTTPServerResponse.h>
|
||||
#include <Poco/Net/HTTPRequestHandlerFactory.h>
|
||||
#include <Poco/Util/LayeredConfiguration.h>
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
@ -1,16 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "IServer.h"
|
||||
|
||||
#include <Poco/Net/HTTPRequestHandler.h>
|
||||
#include <Common/StringUtils/StringUtils.h>
|
||||
#include <common/types.h>
|
||||
#include <IO/WriteBuffer.h>
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
class IServer;
|
||||
class WriteBuffer;
|
||||
|
||||
/// Response with custom string. Can be used for browser.
|
||||
class StaticRequestHandler : public Poco::Net::HTTPRequestHandler
|
||||
{
|
||||
@ -22,7 +21,11 @@ private:
|
||||
String response_expression;
|
||||
|
||||
public:
|
||||
StaticRequestHandler(IServer & server, const String & expression, int status_ = 200, const String & content_type_ = "text/html; charset=UTF-8");
|
||||
StaticRequestHandler(
|
||||
IServer & server,
|
||||
const String & expression,
|
||||
int status_ = 200,
|
||||
const String & content_type_ = "text/html; charset=UTF-8");
|
||||
|
||||
void writeResponse(WriteBuffer & out);
|
||||
|
||||
|
35
src/Server/WebUIRequestHandler.cpp
Normal file
35
src/Server/WebUIRequestHandler.cpp
Normal file
@ -0,0 +1,35 @@
|
||||
#include "WebUIRequestHandler.h"
|
||||
#include "IServer.h"
|
||||
|
||||
#include <Poco/Net/HTTPServerRequest.h>
|
||||
#include <Poco/Net/HTTPServerResponse.h>
|
||||
#include <Poco/Util/LayeredConfiguration.h>
|
||||
|
||||
#include <IO/HTTPCommon.h>
|
||||
#include <common/getResource.h>
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
WebUIRequestHandler::WebUIRequestHandler(IServer & server_, std::string resource_name_)
|
||||
: server(server_), resource_name(std::move(resource_name_))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void WebUIRequestHandler::handleRequest(Poco::Net::HTTPServerRequest & request, Poco::Net::HTTPServerResponse & response)
|
||||
{
|
||||
auto keep_alive_timeout = server.config().getUInt("keep_alive_timeout", 10);
|
||||
|
||||
response.setContentType("text/html; charset=UTF-8");
|
||||
|
||||
if (request.getVersion() == Poco::Net::HTTPServerRequest::HTTP_1_1)
|
||||
response.setChunkedTransferEncoding(true);
|
||||
|
||||
setResponseDefaultHeaders(response, keep_alive_timeout);
|
||||
response.setStatusAndReason(Poco::Net::HTTPResponse::HTTP_OK);
|
||||
response.send() << getResource(resource_name);
|
||||
}
|
||||
|
||||
}
|
23
src/Server/WebUIRequestHandler.h
Normal file
23
src/Server/WebUIRequestHandler.h
Normal file
@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <Poco/Net/HTTPRequestHandler.h>
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
class IServer;
|
||||
|
||||
/// Response with HTML page that allows to send queries and show results in browser.
|
||||
class WebUIRequestHandler : public Poco::Net::HTTPRequestHandler
|
||||
{
|
||||
private:
|
||||
IServer & server;
|
||||
std::string resource_name;
|
||||
public:
|
||||
WebUIRequestHandler(IServer & server_, std::string resource_name_);
|
||||
void handleRequest(Poco::Net::HTTPServerRequest & request, Poco::Net::HTTPServerResponse & response) override;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ SRCS(
|
||||
ReplicasStatusHandler.cpp
|
||||
StaticRequestHandler.cpp
|
||||
TCPHandler.cpp
|
||||
WebUIRequestHandler.cpp
|
||||
|
||||
)
|
||||
|
||||
|
1
tests/queries/0_stateless/01528_play.reference
Normal file
1
tests/queries/0_stateless/01528_play.reference
Normal file
@ -0,0 +1 @@
|
||||
🌞
|
6
tests/queries/0_stateless/01528_play.sh
Executable file
6
tests/queries/0_stateless/01528_play.sh
Executable file
@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
. "$CURDIR"/../shell_config.sh
|
||||
|
||||
${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_PORT_HTTP_PROTO}://${CLICKHOUSE_HOST}:${CLICKHOUSE_PORT_HTTP}/play" | grep -o '🌞'
|
Loading…
Reference in New Issue
Block a user