aboutsummaryrefslogtreecommitdiff
path: root/src/zenserver/frontend/html/zen.js
diff options
context:
space:
mode:
authorMartin Ridgers <[email protected]>2024-11-18 08:41:46 +0100
committerGitHub Enterprise <[email protected]>2024-11-18 08:41:46 +0100
commitcca69117b7ffac5cdd8933148ed9c94dd241528d (patch)
treeba9dfce342e86d9cbdf6cf54059e1e7d618eecee /src/zenserver/frontend/html/zen.js
parentoplog prep gc fix (#216) (diff)
downloadzen-cca69117b7ffac5cdd8933148ed9c94dd241528d.tar.xz
zen-cca69117b7ffac5cdd8933148ed9c94dd241528d.zip
Dashboard: oplog tree view (#217)
* Turned tables and progress bars and friends into "widgets!" * A step to abstracting away a page's the internal DOM structure * Folded sector creation into Page and pivoted it to a widget host * Try and keep start/count as numbers regardless of input * No need for the entry table to be defined up front now * Add op count and log sixe to oplog list page * Cache left side toolbar object * Bounds count page start when building list of oplog entrie * Start/end navigation tools * Build rest of entry page while waiting for indexer to load * Consistent naming with other pages * Spacially consolidate fetching code * Hide fetch latency to speed up index generation workers * Extract dashboard structure from zen.js monolith * Fix breadcrumbs after restructuring * Add view link to actions cell of oplogs list * Generator to enumerate names of entries in indexer * Methods for simple traversal of component relations * is() to check if a component is of a certain type * Extend attr() to get and unset a component's attributes * Unsetting all styles of anchor tags was underisrable * Restore page name as id of container element * A tree view of an oplog * Move helper class out to private module scope * Small tweak to use left var that already exists * Changelog update * Updated frontend .zip archive
Diffstat (limited to 'src/zenserver/frontend/html/zen.js')
-rw-r--r--src/zenserver/frontend/html/zen.js1386
1 files changed, 10 insertions, 1376 deletions
diff --git a/src/zenserver/frontend/html/zen.js b/src/zenserver/frontend/html/zen.js
index ffeaeb4ee..19f3ec69f 100644
--- a/src/zenserver/frontend/html/zen.js
+++ b/src/zenserver/frontend/html/zen.js
@@ -2,1361 +2,7 @@
"use strict";
-import { Fetcher } from "./util/fetcher.js"
-import { Friendly } from "./util/friendly.js"
-import { create_indexer } from "./indexer/indexer.js"
-
-////////////////////////////////////////////////////////////////////////////////
-class ComponentBase
-{
- constructor(element)
- {
- if (element instanceof ComponentBase)
- element = element._element;
-
- this._element = element;
- }
-
- inner()
- {
- return this._element;
- }
-
- destroy()
- {
- this._element.parentNode.removeChild(this._element);
- }
-}
-
-////////////////////////////////////////////////////////////////////////////////
-class ComponentDom extends ComponentBase
-{
- tag(tag="div")
- {
- var element = document.createElement(tag);
- this._element.appendChild(element);
- return this.new_component(element);
- }
-
- retag(new_tag)
- {
- if (this._element.tagName == new_tag.toUpperCase())
- return this;
-
- var element = document.createElement(new_tag);
- element.innerHTML = this._element.innerHTML;
- this._element.parentNode.replaceChild(element, this._element);
- this._element = element;
- return this;
- }
-
- text(value)
- {
- value = (value == undefined) ? "undefined" : value.toString();
- this._element.innerHTML = (value != "") ? value : "";
- return this;
- }
-
- id(value)
- {
- this._element.id = value;
- return this;
- }
-
- classify(value)
- {
- this._element.classList.add(value);
- return this;
- }
-
- style(key, value)
- {
- this._element.style[key] = value;
- return this;
- }
-
- attr(key, value)
- {
- this._element.setAttribute(key, value);
- return this;
- }
-}
-
-////////////////////////////////////////////////////////////////////////////////
-class ComponentInteract extends ComponentDom
-{
- link(resource=undefined, query_params={})
- {
- if (resource != undefined)
- {
- var href = resource;
- var sep = "?";
- for (const key in query_params)
- {
- href += sep + key + "=" + query_params[key];
- sep = "&";
- }
- }
- else
- href = "javascript:void(0);";
-
- var text = this._element.innerHTML;
- this._element.innerHTML = "";
- this.tag("a").text(text).attr("href", href);
- return this;
- }
-
- on(what, func, ...args)
- {
- const thunk = (src) => {
- if (src.target != this._element)
- return;
-
- func(...args);
- src.stopPropagation();
- };
-
- this._element.addEventListener(what, thunk);
- return this;
- }
-
- on_click(func, ...args)
- {
- this.classify("zen_action");
- return this.on("click", func, ...args);
- }
-}
-
-////////////////////////////////////////////////////////////////////////////////
-class Component extends ComponentInteract
-{
- new_component(...args)
- {
- return new Component(...args);
- }
-}
-
-
-
-////////////////////////////////////////////////////////////////////////////////
-class TableCell extends Component
-{
- constructor(element, row)
- {
- super(element);
- this._row = row;
- }
-
- get_table() { return this.get_row().get_table(); }
- get_row() { return this._row; }
-}
-
-////////////////////////////////////////////////////////////////////////////////
-class TableRow extends Component
-{
- constructor(element, table, index, cells)
- {
- super(element);
- this._table = table;
- this._index = index;
- this._cells = cells;
- }
-
- *[Symbol.iterator]()
- {
- for (var cell of this._cells)
- yield cell;
- }
-
- get_table() { return this._table; }
- get_index() { return this._index; }
- get_cell(index) { return this._cells.at(index); }
-}
-
-////////////////////////////////////////////////////////////////////////////////
-class Table extends Component
-{
- static Flag_EvenSpacing = 1 << 0;
- static Flag_PackRight = 1 << 1;
- static Flag_BiasLeft = 1 << 2;
- static Flag_FitLeft = 1 << 3;
-
- constructor(parent, column_names, flags=Table.Flag_EvenSpacing, index_base=0)
- {
- var root = parent.tag().classify("zen_table");
- super(root);
-
- var column_style;
- if (flags & Table.Flag_FitLeft) column_style = "max-content";
- else if (flags & Table.Flag_BiasLeft) column_style = "2fr";
- else column_style = "1fr";
- for (var i = 1; i < column_names.length; ++i)
- {
- const style = (flags & Table.Flag_PackRight) ? " auto" : " 1fr";
- column_style += style;
- }
-
- if (index_base >= 0)
- {
- column_names = ["#", ...column_names];
- column_style = "max-content " + column_style;
- }
-
- root.style("gridTemplateColumns", column_style);
-
- this._add_row(column_names, false);
-
- this._index = index_base;
- this._num_columns = column_names.length;
- this._rows = [];
- }
-
- *[Symbol.iterator]()
- {
- for (var row of this._rows)
- yield row;
- }
-
- get_row(index)
- {
- return this._rows.at(index);
- }
-
- _add_row(cells, indexed=true)
- {
- var index = -1;
- if (indexed && this._index >= 0)
- {
- index = this._index++;
- cells = [index, ...cells];
- }
-
- cells = cells.slice(0, this._num_columns);
- while (cells.length < this._num_columns)
- cells.push("");
-
- var ret = [];
- var row = this.tag();
- row = new TableRow(row, this, index, ret);
- for (const cell of cells)
- {
- var leaf = row.tag().text(cell);
- ret.push(new TableCell(leaf, row));
- }
-
- if (this._index >= 0)
- ret.shift();
-
- return row;
- }
-
- add_row(...args)
- {
- var row = this._add_row(args);
- this._rows.push(row);
- return row;
- }
-
- clear(index=0)
- {
- const elem = this._element;
- elem.replaceChildren(elem.firstElementChild);
- this._index = (this._index >= 0) ? index : -1;
- this._rows = [];
- }
-}
-
-
-
-////////////////////////////////////////////////////////////////////////////////
-class PropTable extends Table
-{
- constructor(parent)
- {
- super(parent, ["prop", "value"], Table.Flag_FitLeft, -1);
- this.classify("zen_proptable");
- }
-
- add_property(key, value)
- {
- return this.add_row(key, value);
- }
-
- add_object(object, friendly=false, prec=2)
- {
- const impl = (node, prefix="") => {
- for (const key in node)
- {
- var value = node[key];
- if (value instanceof Object &&
- (value.constructor.name == "Object" ||
- value.constructor.name == "Array"))
- {
- impl(value, prefix + key + ".");
- continue;
- }
-
- if (friendly && typeof value == "number")
- {
- if (key.indexOf("memory") >= 0) value = Friendly.kib(value);
- else if (key.indexOf("disk") >= 0) value = Friendly.kib(value);
- else if (value > 100000) value = Friendly.k(value);
- else if (value % 1) value = Friendly.sep(value, 3);
- else value = Friendly.sep(value, 0);
- }
-
- this.add_property(prefix + key, value);
- }
- };
-
- return impl(object);
- }
-
- filter(...needles)
- {
- for (var row of this)
- row.retag("div");
-
- if (needles.length == 0)
- return;
-
- for (var row of this)
- {
- var hide = false;
- var cell = row.get_cell(0);
- for (var needle of needles)
- hide = hide || (cell.inner().innerHTML.indexOf(needle) < 0);
-
- if (hide)
- row.retag("hidden");
- }
- }
-}
-
-
-
-////////////////////////////////////////////////////////////////////////////////
-class Modal
-{
- constructor()
- {
- const body = new Component(document.body);
- this._root = body.tag().classify("zen_modal");
-
- const bg = this._root.tag().classify("zen_modal_bg");
- bg.on("click", () => this._root.destroy());
-
- const rect = this._root.tag();
- this._title = rect.tag().classify("zen_modal_title");
- this._content = rect.tag().classify("zen_modal_message");
- this._buttons = rect.tag().classify("zen_modal_buttons");
- }
-
- title(value)
- {
- this._title.text(value);
- return this;
- }
-
- message(value)
- {
- this._content.text(value);
- return this;
- }
-
- option(name, func, ...args)
- {
- const thunk = () => {
- this._root.destroy();
- func(...args);
- };
- this._buttons.tag().text(name).on("click", thunk);
- return this;
- }
-}
-
-
-
-////////////////////////////////////////////////////////////////////////////////
-class Toolbar extends Component
-{
- static Side = class extends Component
- {
- add(name, tag="div") { return this.tag(tag).text(name); }
- sep() { return this.tag().text("|").classify("zen_toolbar_sep"); }
- }
-
- constructor(parent, inline=false)
- {
- var root = parent.tag().classify("zen_toolbar");
- super(root);
-
- if (inline)
- root.classify("zen_toolbar_inline");
-
- this._left = new Toolbar.Side(root.tag());
- this._right = new Toolbar.Side(root.tag());
- }
-
- left() { return this._left; }
- right() { return this._right; }
-}
-
-
-
-////////////////////////////////////////////////////////////////////////////////
-class ProgressBar extends Component
-{
- constructor(parent)
- {
- const root = parent.tag().classify("zen_progressbar");
- super(root);
- this._label = root.tag();
- root.tag(); // bg
- this._bar = root.tag();
- }
-
- set_progress(what, count=0, end=1)
- {
- const percent = (((count * 100) / end) | 0).toString() + "%";
- this._bar.style("width", percent);
- this._label.text(`${what}... ${count}/${end} (${percent})`);
- }
-}
-
-
-
-////////////////////////////////////////////////////////////////////////////////
-class Sectormatron extends Component
-{
- constructor(parent, depth=1)
- {
- super(parent);
- this._depth = depth;
- }
-
- add_section(name)
- {
- var node = this.tag();
- if (this._depth == 1)
- node.classify("zen_sector");
-
- node.tag("h" + this._depth).text(name);
- return new Sectormatron(node, this._depth + 1);
- }
-}
-
-
-
-////////////////////////////////////////////////////////////////////////////////
-class Page
-{
- constructor(parent, params)
- {
- this._parent = parent;
- this._params = params;
- this._sectormatron = new Sectormatron(parent);
- }
-
- set_title(name)
- {
- var value = document.title;
- if (name.length && value.length)
- name = value + " - " + name;
- document.title = name;
- }
-
- get_param(name, fallback=undefined)
- {
- var ret = this._params.get(name);
- if (ret != undefined)
- return ret;
-
- if (fallback != undefined)
- this.set_param(name, fallback);
-
- return fallback;
- }
-
- set_param(name, value, update=true)
- {
- this._params.set(name, value);
- if (!update)
- return value;
-
- const url = new URL(window.location);
- for (var [key, xfer] of this._params)
- url.searchParams.set(key, xfer);
- history.replaceState(null, "", url);
-
- return value;
- }
-
- add_section(name)
- {
- return this._sectormatron.add_section(name);
- }
-
- reload()
- {
- window.location.reload();
- }
-}
-
-
-
-////////////////////////////////////////////////////////////////////////////////
-class ZenPage extends Page
-{
- constructor(parent, ...args)
- {
- super(parent, ...args);
- super.set_title("zen");
- this.add_branding(parent);
- this.generate_crumbs();
- }
-
- add_branding(parent)
- {
- var root = parent.tag().id("branding");
-
- const zen_store = root.tag("pre").id("logo").text(
- "_________ _______ __\n" +
- "\\____ /___ ___ / ___// |__ ___ ______ ____\n" +
- " / __/ __ \\ / \\ \\___ \\\\_ __// \\\\_ \\/ __ \\\n" +
- " / \\ __// | \\/ \\| | ( - )| |\\/\\ __/\n" +
- "/______/\\___/\\__|__/\\______/|__| \\___/ |__| \\___|"
- );
- zen_store.tag().id("go_home").on_click(() => window.location.search = "");
-
- root.tag("img").attr("src", "favicon.ico").id("ue_logo");
-
- /*
- _________ _______ __
- \____ /___ ___ / ___// |__ ___ ______ ____
- / __/ __ \ / \ \___ \\_ __// \\_ \/ __ \
- / \ __// | \/ \| | ( - )| |\/\ __/
- /______/\___/\__|__/\______/|__| \___/ |__| \___|
- */
- }
-
- set_title(...args)
- {
- super.set_title(...args);
- }
-
- generate_crumbs()
- {
- const auto_name = this.constructor.name;
- if (auto_name == "Start")
- return;
-
- const crumbs = this._parent.tag().id("crumbs");
- const new_crumb = function(name, search=undefined) {
- crumbs.tag();
- var crumb = crumbs.tag().text(name);
- if (search != undefined)
- crumb.on_click((x) => window.location.search = x, search);
- };
-
- new_crumb("home", "");
-
- var project = this.get_param("project");
- if (project != undefined)
- {
- var oplog = this.get_param("oplog");
- if (oplog != undefined)
- {
- new_crumb("project", `?page=project&project=${project}`);
- if (this.get_param("opkey"))
- new_crumb("oplog", `?page=oplog&project=${project}&oplog=${oplog}`);
- }
- }
-
- new_crumb(auto_name.toLowerCase());
- }
-}
-
-
-
-////////////////////////////////////////////////////////////////////////////////
-class Entry extends ZenPage
-{
- main()
- {
- this.set_title("oplog entry");
-
- const project = this.get_param("project");
- const oplog = this.get_param("oplog");
- const opkey = this.get_param("opkey");
-
- this._entry = new Fetcher()
- .resource("prj", project, "oplog", oplog, "entries")
- .param("opkey", opkey)
- .cbo();
-
- this.load_indexer(project, oplog, () => this._build_page());
- }
-
- async load_indexer(project, oplog, loaded_cb)
- {
- if (this._indexer != undefined)
- return loaded_cb();
-
- const progress_bar = new ProgressBar(this._parent);
- progress_bar.set_progress("indexing");
- const indexer = create_indexer(project, oplog, (...args) => {
- progress_bar.set_progress(...args);
- });
- this._indexer = await indexer;
- progress_bar.destroy();
-
- loaded_cb();
- }
-
- async _build_page()
- {
- var entry = await this._entry;
- entry = entry.as_object().find("entry").as_object();
-
- const name = entry.find("key").as_value();
- var section = this.add_section(name);
-
- // tree
- {
- var tree = entry.find("$tree");
- if (tree == undefined)
- tree = this._convert_legacy_to_tree(entry);
-
- if (tree == undefined)
- return this._display_unsupported(section, entry);
-
- delete tree["$id"];
-
- const sub_section = section.add_section("deps");
- for (const dep_name in tree)
- {
- const dep_section = sub_section.add_section(dep_name);
- const table = new Table(dep_section, ["name", "id"], Table.Flag_PackRight);
- for (const dep_id of tree[dep_name])
- {
- const cell_values = ["", dep_id.toString(16).padStart(16, "0")];
- const row = table.add_row(...cell_values);
-
- var opkey = this._indexer.lookup_id(dep_id);
- row.get_cell(0).text(opkey).on_click((k) => this.view_opkey(k), opkey);
- }
- }
- }
-
- // data
- {
- const sub_section = section.add_section("data");
- const table = new Table(sub_section, ["name", "actions"], Table.Flag_PackRight);
- for (const field_name of ["packagedata", "bulkdata"])
- {
- var pkg_data = entry.find(field_name);
- if (pkg_data == undefined)
- continue;
-
- for (const item of pkg_data.as_array())
- {
- var io_hash;
- var file_name;
- for (const field of item.as_object())
- {
- if (field.is_named("data")) io_hash = field.as_value();
- else if (field.is_named("filename")) file_name = field.as_value();
- }
-
- if (io_hash instanceof Uint8Array)
- {
- var ret = "";
- for (var x of io_hash)
- ret += x.toString(16).padStart(2, "0");
- io_hash = ret;
- }
-
- const row = table.add_row(file_name);
-
- const project = this.get_param("project");
- const oplog = this.get_param("oplog");
- const link = row.get_cell(0).link(
- "/" + ["prj", project, "oplog", oplog, io_hash].join("/")
- );
-
- const do_nothing = () => void(0);
- const action_tb = new Toolbar(row.get_cell(-1), true);
- action_tb.left().add("copy-hash").on_click(async (v) => {
- await navigator.clipboard.writeText(v);
- }, io_hash);
- }
- }
- }
-
- // props
- {
- const object = entry.to_js_object();
- var sub_section = section.add_section("props");
- new PropTable(sub_section).add_object(object);
- }
- }
-
- _display_unsupported(section, entry)
- {
- const object = entry.to_js_object();
- const text = JSON.stringify(object, null, " ");
- section.tag("pre").text(text);
- }
-
- _convert_legacy_to_tree(entry)
- {
- const pkg_data = entry.find("packagedata");
- if (pkg_data == undefined)
- return
-
- const tree = {};
-
- var id = 0n;
- for (var item of pkg_data.as_array())
- {
- var pkg_id = item.as_object().find("id");
- if (pkg_id == undefined)
- continue;
-
- pkg_id = pkg_id.as_value().subarray(0, 8);
- for (var i = 7; i >= 0; --i)
- {
- id <<= 8n;
- id |= BigInt(pkg_id[i]);
- }
- break;
- }
- tree["$id"] = id;
-
- const pkgst_entry = entry.find("packagestoreentry").as_object();
-
- for (const field of pkgst_entry)
- {
- const field_name = field.get_name();
- if (!field_name.endsWith("importedpackageids"))
- continue;
-
- var dep_name = field_name.slice(0, -18);
- if (dep_name.length == 0)
- dep_name = "imported";
-
- var out = tree[dep_name] = [];
- for (var item of field.as_array())
- out.push(item.as_value(BigInt));
- }
-
- return tree;
- }
-
- view_opkey(opkey)
- {
- const params = this._params;
- params.set("opkey", opkey);
- window.location.search = params;
- }
-}
-
-
-
-////////////////////////////////////////////////////////////////////////////////
-class Oplog extends ZenPage
-{
- constructor(...args)
- {
- super(...args);
-
- this._index_start = Number(this.get_param("start", 0));
- this._index_count = Number(this.get_param("count", 50));
- this._entry_table = undefined;
- }
-
- async main()
- {
- const project = this.get_param("project");
- const oplog = this.get_param("oplog");
-
- this._indexer = this._create_indexer(project, oplog);
-
- this.set_title("oplog - " + oplog);
-
- var section = this.add_section(project + " - " + oplog);
-
- this._build_nav(section);
-
- this._entry_table = new Table(section, ["key"]);
- await this._build_table();
- }
-
- async _create_indexer(project, oplog)
- {
- const progress_bar = new ProgressBar(this._parent);
- progress_bar.set_progress("indexing");
- var indexer = create_indexer(project, oplog, (...args) => {
- progress_bar.set_progress(...args);
- });
- indexer = await indexer;
- progress_bar.destroy();
- return indexer;
- }
-
- _build_nav(section)
- {
- var nav = new Toolbar(section);
- nav.left().add("&lt;&lt;").on_click(() => this._on_next_prev(-10));
- nav.left().add("prev") .on_click(() => this._on_next_prev( -1));
- nav.left().add("next") .on_click(() => this._on_next_prev( 1));
- nav.left().add("&gt;&gt;").on_click(() => this._on_next_prev( 10));
-
- nav.left().sep();
- for (var count of [10, 25, 50, 100])
- {
- var handler = (n) => this._on_change_count(n);
- nav.left().add(count).on_click(handler, count);
- }
-
- var search_input = nav.right().add("search:", "label").tag("input")
- search_input.on("change", (x) => this._search(x.inner().value), search_input);
- }
-
- async _build_table()
- {
- const project = this.get_param("project");
- const oplog = this.get_param("oplog");
-
- var entries = new Fetcher()
- .resource("prj", project, "oplog", oplog, "entries")
- .param("start", this.set_param("start", this._index_start))
- .param("count", this.set_param("count", this._index_count))
- .json();
-
- this._entry_table.clear(this._index_start);
-
- entries = (await entries)["entries"];
- if (entries == undefined)
- return;
-
- for (const entry of entries)
- {
- var row = this._entry_table.add_row(entry["key"]);
-
- row.get_cell(0).link("", {
- "page" : "entry",
- "project" : project,
- "oplog" : oplog,
- "opkey" : entry["key"],
- });
- }
- }
-
- _on_change_count(value)
- {
- this._index_count = parseInt(value);
- this._build_table();
- }
-
- _on_next_prev(direction)
- {
- const index = this._index_start + (this._index_count * direction);
- this._index_start = Math.max(0, index);
- this._build_table();
- }
-
- async _search(needle)
- {
- needle = needle.trim();
- if (needle.length < 3)
- return;
-
- this._entry_table.clear(this._index_start);
-
- const project = this.get_param("project");
- const oplog = this.get_param("oplog");
-
- const indexer = await this._indexer;
-
- var added = 0;
- const truncate_at = this.get_param("searchmax") || 250;
- for (var name of indexer.search(needle))
- {
- var row = this._entry_table.add_row(name);
-
- row.get_cell(0).link("", {
- "page" : "entry",
- "project" : project,
- "oplog" : oplog,
- "opkey" : name,
- });
-
- if (++added >= truncate_at)
- {
- this._entry_table.add_row("...truncated");
- break;
- }
- }
- }
-}
-
-
-
-////////////////////////////////////////////////////////////////////////////////
-class Project extends ZenPage
-{
- async main()
- {
- // info
- var section = this.add_section("info");
-
- const project = this.get_param("project");
-
- this.set_title("project - " + project);
-
- var info = await new Fetcher().resource("prj", project).json();
- var prop_table = new PropTable(section);
- for (const key in info)
- {
- if (key == "oplogs")
- continue;
-
- prop_table.add_property(key, info[key]);
- }
-
- // oplog
- section = this.add_section("oplogs");
-
- var oplog_table = new Table(
- section,
- ["name", "marker", "size", "ops", "expired", "actions"],
- Table.Flag_PackRight
- )
-
- var count = 0;
- for (const oplog of info["oplogs"])
- {
- const name = oplog["id"];
-
- var info = new Fetcher().resource("prj", project, "oplog", name).json();
-
- var row = oplog_table.add_row(name);
-
- var cell = row.get_cell(0);
- cell.link("", {
- "page" : "oplog",
- "project" : project,
- "oplog" : name,
- });
-
- cell = row.get_cell(-1);
- var action_tb = new Toolbar(cell, true);
- action_tb.left().add("drop").on_click((x) => this.drop_oplog(x), name);
-
- info = await info;
- row.get_cell(1).text(info["markerpath"]);
- row.get_cell(2).text(Friendly.kib(info["totalsize"]));
- row.get_cell(3).text(Friendly.sep(info["opcount"]));
- row.get_cell(4).text(info["expired"]);
- }
- }
-
- drop_oplog(oplog_id)
- {
- const drop = async () => {
- await new Fetcher()
- .resource("prj", this.get_param("project"), "oplog", oplog_id)
- .delete();
- this.reload();
- };
-
- new Modal()
- .title("Confirmation")
- .message(`Drop oplog '${oplog_id}'?`)
- .option("Yes", () => drop())
- .option("No");
- }
-}
-
-
-
-////////////////////////////////////////////////////////////////////////////////
-class Test extends ZenPage
-{
- main()
- {
- var gen_word = (function() {
- var s = 0x314251;
- var r = function(a, b) {
- s = (s * 0x493) & 0x7fffffff;
- return ((s >> 3) % (b - a)) + a;
- };
- return function(a=5, b=10) {
- const co = "aeioubcdfghjklmnpqrstvwxyz";
- var ret = "";
- for (var i = 0, n = r(a,b); i < n; ++i)
- ret += co[r(0, co.length)];
- return ret;
- };
- })();
- var gen_para = function(a=5, b=10, s=" ") {
- var ret = gen_word(2, 9);
- for (var i = 0; i < ((ret.length * 0x493) % (b - a)) + b; ++i)
- ret += s + gen_word(2, 9);
- return ret;
- }
-
- this.set_title("test");
-
- // swatches
- const swatches = this._parent.tag()
- .style("position", "absolute")
- .style("top", "3.5em")
- .style("left", "3.5em")
- for (var suffix of ["g0", "g1", "g2", "g3", "g4",
- "p0", "p1", "p2", "p3", "p4",
- "ln", "er"])
- {
- var swatch = swatches.tag()
- .style("float", "left")
- .style("width", "2em")
- .style("height", "2em")
- .style("background-color", `var(--theme_${suffix})`)
- .text(suffix);
- }
-
- // section
- var section0 = this.add_section("section");
- var section1 = section0.add_section("sub-section");
- var section2 = section1.add_section("sub-sub-section");
-
- // table
- const cols = [gen_word(), gen_word(), gen_word(), gen_word()];
- var tables = [
- new Table(section0, cols),
- new Table(section1, cols, Table.Flag_EvenSpacing, 5),
- new Table(section2, cols, Table.Flag_EvenSpacing, -1),
- ];
-
- for (const table of tables)
- {
- table.add_row(gen_word());
- table.add_row(gen_word(), gen_word(), gen_word(), gen_word());
- table.add_row(gen_word(), gen_word(), gen_para(15, 25), gen_word(), gen_word(), gen_word(), gen_word(), gen_word());
- }
-
- // spacing tests
- {
- const spacing_section = section0.add_section("spacing");
- const flags = {
- "EvenSpacing" : Table.Flag_EvenSpacing,
- "EvenSpacing|BiasLeft" : Table.Flag_EvenSpacing | Table.Flag_BiasLeft,
- "PackRight" : Table.Flag_PackRight,
- };
- for (const flag_name in flags)
- {
- const flag = flags[flag_name];
- const another_table = new Table(
- spacing_section,
- [flag_name, gen_word(), gen_word(), gen_word(), gen_word()],
- flag,
- );
- for (var i = 0; i < 3; ++i)
- another_table.add_row(gen_para(1, 5), gen_para(1, 3), gen_word(), gen_word(), gen_word());
- }
- }
-
- // prop-table
- var pt_section = section0.add_section("prop-table")
- var prop_table = new PropTable(pt_section);
- for (var i = 0; i < 7; ++i)
- prop_table.add_property(gen_word(), gen_para(1, 20, "/"));
-
- // misc
- const misc_section = section0.add_section("misc").add_section("misc");
- misc_section.tag().text("just text");
- misc_section.tag().text("this is a link").link();
- misc_section.tag().text("MODAL DIALOG").on_click((e) => {
- new Modal()
- .title("modal")
- .message("here is a message what I wrote")
- .option("press me!", () => { alert("hi"); })
- .option("cancel", () => void(0));
- });
-
- // toolbar
- pt_section.add_section("toolbar");
- var toolbar = new Toolbar(pt_section);
- for (const side of [toolbar.left(), toolbar.right()])
- {
- side.add("tb_item0");
- side.add("tb_item1");
- side.sep();
- side.add("tb_item2");
- }
-
- var tb_item_clicked = function(arg0, arg1) {
- alert(arg0 + " != " + arg1);
- };
- var row = prop_table.add_property("toolbar", "");
- toolbar = new Toolbar(row.get_cell(-1), true);
- toolbar.left() .add("tbitem0").on_click(tb_item_clicked, 11, -22);
- toolbar.left() .add("tbitem1").on_click(tb_item_clicked, 22, -33);
- toolbar.right().add("tbitem2").on_click(tb_item_clicked, 33, -55);
- toolbar.right().add("tbitem3").on_click(tb_item_clicked, 44, -88);
-
- // progress bar
- const progress_bar = new ProgressBar(this._parent);
- setInterval(function() {
- var count = 0
- return () => {
- count = (count + 1) % 100;
- progress_bar.set_progress("testing", count, 100);
- };
- }(), 49.3);
-
- // error
- throw Error("deliberate error");
- }
-}
-
-
-
-////////////////////////////////////////////////////////////////////////////////
-class Start extends ZenPage
-{
- async main()
- {
- var section = this.add_section("projects");
-
- // project list
- var columns = [
- "name",
- "project_dir",
- "engine_dir",
- "actions",
- ];
- var table = new Table(section, columns);
-
- for (const project of await new Fetcher().resource("/prj/list").json())
- {
- var row = table.add_row(
- "",
- project.ProjectRootDir,
- project.EngineRootDir,
- );
-
- var cell = row.get_cell(0);
- cell.tag().text(project.Id).on_click((x) => this.view_project(x), project.Id);
-
- var cell = row.get_cell(-1);
- var action_tb = new Toolbar(cell, true);
- action_tb.left().add("view").on_click((x) => this.view_project(x), project.Id);
- action_tb.left().add("drop").on_click((x) => this.drop_project(x), project.Id);
- }
-
- // stats
- section = this.add_section("stats");
- columns = [
- "name",
- "req count",
- "size disk",
- "size mem",
- "cid total",
- ];
- const stats_table = new Table(section, columns, Table.Flag_PackRight);
- var providers = new Fetcher().resource("stats").json();
- for (var provider of (await providers)["providers"])
- {
- var stats = await new Fetcher().resource("stats", provider).json();
- var values = [""];
- try {
- values.push(stats.requests.count);
- const size_stat = (stats.store || stats.cache).size;
- values.push(Friendly.kib(size_stat.disk));
- values.push(Friendly.kib(size_stat.memory));
- values.push(stats.cid.size.total);
- }
- catch {}
- row = stats_table.add_row(...values);
- row.get_cell(0).tag().text(provider).on_click((x) => this.view_stat(x), provider);
- }
- }
-
- view_stat(provider)
- {
- window.location = "?page=stat&provider=" + provider;
- }
-
- view_project(project_id)
- {
- window.location = "?page=project&project=" + project_id;
- }
-
- drop_project(project_id)
- {
- const drop = async () => {
- await new Fetcher().resource("prj", project_id).delete();
- this.reload();
- };
-
- new Modal()
- .title("Confirmation")
- .message(`Drop project '${project_id}'?`)
- .option("Yes", () => drop())
- .option("No");
- }
-}
-
-
-
-////////////////////////////////////////////////////////////////////////////////
-class Stat extends ZenPage
-{
- static TemporalStat = class
- {
- constructor(data, as_bytes)
- {
- this._data = data;
- this._as_bytes = as_bytes;
- }
-
- toString()
- {
- const columns = [
- /* count */ {},
- /* rate */ {},
- /* t */ {}, {},
- ];
- const data = this._data;
- for (var key in data)
- {
- var out = columns[0];
- if (key.startsWith("rate_")) out = columns[1];
- else if (key.startsWith("t_p")) out = columns[3];
- else if (key.startsWith("t_")) out = columns[2];
- out[key] = data[key];
- }
-
- var friendly = this._as_bytes ? Friendly.kib : Friendly.sep;
-
- var content = "";
- for (var i = 0; i < columns.length; ++i)
- {
- content += "<pre>";
- const column = columns[i];
- for (var key in column)
- {
- var value = column[key];
- if (i)
- {
- value = Friendly.sep(value, 2);
- key = key.padStart(9);
- content += key + ": " + value;
- }
- else
- content += friendly(value);
- content += "\n";
- }
- content += "</pre>";
- }
-
- return content;
- }
- }
-
- async main()
- {
- const provider = this.get_param("provider", "z$");
- var stats = new Fetcher()
- .resource("stats", provider)
- .param("cidstorestats", "true")
- .param("cachestorestats", "true")
- .json();
-
- this.set_title("stat - " + provider);
- const section = this.add_section(provider);
-
- var toolbar = new Toolbar(section);
- var tb_right = toolbar.right();
- tb_right.add("filter:");
- tb_right.add("-none-").on_click((x) => this.update_filter(""));
- for (var preset of ["read.", "write.", ".request", ".bytes"])
- tb_right.add(preset).on_click((x) => this.update_filter(x), preset);
- this._filter_input = tb_right.add("", "label").tag("input");
- this._filter_input.on("change", (x) => this.update_filter(x.inner().value), this._filter_input);
-
- this._table = new PropTable(section);
-
- this._stats = stats = await stats;
- this._condense(stats);
-
- var first = undefined;
- for (var name in stats)
- {
- first = first || name;
- toolbar.left().add(name).on_click((x) => this.view_category(x), name);
- }
-
- var filter = this.get_param("filter");
-
- first = this.get_param("view", first);
- this.view_category(first);
-
- if (filter)
- this.update_filter(filter);
- }
-
- view_category(name)
- {
- const friendly = (this.get_param("raw") == undefined);
- this._table.clear();
- this._table.add_object(this._stats[name], friendly, 3);
- this.set_param("view", name);
- this.update_filter("");
- }
-
- update_filter(needle)
- {
- this._filter_input.attr("value", needle);
-
- this.set_param("filter", needle);
- if (!needle)
- return this._table.filter();
-
- var needles = needle.split(" ");
- this._table.filter(...needles);
- }
-
- _condense(stats)
- {
- const impl = function(node)
- {
- for (var name in node)
- {
- const candidate = node[name];
- if (!(candidate instanceof Object))
- continue;
-
- if (candidate["rate_mean"] != undefined)
- {
- const as_bytes = (name.indexOf("bytes") >= 0);
- node[name] = new Stat.TemporalStat(candidate, as_bytes);
- continue;
- }
-
- impl(candidate);
- }
- }
-
- for (var name in stats)
- impl(stats[name]);
- }
-}
-
-
+import { Component } from "./util/component.js"
////////////////////////////////////////////////////////////////////////////////
function display_error(message, stack)
@@ -1378,32 +24,20 @@ window.addEventListener("unhandledrejection", function(evt) {
display_error(reason.message, reason.stack);
});
-
-
////////////////////////////////////////////////////////////////////////////////
async function main()
{
- const params = new URLSearchParams(window.location.search);
- const page = params.get("page");
-
- const body = new Component(document.body).id(page);
+ const body = new Component(document.body);
const root = body.tag().id("container").tag();
- var impl = undefined;
- if (page == "project") impl = new Project(root, params);
- else if (page == "stat") impl = new Stat(root, params);
- else if (page == "oplog") impl = new Oplog(root, params);
- else if (page == "entry") impl = new Entry(root, params);
- else if (page == "test") impl = new Test(root, params);
- else if (page == undefined) impl = new Start(root, params);
-
- if (impl == undefined)
- {
- root.tag().text("unknown page");
- return;
- }
-
- impl.main();
+ const params = new URLSearchParams(window.location.search);
+ var page = params.get("page") || "start";
+ page = page.replace(".", "");
+ page = page.replace("/", "");
+ page = page.replace("\\", "");
+ root.id(page);
+ const module = await import(`./pages/${page}.js`);
+ new module.Page(root, params).main();
}
main();