From e8588f62e4b804231a90ae9e92c5dd130ec7af29 Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Tue, 12 May 2026 18:43:34 +0200 Subject: [PATCH] docs(metablog-ui): Design spec for version dashboard (ref #103) Sub-project D of #49. Extends existing /metablogizer/ list view with 3 columns (version, streamlit_app, last_updated) + filter box + sort + 60s polling. New site.html drill-in page that surfaces every site.json field plus 3 external links (live site, Gitea repo, Streamlit app). Tag history defers to Gitea's UI (browser auth) because the repos are private and browser-side proxying through a stored token is heavier than the MVP warrants. Co-Authored-By: Claude Opus 4.7 (1M context) --- ...05-12-metablog-version-dashboard-design.md | 204 ++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 docs/superpowers/specs/2026-05-12-metablog-version-dashboard-design.md diff --git a/docs/superpowers/specs/2026-05-12-metablog-version-dashboard-design.md b/docs/superpowers/specs/2026-05-12-metablog-version-dashboard-design.md new file mode 100644 index 00000000..1564a4da --- /dev/null +++ b/docs/superpowers/specs/2026-05-12-metablog-version-dashboard-design.md @@ -0,0 +1,204 @@ +# MetaBlogizer Version Dashboard — Design + +**Date:** 2026-05-12 +**Author:** Gandalf (CyberMind), with Claude +**Status:** Draft for approval +**Issue:** [#103](https://github.com/CyberMind-FR/secubox-deb/issues/103) (sub-project D of [#49](https://github.com/CyberMind-FR/secubox-deb/issues/49)) +**Depends on:** [#102](https://github.com/CyberMind-FR/secubox-deb/pull/102) (merged — sub-C: schema + enriched API) + +## Context + +`packages/secubox-metablogizer/www/metablogizer/index.html` already exists (533 lines, served live at `https://admin.gk2.secubox.in/metablogizer/`). It has a tabbed layout (Sites / Access / Actions), stat cards, a sites table that consumes `/api/v1/metablogizer/sites`, and the CRT P31 phosphor theme. + +What's missing for D: + +- Columns for `version`, `streamlit_app`, `last_updated` (sub-C now exposes these on the API). +- A filter box (165 sites is too many to scroll). +- Sortable columns. +- A 60-second auto-refresh (today: manual button only). +- A per-site drill-in page. + +## Goal + +Extend the existing list view with version-aware columns, filter, sort, and polling — plus add a per-site drill-in page that surfaces every site.json field and links out to Gitea / live site / Streamlit app. + +## Non-goals + +- Editing site.json from the UI (no inline edit, no form posts). +- Triggering deploys / rollbacks (sub-E concern). +- Per-tag diff view inside the UI (operator follows the external Gitea link). +- Fetching Gitea tag history from the browser (repos are private — needs server-side proxying, defer to a separate iteration). +- Bulk operations (multi-select, batch publish, etc.). + +## Decisions taken in brainstorming + +| Decision | Choice | Rationale | +|----------|--------|-----------| +| Scaffold or extend? | Extend the existing 533-line `index.html` | Avoid duplicating layout/theme/sidebar wiring; UI lives in `packages/secubox-metablogizer/www/metablogizer/` per the established Hub pattern | +| Drill-in route | Separate file `site.html?name=` (query-string param) | Deep-linkable; one URL maps to one site; no JS router needed | +| Tag history in drill-in | **External link to Gitea** (no inline fetch) | Repos are private; browser-side Gitea auth is brittle. Operator clicks → opens Gitea Releases tab in a new tab | +| Sort/filter scope | In-memory JS, no URL state | 165 items fit easily; URL state adds complexity without UX win | +| Refresh cadence | 60 s polling, paused when tab hidden | Cheap (one API call); `document.hidden` skip avoids waste | +| Streamlit link | Visible only when `streamlit_app != null` | Half the sites have one, half don't | + +## Architecture + +### Component 1 — Extended list view (`index.html`) + +The existing `loadSites()` function takes `d.sites` from the API and renders 5 columns. We add 3 columns and 3 toolbar features. + +**New table columns** (between `domain` and `published`): + +| Column | Source | Render | +|--------|--------|--------| +| Version | `s.version` (from `.deploy.json` or `git describe`) | `v1.0.0` — links to `https://gitea.gk2.secubox.in/gandalf/metablog-` (releases tab anchor) | +| Streamlit | `s.streamlit_app` | If non-null: 🎨 icon + link to `https://gitea.gk2.secubox.in/gandalf/streamlit-`. If null: empty cell | +| Updated | `s.last_updated` | Relative time ("2d ago") via small format helper; tooltip shows the full ISO 8601 | + +**Filter box** — placed above the table, inside the existing `
` that wraps the Sites tab: + +```html + +``` + +`applyFilter()` walks `.site-row` ``s and toggles `display: none` based on case-insensitive substring match. + +**Sortable headers** — each `` carries `data-sort=""` and a click handler: + +```html +Name +``` + +`sortBy(th)` toggles asc/desc, updates the `sort-ind` to ▲/▼, and re-renders the rows. Current sort state lives in two module-level vars: `currentSort = {field: 'name', dir: 'asc'}`. + +**60-second polling** — at the end of `refresh()`: + +```js +setInterval(() => { if (!document.hidden) refresh(); }, 60000); +``` + +Just once; subsequent calls don't stack timers. The function is wrapped in a guard so the timer isn't installed twice. + +### Component 2 — Drill-in page (`site.html`) + +New file at `packages/secubox-metablogizer/www/metablogizer/site.html`. Same CRT P31 phosphor theme via `` and inline `