aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDan Engelbrecht <[email protected]>2026-04-07 17:09:03 +0200
committerGitHub Enterprise <[email protected]>2026-04-07 17:09:03 +0200
commit2e8fe4ef83c5ca188b840c7141fa02d6167f2d0b (patch)
treebf7da032993231355879845ecf0dd4682f485c14 /src
parentincremental dehydrate (#921) (diff)
downloadzen-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.js159
-rw-r--r--src/zenserver/frontend/html/util/widgets.js77
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)