From 72fc9cb755c1055a0963d0d13c042a11d54d9403 Mon Sep 17 00:00:00 2001 From: ZGaetano Date: Sat, 23 May 2026 10:48:06 -0400 Subject: [PATCH] feat(home): restore launcher home page; move current home to Dashboard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The original first-version home page (big-button launcher with the Dragonflight wordmark) is back at /. The Frame.io-style metrics + recent-activity layout we've been treating as "home" is now the Dashboard, reachable from the sidebar and from the launcher's "Open dashboard" button. - Renames existing Home → Dashboard (all the cards, sparklines, live feed, job-queue, cluster mini-list are unchanged). - New Home component: hero with the dragon-coiled-D logo (existing img/dragon-logo.png), wordmark "DRAGONFLIGHT", a tag line, and 5 big tiles (Library, Recorders, Editor, Jobs, Settings) plus a smaller Dashboard tile. Live cluster + recorder status pip at the bottom mirrors what's in the topbar. - The launcher pulls /metrics/home so the tile counts ("34 assets", "0 live", "0 running") reflect reality. --- services/web-ui/public/screens-home.jsx | 162 +++++++++++++++++++++++- 1 file changed, 160 insertions(+), 2 deletions(-) diff --git a/services/web-ui/public/screens-home.jsx b/services/web-ui/public/screens-home.jsx index 03de81e..badc885 100644 --- a/services/web-ui/public/screens-home.jsx +++ b/services/web-ui/public/screens-home.jsx @@ -1,6 +1,164 @@ // screens-home.jsx +// +// Two routes share this file: +// +// • Home — the launcher. Big-button entry into each section of the MAM. +// This is what you see when you log in / hit /. Resembles the original +// first-version landing page. +// +// • Dashboard — the operations view (recent activity, job queue, cluster, +// live recorders). Reachable from the sidebar or from the Home launcher. +// This is the React component that used to be `Home`. function Home({ navigate }) { + // Pull live counts so the tile subtitles ("34 assets", "0 live", "3 running") + // reflect what's actually in the DB right now, not a stale boot-time cache. + const [cards, setCards] = React.useState({}); + React.useEffect(() => { + let cancelled = false; + const load = () => { + window.ZAMPP_API.fetch('/metrics/home?hours=1') + .then(d => { if (!cancelled) setCards(d?.cards || {}); }) + .catch(() => {}); + }; + load(); + const t = setInterval(load, 30_000); + return () => { cancelled = true; clearInterval(t); }; + }, []); + + const { ASSETS = [], RECORDERS = [], JOBS = [], NODES = [] } = window.ZAMPP_DATA || {}; + const assetsTotal = cards.assets?.total ?? ASSETS.length; + const liveCount = cards.recorders?.live ?? RECORDERS.filter(r => r.status === 'recording').length; + const totalRecs = cards.recorders?.total ?? RECORDERS.length; + const runningJobs = cards.jobs?.running ?? JOBS.filter(j => j.status === 'running' || j.status === 'queued').length; + const failedJobs = cards.jobs?.failed_total ?? JOBS.filter(j => j.status === 'failed').length; + const nodesOnline = cards.cluster?.online ?? NODES.filter(n => n.status === 'online' || n.online === true).length; + const nodesTotal = cards.cluster?.total ?? NODES.length; + + const tiles = [ + { + id: 'library', + label: 'Library', + icon: 'library', + tone: 'accent', + sub: assetsTotal > 0 ? assetsTotal.toLocaleString() + ' assets' : 'No assets yet', + desc: 'Browse projects, bins, and assets. Hover-scrub previews.', + }, + { + id: 'recorders', + label: 'Recorders', + icon: 'record', + tone: 'live', + sub: liveCount > 0 + ? liveCount + ' live · ' + totalRecs + ' configured' + : totalRecs + ' configured', + desc: 'SDI · SRT · RTMP ingest. Start, stop, schedule.', + }, + { + id: 'editor', + label: 'Editor', + icon: 'editor', + tone: 'purple', + sub: 'Beta', + desc: 'Timeline editor with cross-clip preview and render queue.', + }, + { + id: 'jobs', + label: 'Jobs', + icon: 'jobs', + tone: failedJobs > 0 ? 'warn' : 'success', + sub: runningJobs > 0 + ? runningJobs + ' running' + (failedJobs > 0 ? ' · ' + failedJobs + ' failed' : '') + : (failedJobs > 0 ? failedJobs + ' failed' : 'All clear'), + desc: 'Proxy + thumbnail queue. Retry failed jobs.', + }, + { + id: 'settings', + label: 'Settings', + icon: 'settings', + tone: 'neutral', + sub: 'S3 · Encoder · Growing files', + desc: 'Storage, proxy encoder, capture SDK, growing-file mode.', + }, + ]; + + const clusterHealthy = !nodesTotal || nodesOnline >= nodesTotal; + + return ( +
+
+
+ Dragonflight +

DRAGONFLIGHT

+

+ Self-hosted broadcast media-asset management +

+
+ +
+ {tiles.map(t => ( + + ))} + + +
+ +
+ + + {clusterHealthy ? 'cluster healthy' : 'cluster degraded'} + · {nodesOnline}/{nodesTotal || 0} node{nodesTotal === 1 ? '' : 's'} + + {liveCount > 0 && ( + + + {liveCount} recorder{liveCount === 1 ? '' : 's'} live + + )} +
+
+
+ ); +} + +function Dashboard({ navigate }) { const { RECORDERS, JOBS, ASSETS, NODES } = window.ZAMPP_DATA; // Live (current-state) data from the boot-time data load @@ -38,13 +196,12 @@ function Home({ navigate }) { // Sum the most recent hour of each bucketed series for the delta line so // the "+N this hour" hint always reflects the latest bucket. - const lastBucket = (series) => (Array.isArray(series) && series.length ? series[series.length - 1].v : 0); const sumWindow = (series) => (Array.isArray(series) ? series.reduce((a, p) => a + p.v, 0) : 0); return (
-

Dragonflight

+

Dashboard

{liveCount > 0 ? liveCount + ' live · ' : ''} {runningCount > 0 ? runningCount + ' job' + (runningCount > 1 ? 's running' : ' running') + ' · ' : ''} @@ -204,3 +361,4 @@ function MiniJobRow({ job }) { } window.Home = Home; +window.Dashboard = Dashboard;