refined dashboard

This commit is contained in:
speeedmaster 2024-05-15 07:24:26 +00:00 committed by Aleksandr Tolkachev
parent bc50b68f05
commit 03e91450a3
6 changed files with 693 additions and 133 deletions

View File

@ -26,76 +26,45 @@
<style>
:root {
--background-color: #DDF8FF;
/* Or #FFFBEF; actually many pastel colors look great for light theme. */
--element-background-color: #FFF;
--input-background-color: #cccccc;
--accent-color: #faff68;
--border-color: #EEE;
--input-background-color: #BDD;
--shadow-color: rgba(0, 0, 0, 0.1);
--button-color: #FFAA00;
/* Orange on light-cyan is especially good. */
--text-color: #080808;
--header-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. */
--null-color: #A88;
--link-color: #06D;
--logo-color: #CEE;
--logo-color-active: #BDD;
--tab-switch-background-color: #FFF;
--tab-switch-hover-color: #F1F1F1;
--tab-switch-selected-border-color: #000;
--storage-path-hover-color: #FFF;
--storage-path-selected-border-color: #000;
--storage-ui-button-hover-color: #F1F1F1;
--storage-ui-button-border-color: #000;
}
[data-theme="dark"] {
--background-color: #414141;
--element-background-color: #252521;
--input-background-color: #111;
--accent-color: #faff68;
--border-color: #111;
--shadow-color: rgba(255, 255, 255, 0.1);
--text-color: #B4B4B4;
--header-text-color: #F9F9F9;
--button-color: #FFAA00;
--button-text-color: #000;
--button-active-color: #F00;
--button-active-text-color: #FFF;
--misc-text-color: #888;
--error-color: #400;
--null-color: #A88;
--link-color: #4BDAF7;
--logo-color: #222;
--logo-color-active: #333;
--tab-switch-background-color: #252521;
--tab-switch-hover-color: #151515;
--tab-switch-selected-border-color: #faff68;
--storage-path-hover-color: #151515;
--storage-path-selected-border-color: #faff68;
--storage-ui-button-hover-color: #151515;
--storage-ui-button-border-color: #faff68;
}
/* [data-theme="dark"] {
--background-color: #000;
--element-background-color: #102030;
--accent-color: #faff68;
--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;
--null-color: #A88;
--link-color: #4BDAF7;
--logo-color: #222;
--logo-color-active: #333;
--tab-switch-background-color: #102030;
--tab-switch-hover-color: #182838;
--tab-switch-selected-border-color: #CCC;
} */
* {
box-sizing: border-box;
/* For iPad */
@ -125,20 +94,49 @@
}
#controls {
/* When a page will be scrolled horizontally due to large table size, keep controls in place. */
position: sticky;
left: 0;
position: fixed;
top: 1rem;
right: 1rem;
z-index: 100;
}
/* Otherwise Webkit based browsers will display ugly border on focus. */
textarea,
input,
button {
input {
outline: none;
border: none;
color: var(--text-color);
}
.storage-ui-button {
display: inline-block;
background-color: transparent;
padding: 0.5rem 1rem;
margin-bottom: 0.5rem;
font-size: 1.0rem;
color: var(--header-text-color);
text-align: center;
text-decoration: none;
cursor: pointer;
border: 1px solid var(--storage-ui-button-border-color);
}
.storage-ui-button:hover {
background-color: var(--storage-ui-button-hover-color);
}
.storage-ui-button:disabled {
display: inline-block;
background-color: transparent;
padding: 0.5rem 1rem;
margin-bottom: 0.5rem;
font-size: 1.0rem;
border: none;
color: var(--text-color);
text-align: center;
text-decoration: none;
}
.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). */
@ -154,6 +152,7 @@
box-shadow: 0 0 1rem var(--shadow-color);
}
#displayStorageData,
#command-scrolling-container,
#command,
#command-output {
@ -180,9 +179,7 @@
display: none;
}
a,
a:visited {
color: var(--link-color);
a {
text-decoration: none;
}
@ -314,6 +311,11 @@
margin-bottom: 1rem;
}
h4 {
color: var(--header-text-color);
font-size: 1.0rem;
}
p {
line-height: 1.6;
margin-bottom: 1rem;
@ -349,37 +351,6 @@
text-shadow: 0 0 0;
}
.tab:checked:nth-of-type(3)~.tab__content:nth-of-type(3) {
opacity: 1;
/* transition: 0.5s opacity ease-in, 0.8s transform ease; */
/* flex-grow: 2; */
position: relative;
top: 0;
z-index: 100;
transform: translateY(0px);
text-shadow: 0 0 0;
}
.tab:checked:nth-of-type(4)~.tab__content:nth-of-type(4) {
opacity: 1;
/* transition: 0.5s opacity ease-in, 0.8s transform ease; */
position: relative;
top: 0;
z-index: 100;
transform: translateY(0px);
text-shadow: 0 0 0;
}
.tab:checked:nth-of-type(5)~.tab__content:nth-of-type(5) {
opacity: 1;
/* transition: 0.5s opacity ease-in, 0.8s transform ease; */
position: relative;
top: 0;
z-index: 100;
transform: translateY(0px);
text-shadow: 0 0 0;
}
#sidebar-version-display {
position: absolute;
bottom: -1rem;
@ -403,6 +374,56 @@
margin-left: 16rem;
}
.storage_viewer_container * {
position: relative;
box-sizing: border-box;
}
.storage_viewer_container ul {
padding-left: 1em;
list-style-type: none;
}
.storage_viewer_container>ul:first-of-type {
padding: 0;
}
.storage_viewer_container li span.storage_node_description {
cursor: pointer;
padding-left: 5px;
padding-bottom: 4px;
display: block;
text-align: left;
}
.storage_viewer_container li span.storage_node_description:hover {
background-color: var(--storage-path-hover-color);
}
.storage_viewer_container li span.storage_node_description.storage_leaf {
margin-left: 1.5em;
margin-top: 0.2em;
}
.storage_viewer_container span.storage_node_description.selected {
color: var(--header-color);
border-left: 4px solid var(--storage-path-selected-border-color);
}
.storage_viewer_container li span.storage_ui_icon,
.storage_viewer_container li span.storage_ui_icon * {
display: inline-block;
width: 1em;
font-size: 1.5rem;
}
.storage_viewer_container input.storage_node_name_input {
height: 2rem;
width: 100%;
padding-left: 5px;
padding-bottom: 4px;
background-color: var(--input-background-color);
}
</style>
</head>
@ -440,18 +461,88 @@
</div>
<div id="main">
<div id="controls">
<div id="theme_div">
<span id="toggle-dark">🌑</span><span id="toggle-light">🌞</span>
</div>
</div>
<div class="tab-wrap">
<!-- active tab on page load gets checked attribute -->
<input type="radio" id="tab_storage" name="tabGroup1" class="tab">
<input type="radio" id="tab_storage" name="tabGroup1" class="tab" checked>
<label for="tab_storage">Storage</label>
<input type="radio" id="tab_4lw" name="tabGroup1" class="tab" checked>
<input type="radio" id="tab_4lw" name="tabGroup1" class="tab">
<label for="tab_4lw">4lw</label>
<div class="tab__content">
<h3>Storage</h3>
<div style="display: flex; width: 100%;">
<div id="storage-container"></div>
<!-- Tree view side -->
<div style="flex-basis: 40%; box-sizing: border-box; margin-right: 0.5rem;">
<div id="storage-viewer-container"></div>
</div>
<div style="flex: 1; box-sizing: border-box; margin-left: 0.5rem">
<div style="display: flex; justify-content: space-between; align-items: flex-start;">
<div style="flex: 1; overflow-wrap: break-word;">
<p id="displayStoragePath" class="monospace">--</p>
</div>
<div style="display: flex; gap: 10px;">
<button class="storage-ui-button monospace" id="addChildButton"
onclick="storage_viewer.addNodeToSelectedNode()">
Add Child
</button>
<button class="storage-ui-button monospace" id="deleteButton"
onclick="storage_viewer.deleteSelectedNode()">
Delete
</button>
</div>
</div>
<div style="display: flex;">
<div style="flex-basis: 50%; box-sizing: border-box; margin-right: 0.1rem;">
<h4>numChildren:</h4>
<p id="displayStorageNumChildren" class="monospace">--</p>
<h4>dataLength:</h4>
<p id="displayStorageDataLength" class="monospace">--</p>
<h4>version:</h4>
<p id="displayStorageVersion" class="monospace">--</p>
<h4>aversion:</h4>
<p id="displayStorageAVersion" class="monospace">--</p>
<h4>cversion:</h4>
<p id="displayStorageCVersion" class="monospace">--</p>
<h4>ctime:</h4>
<p id="displayStorageCtime" class="monospace">--</p>
</div>
<div style="flex: 1; box-sizing: border-box; margin-left: 0.1rem">
<h4>ephemeralOwner:</h4>
<p id="displayStorageEphemeralOwner" class="monospace">--</p>
<h4>cZxid:</h4>
<p id="displayStorageCZxid" class="monospace">--</p>
<h4>pZxid:</h4>
<p id="displayStoragePZxid" class="monospace">--</p>
<h4>mZxid:</h4>
<p id="displayStorageMZxid" class="monospace">--</p>
<h4>mtime:</h4>
<p id="displayStorageMtime" class="monospace">--</p>
</div>
</div>
<div style="display: flex; justify-content: space-between; align-items: flex-start;">
<div style="display: flex; flex-direction: column; justify-content: flex-end;">
<h1>Node Data:</h1>
</div>
<div style="display: flex; align-items: flex-start;">
<button class="storage-ui-button monospace"
onclick="storage_viewer.saveSelectedNodeData()">Save</button>
</div>
</div>
<textarea id="displayStorageData" spellcheck="false" data-gramm="false" class="monospace"
style="height: 20em"></textarea>
</div>
</div>
</div>
<div class="tab__content">
@ -491,13 +582,6 @@
</div>
</div>
<div id="controls">
<div id="theme_div">
<span id="toggle-dark">🌑</span><span id="toggle-light">🌞</span>
</div>
</div>
<p id="logo-container">
<a href="https://clickhouse.com/">
<svg id="logo" width="50%" viewBox="0 0 180 28" version="1.1" xmlns="http://www.w3.org/2000/svg"
@ -543,6 +627,467 @@
</body>
<script type="text/javascript">
class StorageViewer {
constructor(container) {
this.container = container;
this.root = undefined;
this.selected_node = undefined;
}
async init() {
this.root = await this.loadRoot();
if (this.root.hasData()) {
await this.root.loadData();
}
this.root.select(); // select root
this.reload();
}
async loadRoot() {
const server_url = new URL(window.location);
server_url.pathname = `/api/v1/storage/list/`;
const res = await fetch(server_url.toString());
const data = res.ok ? await res.json() : undefined;
return data ? new StorageNode('/', undefined, data.stat, data.child_node_names) : undefined;
}
reload() {
if (this.container == null) {
console.warn("No container specified");
return;
}
if (this.root == null) {
console.warn("Root is not loaded");
return;
}
this.container.classList.add("storage_viewer_container");
var cnt = document.createElement("ul");
cnt.appendChild(this.root.render());
this.container.innerHTML = "";
this.container.appendChild(cnt);
this.renderSelectedNodeData();
}
updateSelectedNode(node) {
if (this.selected_node !== undefined) {
this.selected_node.selected = false;
}
this.selected_node = node;
this.selected_node.selected = true;
}
renderSelectedNodeData() {
// path
document.getElementById('displayStoragePath').innerText = this.selected_node?.path ?? '--';
// stat
document.getElementById('displayStorageAVersion').innerText = this.selected_node?.stat?.aversion ?? '--';
document.getElementById('displayStorageCZxid').innerText = this.selected_node?.stat?.cZxid ?? '--';
document.getElementById('displayStorageCVersion').innerText = this.selected_node?.stat?.cversion ?? '--';
document.getElementById('displayStorageDataLength').innerText = this.selected_node?.stat?.dataLength ?? '--';
document.getElementById('displayStorageEphemeralOwner').innerText = this.selected_node?.stat?.ephemeralOwner ?? '--';
document.getElementById('displayStorageMZxid').innerText = this.selected_node?.stat?.mZxid ?? '--';
document.getElementById('displayStorageNumChildren').innerText = this.selected_node?.stat?.numChildren ?? '--';
document.getElementById('displayStoragePZxid').innerText = this.selected_node?.stat?.pZxid ?? '--';
document.getElementById('displayStorageVersion').innerText = this.selected_node?.stat?.version ?? '--';
if (this.selected_node?.stat?.mtime != undefined) {
document.getElementById('displayStorageMtime').innerText = new Date(this.selected_node?.stat?.mtime).toLocaleString('en-US', { hour12: false });
} else {
document.getElementById('displayStorageMtime').innerText = '--';
}
if (this.selected_node?.stat?.mtime != undefined) {
document.getElementById('displayStorageCtime').innerText = new Date(this.selected_node?.stat?.mtime).toLocaleString('en-US', { hour12: false });
} else {
document.getElementById('displayStorageCtime').innerText = '--';
}
// data
if (this.selected_node && this.selected_node.data) {
document.getElementById('displayStorageData').value = new TextDecoder('utf-8').decode(this.selected_node.data);
} else {
document.getElementById('displayStorageData').value = '';
}
// buttons
var delete_button = document.getElementById('deleteButton');
if (!this.selected_node || !this.selected_node.isLeaf() || this.selected_node.isRoot()) {
delete_button.disabled = true;
delete_button.setAttribute('title', 'Only nodes with no children can be deleted.');
} else {
delete_button.disabled = false;
delete_button.removeAttribute('title');
}
}
async saveSelectedNodeData() {
if (!this.selected_node) {
return;
}
let data_to_save = document.getElementById('displayStorageData').value;
let encoder = new TextEncoder();
var server_url = new URL(window.location);
server_url.pathname = `/api/v1/storage/set${this.selected_node.path}`;
var request_url = server_url.toString();
request_url += '?' + new URLSearchParams({ version: this.selected_node.version })
const res = await fetch(request_url,
{
method: 'POST',
body: encoder.encode(data_to_save)
});
if (res.ok) {
await this.selected_node.reloadContent();
} else {
// TODO render conflict error.
}
this.reload();
}
async deleteSelectedNode() {
if (!this.selected_node) {
return;
}
if (!this.selected_node.isLeaf()) {
return; // the button should be disabled
}
if (this.selected_node.isRoot()) {
return; // the button should be disabled
}
this.selected_node.parent.removeChild(this.selected_node.name());
var server_url = new URL(window.location);
server_url.pathname = `/api/v1/storage/remove${this.selected_node.path}`;
var request_url = server_url.toString();
request_url += '?' + new URLSearchParams({ version: this.selected_node.version })
const res = await fetch(request_url,
{
method: 'POST',
});
if (res.ok) {
await this.selected_node.parent.reloadContent();
await this.selected_node.parent.select();
} else {
// TODO render removal error.
}
this.reload();
}
async addNodeToSelectedNode() {
if (!this.selected_node) {
return;
}
this.selected_node.makeChildStub();
// expand the node if not expanded
if (!this.selected_node.expanded) {
await this.selected_node.toggleExpanded();
}
this.reload();
// focus on the input after everything is rendered
var stub_name_input = document.getElementsByClassName('storage_node_name_input')[0];
stub_name_input.focus();
}
}
class StorageNodeStub {
constructor(parent) {
this.parent = parent
}
render() {
var result = document.createElement("li");
var input_field = document.createElement("input");
input_field.className = "storage_node_name_input"; // setting a class to your input field
input_field.storage_node = this;
input_field.onblur = function () {
// need to destroy the stub as user discarded the input
this.storage_node.destroy();
storage_viewer.reload();
}
// trigger event if user press Enter
input_field.addEventListener("keydown", async function (event) {
if (event.keyCode === 13) {
event.preventDefault();
if (input_field.value == '') {
// need to destroy the stub as user discarded the input
this.storage_node.destroy();
storage_viewer.reload();
return;
}
await this.storage_node.parent.createChild(input_field.value);
this.storage_node.destroy(); // we replaced the stub with a real node
storage_viewer.reload();
}
});
result.appendChild(input_field);
return result;
}
destroy() {
this.parent.stub_child = undefined;
}
}
class StorageNode {
constructor(path, parent, stat, children_names = []) {
this.path = path;
this.stat = stat;
this.data = undefined;
this.version = stat?.version ?? undefined;
this.children_names = children_names;
this.children = []; // nodes themselves are lazy loaded
this.expanded = false;
this.selected = false;
this.parent = parent
this.stub_child = undefined; // StorageNodeStub reference if a child node is in creation
}
isLeaf() {
return (this.children_names.length == 0 && this.stub_child == undefined);
}
isRoot() {
return this.path == '/';
}
async reloadContent() {
var server_url = new URL(window.location);
var promises = [];
server_url.pathname = `/api/v1/storage/list${this.path}`;
promises.push(fetch(server_url.toString())
.then(res => res.ok ? res.json() : undefined)
.then(data => {
if (data) {
this.stat = data.stat;
this.version = data.stat?.version ?? undefined;
this.children_names = data.child_node_names;
}
}));
promises.push(this.loadData());
await Promise.all(promises);
}
async expand() {
if (this.isLeaf()) {
console.warn('This node has no children.');
return;
}
var server_url = new URL(window.location);
server_url.pathname = `/api/v1/storage/list`;
var list_url = server_url.toString();
const childNodeRequests = this.children_names.map(child_name =>
fetch(list_url + this.getPathOfChild(child_name))
.then(res => res.ok ? res.json() : undefined)
.then(data => data ? new StorageNode(this.getPathOfChild(child_name), this, data.stat, data.child_node_names) : undefined)
);
this.children = await Promise.all(childNodeRequests);
this.expanded = true;
}
async collapse() {
if (storage_viewer.selected_node.isChildOf(this)) {
this.select();
}
this.children.forEach(function (child) {
child.collapse();
});
this.expanded = false;
}
render() {
var result = document.createElement("li");
var span_desc = document.createElement("span");
span_desc.className = "storage_node_description";
span_desc.storage_node = this;
span_desc.addEventListener("click", async function (e) {
var cur_el = e.target;
while (typeof cur_el.storage_node === "undefined" || cur_el.classList.contains("storage_viewer_container")) {
cur_el = cur_el.parentElement;
}
var node_cur = cur_el.storage_node;
if (typeof node_cur === "undefined") {
return;
}
if (node_cur.hasData()) {
await node_cur.loadData();
}
node_cur.select();
storage_viewer.reload();
});
if (this.selected) {
span_desc.classList.add("selected");
}
if (this.isLeaf()) {
var ret = '';
span_desc.innerHTML = ret + this.name() + "</span>";
span_desc.classList.add("storage_leaf");
result.appendChild(span_desc);
} else {
var ret = '';
var span_icon = document.createElement("span");
span_icon.className = "storage_ui_icon";
span_icon.style.fontFamily = "monospace";
span_icon.innerHTML = this.expanded ? "<span>&#x25BE;</span>" : "<span>&#x25B8;</span>";
span_icon.addEventListener("click", async function (e) {
e.stopPropagation();
var cur_el = e.target;
while (typeof cur_el.storage_node === "undefined" || cur_el.classList.contains("storage_viewer_container")) {
cur_el = cur_el.parentElement;
}
var node_cur = cur_el.storage_node;
if (typeof node_cur === "undefined") {
return;
}
if (!node_cur.isLeaf()) {
await node_cur.toggleExpanded();
}
storage_viewer.reload();
});
span_desc.appendChild(span_icon);
span_desc.appendChild(document.createTextNode(this.name()));
result.appendChild(span_desc);
if (this.expanded) {
var ul_container = document.createElement("ul");
if (this.stub_child) {
ul_container.appendChild(this.stub_child.render());
}
this.children.forEach(function (child) {
ul_container.appendChild(child.render());
});
result.appendChild(ul_container)
}
}
return result;
}
async toggleExpanded() {
if (this.isLeaf()) {
return;
}
if (this.expanded) {
this.collapse();
} else {
await this.expand();
}
};
select() {
storage_viewer.updateSelectedNode(this);
};
name() {
if (this.path === '/') {
return '/';
}
const split_path = this.path.split('/');
if (!split_path[split_path.length - 1]) {
split_path.pop();
}
return split_path.length > 0 ? split_path[split_path.length - 1] : 'unknown';
}
getPathOfChild(child_name) {
return (this.path == '/' ? '' : this.path) + `/${child_name}`;
}
hasData() {
return (this.stat?.dataLength ?? 0) !== 0;
}
async loadData() {
var server_url = new URL(window.location);
server_url.pathname = `/api/v1/storage/get`;
const res = await fetch(server_url.toString() + this.path);
if (res.ok) {
const buffer = await res.arrayBuffer();
this.data = new Uint8Array(buffer);
} else {
this.data = undefined;
}
}
removeChild(child_name) {
this.children_names = this.children_names.filter(name => name !== child_name);
this.children = this.children.filter(child => child.name() !== child_name);
}
makeChildStub() {
this.stub_child = new StorageNodeStub(this);
}
async createChild(child_name) {
var server_url = new URL(window.location);
server_url.pathname = `/api/v1/storage/create`;
var create_url = server_url.toString();
const res = await fetch(create_url + this.getPathOfChild(child_name),
{
method: 'POST'
});
if (res.ok) {
this.children_names.push(child_name);
var new_child_node = new StorageNode(this.getPathOfChild(child_name), this, undefined, []);
// load necessary data for a new node
await new_child_node.reloadContent();
await this.reloadContent()
this.children.push(new_child_node);
new_child_node.select();
} else {
// TODO render creation error.
}
}
isChildOf(node) {
return this.path.startsWith(node.path);
}
}
const current_url = new URL(window.location);
const opened_locally = location.protocol == 'file:';
@ -606,7 +1151,6 @@
window.localStorage.setItem('theme', theme);
}
document.documentElement.setAttribute('data-theme', theme);
redrawChart();
}
if (theme) {
@ -700,6 +1244,9 @@
}
}
var storage_viewer = new StorageViewer(document.getElementById("storage-viewer-container"));
storage_viewer.init();
start();
</script>

