diff options
Diffstat (limited to 'src/zenserver/frontend/html/pages/start.js')
| -rw-r--r-- | src/zenserver/frontend/html/pages/start.js | 281 |
1 files changed, 177 insertions, 104 deletions
diff --git a/src/zenserver/frontend/html/pages/start.js b/src/zenserver/frontend/html/pages/start.js index df70ea2f4..d06040b2f 100644 --- a/src/zenserver/frontend/html/pages/start.js +++ b/src/zenserver/frontend/html/pages/start.js @@ -6,7 +6,7 @@ import { ZenPage } from "./page.js" import { Fetcher } from "../util/fetcher.js" import { Friendly } from "../util/friendly.js" import { Modal } from "../util/modal.js" -import { Table, Toolbar } from "../util/widgets.js" +import { Table, Toolbar, Pager } from "../util/widgets.js" //////////////////////////////////////////////////////////////////////////////// export class Page extends ZenPage @@ -36,59 +36,54 @@ export class Page extends ZenPage all_stats[provider] = await new Fetcher().resource("stats", provider).json(); })); + this._http_panel = section.tag().classify("card").classify("stats-tile").classify("stats-http-panel"); + this._http_panel.inner().addEventListener("click", () => { window.location = "?page=metrics"; }); + this._http_panel.tag().classify("http-title").text("HTTP"); + const req_section = this._http_panel.tag().classify("http-section"); + req_section.tag().classify("http-section-label").text("Requests"); + this._http_req_metrics = req_section.tag().classify("tile-metrics"); + const ws_section = this._http_panel.tag().classify("http-section"); + ws_section.tag().classify("http-section-label").text("Websockets"); + this._http_ws_metrics = ws_section.tag().classify("tile-metrics"); this._stats_grid = section.tag().classify("grid").classify("stats-tiles"); this._safe_lookup = safe_lookup; this._render_stats(all_stats); // project list - var project_table = null; if (available.has("/prj/")) { var section = this.add_section("Cooked Projects"); - section.tag().classify("dropall").text("drop-all").on_click(() => this.drop_all("projects")); - var columns = [ "name", "project_dir", "engine_dir", "actions", ]; - project_table = section.add_widget(Table, columns); - - var projects = await new Fetcher().resource("/prj/list").json(); - projects.sort((a, b) => (b.LastAccessTime || 0) - (a.LastAccessTime || 0)); - projects = projects.slice(0, 25); - projects.sort((a, b) => a.Id.localeCompare(b.Id)); - - for (const project of projects) - { - var row = project_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); - - row.attr("zs_name", project.Id); - } + this._project_table = section.add_widget(Table, columns); + + this._project_pager = new Pager(section, 25, () => this._render_projects_page(), + Pager.make_search_fn(() => this._projects_data, p => p.Id)); + const drop_link = document.createElement("span"); + drop_link.className = "dropall zen_action"; + drop_link.style.position = "static"; + drop_link.textContent = "drop-all"; + drop_link.addEventListener("click", () => this.drop_all("projects")); + this._project_pager.prepend(drop_link); + + const prj_loading = Pager.loading(section); + this._projects_data = await new Fetcher().resource("/prj/list").json(); + this._projects_data.sort((a, b) => a.Id.localeCompare(b.Id)); + this._project_pager.set_total(this._projects_data.length); + this._render_projects_page(); + prj_loading.remove(); } // cache - var cache_table = null; if (available.has("/z$/")) { var section = this.add_section("Cache"); - section.tag().classify("dropall").text("drop-all").on_click(() => this.drop_all("z$")); - var columns = [ "namespace", "dir", @@ -98,31 +93,30 @@ export class Page extends ZenPage "size mem", "actions", ]; - var zcache_info = await new Fetcher().resource("/z$/").json(); - cache_table = section.add_widget(Table, columns, Table.Flag_FitLeft|Table.Flag_PackRight); - for (const namespace of zcache_info["Namespaces"] || []) - { - new Fetcher().resource(`/z$/${namespace}/`).json().then((data) => { - const row = cache_table.add_row( - "", - data["Configuration"]["RootDir"], - data["Buckets"].length, - data["EntryCount"], - Friendly.bytes(data["StorageSize"].DiskSize), - Friendly.bytes(data["StorageSize"].MemorySize) - ); - var cell = row.get_cell(0); - cell.tag().text(namespace).on_click(() => this.view_zcache(namespace)); - row.get_cell(1).tag().text(namespace); - - cell = row.get_cell(-1); - const action_tb = new Toolbar(cell, true); - action_tb.left().add("view").on_click(() => this.view_zcache(namespace)); - action_tb.left().add("drop").on_click(() => this.drop_zcache(namespace)); - - row.attr("zs_name", namespace); - }); - } + this._cache_table = section.add_widget(Table, columns, Table.Flag_FitLeft|Table.Flag_PackRight); + + this._cache_pager = new Pager(section, 25, () => this._render_cache_page(), + Pager.make_search_fn(() => this._cache_data, item => item.namespace)); + const cache_drop_link = document.createElement("span"); + cache_drop_link.className = "dropall zen_action"; + cache_drop_link.style.position = "static"; + cache_drop_link.textContent = "drop-all"; + cache_drop_link.addEventListener("click", () => this.drop_all("z$")); + this._cache_pager.prepend(cache_drop_link); + + const cache_loading = Pager.loading(section); + const zcache_info = await new Fetcher().resource("/z$/").json(); + const namespaces = zcache_info["Namespaces"] || []; + const results = await Promise.allSettled( + namespaces.map(ns => new Fetcher().resource(`/z$/${ns}/`).json().then(data => ({ namespace: ns, data }))) + ); + this._cache_data = results + .filter(r => r.status === "fulfilled") + .map(r => r.value) + .sort((a, b) => a.namespace.localeCompare(b.namespace)); + this._cache_pager.set_total(this._cache_data.length); + this._render_cache_page(); + cache_loading.remove(); } // version @@ -131,57 +125,54 @@ export class Page extends ZenPage version.param("detailed", "true"); version.text().then((data) => ver_tag.text(data)); - this._project_table = project_table; - this._cache_table = cache_table; - // WebSocket for live stats updates this.connect_stats_ws((all_stats) => this._render_stats(all_stats)); } _render_stats(all_stats) { + all_stats = this._merge_last_stats(all_stats); const grid = this._stats_grid; const safe_lookup = this._safe_lookup; - // Clear existing tiles + // Clear and repopulate service tiles grid grid.inner().innerHTML = ""; - // HTTP tile — aggregate request stats across all providers - { - const tile = grid.tag().classify("card").classify("stats-tile"); - tile.tag().classify("card-title").text("HTTP"); - const columns = tile.tag().classify("tile-columns"); + // HTTP panel — update metrics containers built once in main() + const left = this._http_req_metrics; + left.inner().innerHTML = ""; - // Left column: request stats - const left = columns.tag().classify("tile-metrics"); - - let total_requests = 0; - let total_rate = 0; - for (const p in all_stats) - { - total_requests += (safe_lookup(all_stats[p], "requests.count") || 0); - total_rate += (safe_lookup(all_stats[p], "requests.rate_1") || 0); - } - - this._add_tile_metric(left, Friendly.sep(total_requests), "total requests", true); - if (total_rate > 0) - this._add_tile_metric(left, Friendly.sep(total_rate, 1) + "/s", "req/sec (1m)"); + let total_requests = 0; + let total_rate = 0; + for (const p in all_stats) + { + total_requests += (safe_lookup(all_stats[p], "requests.count") || 0); + total_rate += (safe_lookup(all_stats[p], "requests.rate_1") || 0); + } - // Right column: websocket stats - const ws = all_stats["http"] ? (all_stats["http"]["websockets"] || {}) : {}; - const right = columns.tag().classify("tile-metrics"); + this._add_tile_metric(left, Friendly.sep(total_requests), "total requests", true); + if (total_rate > 0) + { + this._add_tile_metric(left, Friendly.sep(total_rate, 1) + "/s", "req/sec (1m)"); + } - this._add_tile_metric(right, Friendly.sep(ws.active_connections || 0), "ws connections", true); - const ws_frames = (ws.frames_received || 0) + (ws.frames_sent || 0); - if (ws_frames > 0) - this._add_tile_metric(right, Friendly.sep(ws_frames), "ws frames"); - const ws_bytes = (ws.bytes_received || 0) + (ws.bytes_sent || 0); - if (ws_bytes > 0) - this._add_tile_metric(right, Friendly.bytes(ws_bytes), "ws traffic"); + const right = this._http_ws_metrics; + right.inner().innerHTML = ""; - tile.on_click(() => { window.location = "?page=metrics"; }); + const ws = all_stats["http"] ? (all_stats["http"]["websockets"] || {}) : {}; + this._add_tile_metric(right, Friendly.sep(ws.active_connections || 0), "ws connections", true); + const ws_frames = (ws.frames_received || 0) + (ws.frames_sent || 0); + if (ws_frames > 0) + { + this._add_tile_metric(right, Friendly.sep(ws_frames), "ws frames"); + } + const ws_bytes = (ws.bytes_received || 0) + (ws.bytes_sent || 0); + if (ws_bytes > 0) + { + this._add_tile_metric(right, Friendly.bytes(ws_bytes), "ws traffic"); } + // Cache tile (z$) if (all_stats["z$"]) { @@ -198,7 +189,7 @@ export class Page extends ZenPage this._add_tile_metric(body, safe_lookup(s, "cache.size.disk", Friendly.bytes) || "-", "disk"); this._add_tile_metric(body, safe_lookup(s, "cache.size.memory", Friendly.bytes) || "-", "memory"); - tile.on_click(() => { window.location = "?page=stat&provider=z$"; }); + tile.inner().addEventListener("click", () => { window.location = "?page=stat&provider=z$"; }); } // Project Store tile (prj) @@ -210,9 +201,9 @@ export class Page extends ZenPage const body = tile.tag().classify("tile-metrics"); this._add_tile_metric(body, safe_lookup(s, "requests.count", Friendly.sep) || "-", "requests", true); - this._add_tile_metric(body, safe_lookup(s, "store.size.disk", Friendly.bytes) || "-", "disk"); + this._add_tile_metric(body, safe_lookup(s, "project_count", Friendly.sep) || "-", "projects"); - tile.on_click(() => { window.location = "?page=stat&provider=prj"; }); + tile.inner().addEventListener("click", () => { window.location = "?page=stat&provider=prj"; }); } // Build Store tile (builds) @@ -226,7 +217,7 @@ export class Page extends ZenPage this._add_tile_metric(body, safe_lookup(s, "requests.count", Friendly.sep) || "-", "requests", true); this._add_tile_metric(body, safe_lookup(s, "store.size.disk", Friendly.bytes) || "-", "disk"); - tile.on_click(() => { window.location = "?page=stat&provider=builds"; }); + tile.inner().addEventListener("click", () => { window.location = "?page=builds"; }); } // Proxy tile @@ -250,7 +241,37 @@ export class Page extends ZenPage this._add_tile_metric(body, Friendly.sep(mappings.length), "mappings"); this._add_tile_metric(body, Friendly.bytes(totalBytes), "traffic"); - tile.on_click(() => { window.location = "?page=proxy"; }); + tile.inner().addEventListener("click", () => { window.location = "?page=proxy"; }); + } + + // Hub tile + if (all_stats["hub"]) + { + const s = all_stats["hub"]; + const tile = grid.tag().classify("card").classify("stats-tile"); + tile.tag().classify("card-title").text("Hub"); + const body = tile.tag().classify("tile-metrics"); + + const current = safe_lookup(s, "currentInstanceCount") || 0; + const limit = safe_lookup(s, "instanceLimit") || safe_lookup(s, "maxInstanceCount") || 0; + this._add_tile_metric(body, `${current} / ${limit}`, "instances", true); + this._add_tile_metric(body, safe_lookup(s, "requests.count", Friendly.sep) || "-", "requests"); + + tile.inner().addEventListener("click", () => { window.location = "?page=stat&provider=hub"; }); + } + + // Object Store tile (obj) + if (all_stats["obj"]) + { + const s = all_stats["obj"]; + const tile = grid.tag().classify("card").classify("stats-tile"); + tile.tag().classify("card-title").text("Object Store"); + const body = tile.tag().classify("tile-metrics"); + + this._add_tile_metric(body, safe_lookup(s, "requests.count", Friendly.sep) || "-", "requests", true); + this._add_tile_metric(body, safe_lookup(s, "total_bytes_served", Friendly.bytes) || "-", "bytes served"); + + tile.inner().addEventListener("click", () => { window.location = "?page=stat&provider=obj"; }); } // Workspace tile (ws) @@ -262,9 +283,9 @@ export class Page extends ZenPage const body = tile.tag().classify("tile-metrics"); this._add_tile_metric(body, safe_lookup(s, "requests.count", Friendly.sep) || "-", "requests", true); - this._add_tile_metric(body, safe_lookup(s, "workspaces.filescount", Friendly.sep) || "-", "files"); + this._add_tile_metric(body, safe_lookup(s, "workspaces", Friendly.sep) || "-", "workspaces"); - tile.on_click(() => { window.location = "?page=stat&provider=ws"; }); + tile.inner().addEventListener("click", () => { window.location = "?page=stat&provider=ws"; }); } } @@ -279,6 +300,60 @@ export class Page extends ZenPage m.tag().classify("metric-label").text(label); } + _render_projects_page() + { + const { start, end } = this._project_pager.page_range(); + this._project_table.clear(start); + for (let i = start; i < end; i++) + { + const project = this._projects_data[i]; + const row = this._project_table.add_row( + "", + project.ProjectRootDir, + project.EngineRootDir, + ); + + const cell = row.get_cell(0); + cell.tag().text(project.Id).on_click((x) => this.view_project(x), project.Id); + + const action_cell = row.get_cell(-1); + const action_tb = new Toolbar(action_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); + + row.attr("zs_name", project.Id); + } + } + + _render_cache_page() + { + const { start, end } = this._cache_pager.page_range(); + this._cache_table.clear(start); + for (let i = start; i < end; i++) + { + const item = this._cache_data[i]; + const data = item.data; + const row = this._cache_table.add_row( + "", + data["Configuration"]["RootDir"], + data["Buckets"].length, + data["EntryCount"], + Friendly.bytes(data["StorageSize"].DiskSize), + Friendly.bytes(data["StorageSize"].MemorySize) + ); + + const cell = row.get_cell(0); + cell.tag().text(item.namespace).on_click(() => this.view_zcache(item.namespace)); + + const action_cell = row.get_cell(-1); + const action_tb = new Toolbar(action_cell, true); + action_tb.left().add("view").on_click(() => this.view_zcache(item.namespace)); + action_tb.left().add("drop").on_click(() => this.drop_zcache(item.namespace)); + + row.attr("zs_name", item.namespace); + } + } + view_stat(provider) { window.location = "?page=stat&provider=" + provider; @@ -324,20 +399,18 @@ export class Page extends ZenPage async drop_all_projects() { - for (const row of this._project_table) + for (const project of this._projects_data || []) { - const project_id = row.attr("zs_name"); - await new Fetcher().resource("prj", project_id).delete(); + await new Fetcher().resource("prj", project.Id).delete(); } this.reload(); } async drop_all_zcache() { - for (const row of this._cache_table) + for (const item of this._cache_data || []) { - const namespace = row.attr("zs_name"); - await new Fetcher().resource("z$", namespace).delete(); + await new Fetcher().resource("z$", item.namespace).delete(); } this.reload(); } |