diff options
| author | Dan Engelbrecht <[email protected]> | 2026-04-07 17:09:03 +0200 |
|---|---|---|
| committer | GitHub Enterprise <[email protected]> | 2026-04-07 17:09:03 +0200 |
| commit | 2e8fe4ef83c5ca188b840c7141fa02d6167f2d0b (patch) | |
| tree | bf7da032993231355879845ecf0dd4682f485c14 /src | |
| parent | incremental dehydrate (#921) (diff) | |
| download | zen-2e8fe4ef83c5ca188b840c7141fa02d6167f2d0b.tar.xz zen-2e8fe4ef83c5ca188b840c7141fa02d6167f2d0b.zip | |
add pagination of cooked projects and caches on dashboard front page (#922)
Diffstat (limited to 'src')
| -rw-r--r-- | src/zenserver/frontend/html/pages/start.js | 159 | ||||
| -rw-r--r-- | src/zenserver/frontend/html/util/widgets.js | 77 |
2 files changed, 171 insertions, 65 deletions
diff --git a/src/zenserver/frontend/html/pages/start.js b/src/zenserver/frontend/html/pages/start.js index e5b4d14f1..14ec4bd4a 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 @@ -50,54 +50,37 @@ export class Page extends ZenPage 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()); + 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); + + 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(); } // 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", @@ -107,30 +90,27 @@ 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)); - - 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()); + 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 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(); } // version @@ -139,9 +119,6 @@ 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)); } @@ -316,6 +293,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; @@ -361,20 +392,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(); } diff --git a/src/zenserver/frontend/html/util/widgets.js b/src/zenserver/frontend/html/util/widgets.js index 17bd2fde7..33d6755ac 100644 --- a/src/zenserver/frontend/html/util/widgets.js +++ b/src/zenserver/frontend/html/util/widgets.js @@ -402,6 +402,83 @@ export class ProgressBar extends Widget //////////////////////////////////////////////////////////////////////////////// +export class Pager +{ + constructor(section, page_size, on_change) + { + this._page = 0; + this._page_size = page_size; + this._total = 0; + this._on_change = on_change; + + const pager = section.tag().classify("module-pager").inner(); + this._btn_prev = document.createElement("button"); + this._btn_prev.className = "module-pager-btn"; + this._btn_prev.textContent = "\u2190 Prev"; + this._btn_prev.addEventListener("click", () => this._go_page(this._page - 1)); + this._label = document.createElement("span"); + this._label.className = "module-pager-label"; + this._btn_next = document.createElement("button"); + this._btn_next.className = "module-pager-btn"; + this._btn_next.textContent = "Next \u2192"; + this._btn_next.addEventListener("click", () => this._go_page(this._page + 1)); + pager.appendChild(this._btn_prev); + pager.appendChild(this._label); + pager.appendChild(this._btn_next); + this._pager = pager; + + this._update_ui(); + } + + prepend(element) + { + this._pager.insertBefore(element, this._btn_prev); + } + + set_total(n) + { + this._total = n; + const max_page = Math.max(0, Math.ceil(n / this._page_size) - 1); + if (this._page > max_page) + { + this._page = max_page; + } + this._update_ui(); + } + + page_range() + { + const start = this._page * this._page_size; + const end = Math.min(start + this._page_size, this._total); + return { start, end }; + } + + _go_page(n) + { + const max = Math.max(0, Math.ceil(this._total / this._page_size) - 1); + this._page = Math.max(0, Math.min(n, max)); + this._update_ui(); + this._on_change(); + } + + _update_ui() + { + const total = this._total; + const page_count = Math.max(1, Math.ceil(total / this._page_size)); + const start = this._page * this._page_size + 1; + const end = Math.min(start + this._page_size - 1, total); + + this._btn_prev.disabled = this._page === 0; + this._btn_next.disabled = this._page >= page_count - 1; + this._label.textContent = total === 0 + ? "No items" + : `${start}\u2013${end} of ${total}`; + } +} + + + +//////////////////////////////////////////////////////////////////////////////// export class WidgetHost { constructor(parent, depth=1) |