View File

@ -26,7 +26,8 @@ INCBIN(resource_keeper_dashboard_html, SOURCE_DIR "/programs/keeper/dashboard.ht
namespace DB
{
void KeeperDashboardWebUIRequestHandler::handleRequest(HTTPServerRequest & request, HTTPServerResponse & response, const ProfileEvents::Event &)
void KeeperDashboardWebUIRequestHandler::handleRequest(
HTTPServerRequest & request, HTTPServerResponse & response, const ProfileEvents::Event &)
{
/// Raw config reference is used here to avoid dependency on Context and ServerSettings.
/// This is painful, because this class is also used in a build with CLICKHOUSE_KEEPER_STANDALONE_BUILD=1
@ -65,7 +66,8 @@ try
response_json.set("ch_version", VERSION_DESCRIBE);
if (keeper_dispatcher->isServerActive()) {
if (keeper_dispatcher->isServerActive())
{
Poco::JSON::Object keeper_details;
auto & stats = keeper_dispatcher->getKeeperConnectionStats();
Keeper4LWInfo keeper_info = keeper_dispatcher->getKeeper4LWInfo();

View File

@ -19,7 +19,7 @@ private:
const IServer & server;
public:
explicit KeeperDashboardWebUIRequestHandler(const IServer & server_) : server(server_) {}
explicit KeeperDashboardWebUIRequestHandler(const IServer & server_) : server(server_) { }
void handleRequest(HTTPServerRequest & request, HTTPServerResponse & response, const ProfileEvents::Event & write_event) override;
};
@ -30,7 +30,10 @@ private:
std::shared_ptr<KeeperDispatcher> keeper_dispatcher;
public:
explicit KeeperDashboardContentRequestHandler(std::shared_ptr<KeeperDispatcher> keeper_dispatcher_) : keeper_dispatcher(keeper_dispatcher_) {}
explicit KeeperDashboardContentRequestHandler(std::shared_ptr<KeeperDispatcher> keeper_dispatcher_)
: keeper_dispatcher(keeper_dispatcher_)
{
}
void handleRequest(HTTPServerRequest & request, HTTPServerResponse & response, const ProfileEvents::Event & write_event) override;
};

View File

@ -28,20 +28,26 @@ namespace DB
namespace ErrorCodes
{
extern const int INVALID_CONFIG_PARAMETER;
extern const int INVALID_CONFIG_PARAMETER;
}
KeeperHTTPRequestHandlerFactory::KeeperHTTPRequestHandlerFactory(const std::string & name_)
: log(getLogger(name_)), name(name_)
KeeperHTTPRequestHandlerFactory::KeeperHTTPRequestHandlerFactory(const std::string & name_) : log(getLogger(name_)), name(name_)
{
}
std::unique_ptr<HTTPRequestHandler> KeeperHTTPRequestHandlerFactory::createRequestHandler(const HTTPServerRequest & request)
{
LOG_TRACE(log, "HTTP Request for {}. Method: {}, Address: {}, User-Agent: {}{}, Content Type: {}, Transfer Encoding: {}, X-Forwarded-For: {}",
name, request.getMethod(), request.clientAddress().toString(), request.get("User-Agent", "(none)"),
LOG_TRACE(
log,
"HTTP Request for {}. Method: {}, Address: {}, User-Agent: {}{}, Content Type: {}, Transfer Encoding: {}, X-Forwarded-For: {}",
name,
request.getMethod(),
request.clientAddress().toString(),
request.get("User-Agent", "(none)"),
(request.hasContentLength() ? (", Length: " + std::to_string(request.getContentLength())) : ("")),
request.getContentType(), request.getTransferEncoding(), request.get("X-Forwarded-For", "(none)"));
request.getContentType(),
request.getTransferEncoding(),
request.get("X-Forwarded-For", "(none)"));
for (auto & handler_factory : child_factories)
{
@ -50,8 +56,7 @@ std::unique_ptr<HTTPRequestHandler> KeeperHTTPRequestHandlerFactory::createReque
return handler;
}
if (request.getMethod() == Poco::Net::HTTPRequest::HTTP_GET
|| request.getMethod() == Poco::Net::HTTPRequest::HTTP_HEAD
if (request.getMethod() == Poco::Net::HTTPRequest::HTTP_GET || request.getMethod() == Poco::Net::HTTPRequest::HTTP_HEAD
|| request.getMethod() == Poco::Net::HTTPRequest::HTTP_POST)
{
return std::unique_ptr<HTTPRequestHandler>(new KeeperNotFoundHandler(hints.getHints(request.getURI())));
@ -66,8 +71,7 @@ void addDashboardHandlersToFactory(
auto dashboard_ui_creator = [&server]() -> std::unique_ptr<KeeperDashboardWebUIRequestHandler>
{ return std::make_unique<KeeperDashboardWebUIRequestHandler>(server); };
auto dashboard_handler
= std::make_shared<HandlingRuleHTTPHandlerFactory<KeeperDashboardWebUIRequestHandler>>(dashboard_ui_creator);
auto dashboard_handler = std::make_shared<HandlingRuleHTTPHandlerFactory<KeeperDashboardWebUIRequestHandler>>(dashboard_ui_creator);
dashboard_handler->attachStrictPath("/dashboard");
dashboard_handler->allowGetAndHeadRequest();
factory.addPathToHints("/dashboard");
@ -118,9 +122,7 @@ void addDefaultHandlersToFactory(
const Poco::Util::AbstractConfiguration & config)
{
auto readiness_creator = [keeper_dispatcher]() -> std::unique_ptr<KeeperHTTPReadinessHandler>
{
return std::make_unique<KeeperHTTPReadinessHandler>(keeper_dispatcher);
};
{ return std::make_unique<KeeperHTTPReadinessHandler>(keeper_dispatcher); };
auto readiness_handler = std::make_shared<HandlingRuleHTTPHandlerFactory<KeeperHTTPReadinessHandler>>(std::move(readiness_creator));
readiness_handler->attachStrictPath(config.getString("keeper_server.http_control.readiness.endpoint", "/ready"));
readiness_handler->allowGetAndHeadRequest();
@ -155,28 +157,34 @@ static inline auto createHandlersFactoryFromConfig(
const auto & handler_type = config.getString(prefix + "." + key + ".handler.type", "");
if (handler_type.empty())
throw Exception(ErrorCodes::INVALID_CONFIG_PARAMETER, "Handler type in config is not specified here: "
"{}.{}.handler.type", prefix, key);
throw Exception(
ErrorCodes::INVALID_CONFIG_PARAMETER,
"Handler type in config is not specified here: "
"{}.{}.handler.type",
prefix,
key);
if (handler_type == "dashboard")
{
addDashboardHandlersToFactory(*main_handler_factory, server, keeper_dispatcher);
}
if (handler_type == "commands")
{
addCommandsHandlersToFactory(*main_handler_factory, server, keeper_dispatcher);
}
if (handler_type == "storage")
{
addStorageHandlersToFactory(*main_handler_factory, server, keeper_dispatcher);
else
throw Exception(
ErrorCodes::INVALID_CONFIG_PARAMETER,
"Unknown handler type '{}' in config here: {}.{}.handler.type",
handler_type,
prefix,
key);
}
else
throw Exception(ErrorCodes::INVALID_CONFIG_PARAMETER, "Unknown handler type '{}' in config here: {}.{}.handler.type",
handler_type, prefix, key);
}
else
throw Exception(ErrorCodes::UNKNOWN_ELEMENT_IN_CONFIG, "Unknown element in config: "
"{}.{}, must be 'rule' or 'defaults'", prefix, key);
throw Exception(
ErrorCodes::UNKNOWN_ELEMENT_IN_CONFIG,
"Unknown element in config: "
"{}.{}, must be 'rule' or 'defaults'",
prefix,
key);
}
return main_handler_factory;
@ -185,10 +193,10 @@ static inline auto createHandlersFactoryFromConfig(
KeeperHTTPReadinessHandler::KeeperHTTPReadinessHandler(std::shared_ptr<KeeperDispatcher> keeper_dispatcher_)
: log(getLogger("KeeperHTTPReadinessHandler")), keeper_dispatcher(keeper_dispatcher_)
{
}
void KeeperHTTPReadinessHandler::handleRequest(HTTPServerRequest & /*request*/, HTTPServerResponse & response, const ProfileEvents::Event & /*write_event*/)
void KeeperHTTPReadinessHandler::handleRequest(
HTTPServerRequest & /*request*/, HTTPServerResponse & response, const ProfileEvents::Event & /*write_event*/)
{
try
{
@ -245,7 +253,8 @@ KeeperHTTPCommandsHandler::KeeperHTTPCommandsHandler(const IServer & server_, st
{
}
void KeeperHTTPCommandsHandler::handleRequest(HTTPServerRequest & request, HTTPServerResponse & response, const ProfileEvents::Event & /*write_event*/)
void KeeperHTTPCommandsHandler::handleRequest(
HTTPServerRequest & request, HTTPServerResponse & response, const ProfileEvents::Event & /*write_event*/)
try
{
std::vector<std::string> uri_segments;
@ -261,7 +270,7 @@ try
return;
}
// non-strict path "/api/v1/commands" filter is already attached
/// non-strict path "/api/v1/commands" filter is already attached
if (uri_segments.size() != 4)
{
response.setStatusAndReason(Poco::Net::HTTPResponse::HTTP_BAD_REQUEST, "Invalid command path");
@ -339,9 +348,7 @@ HTTPRequestHandlerFactoryPtr createKeeperHTTPHandlerFactory(
const std::string & name)
{
if (config.has("keeper_server.http_control.handlers"))
{
return createHandlersFactoryFromConfig(server, keeper_dispatcher, config, name, "keeper_server.http_control.handlers");
}
auto factory = std::make_shared<KeeperHTTPRequestHandlerFactory>(name);
addDefaultHandlersToFactory(*factory, server, keeper_dispatcher, config);

View File

@ -24,7 +24,7 @@ extern const int LOGICAL_ERROR;
extern const int TIMEOUT_EXCEEDED;
}
Poco::JSON::Object toJson(const Coordination::Stat & stat)
Poco::JSON::Object toJSON(const Coordination::Stat & stat)
{
Poco::JSON::Object result;
result.set("cZxid", stat.czxid);
@ -173,7 +173,7 @@ void KeeperHTTPStorageHandler::performZooKeeperExistsRequest(const std::string &
return;
Poco::JSON::Object response_json;
response_json.set("stat", toJson(exists_result_ptr->stat));
response_json.set("stat", toJSON(exists_result_ptr->stat));
std::ostringstream oss; // STYLE_CHECK_ALLOW_STD_STRING_STREAM
oss.exceptions(std::ios::failbit);
@ -200,7 +200,7 @@ void KeeperHTTPStorageHandler::performZooKeeperListRequest(const std::string & s
Poco::JSON::Object response_json;
response_json.set("child_node_names", list_result_ptr->names);
response_json.set("stat", toJson(list_result_ptr->stat));
response_json.set("stat", toJSON(list_result_ptr->stat));
std::ostringstream oss; // STYLE_CHECK_ALLOW_STD_STRING_STREAM
oss.exceptions(std::ios::failbit);

View File

@ -32,7 +32,8 @@ public:
private:
Coordination::ResponsePtr awaitKeeperResponse(std::shared_ptr<Coordination::ZooKeeperRequest> request);
void performZooKeeperRequest(const Coordination::OpNum opnum, const std::string & storage_path, HTTPServerRequest & request, HTTPServerResponse & response);
void performZooKeeperRequest(
const Coordination::OpNum opnum, const std::string & storage_path, HTTPServerRequest & request, HTTPServerResponse & response);
void performZooKeeperExistsRequest(const std::string & storage_path, HTTPServerResponse & response);
void performZooKeeperListRequest(const std::string & storage_path, HTTPServerResponse & response);