diff options
| author | Martin Ridgers <[email protected]> | 2024-11-18 08:41:46 +0100 |
|---|---|---|
| committer | GitHub Enterprise <[email protected]> | 2024-11-18 08:41:46 +0100 |
| commit | cca69117b7ffac5cdd8933148ed9c94dd241528d (patch) | |
| tree | ba9dfce342e86d9cbdf6cf54059e1e7d618eecee /src/zenserver/frontend/html/zen.js | |
| parent | oplog prep gc fix (#216) (diff) | |
| download | zen-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.js | 1386 |
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("<<").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(">>").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(); |