homepage: sectioned panels, WIP open/closed, current model facts, Tautulli card
Both pages now group their panels under section headings rendered by shared
renderSection()/renderPanel() components in app.js (SECTION_ORDER fixes each
page's order: user = AI, Web Apps, Media Library Apps; admin = Media Library
Apps, Observability, AI, Infrastructure). The WIP badge splits into WIP: OPEN
and WIP: CLOSED via the wip value ("open"/"closed"); the checker passes the
value through as wip-open/wip-closed. The frank-large card follows the
unsloth llama.cpp cutover, frank-medium follows the FP8 checkpoint, and the
/docs model guide matches. Tautulli (Plex statistics, 192.168.0.235:8181)
joins the admin page's Media Library Apps section with an open link.
This commit is contained in:
1 parent
32eafeb7c5
commit
384747a72f
9 files changed
+221
-54
No files matched your search
+59
-19
@@ -1,10 +1,26 @@
|
||||
// The homepage front end. It reads status.json and services.json once on load
|
||||
// (no auto-refresh, Q41), renders one TUI panel per service for this page's
|
||||
// category, offers the styling toggle (Q27), and shows the last master
|
||||
// deployment datetime (Q43). It never alters the user's cursor (Q28).
|
||||
// (no auto-refresh, Q41), renders the services for this page's category as
|
||||
// sectioned grids of TUI panels, offers the styling toggle (Q27), and shows
|
||||
// the last master deployment datetime (Q43). It never alters the user's
|
||||
// cursor (Q28).
|
||||
|
||||
const CATEGORY = window.location.pathname.indexOf('/admin') !== -1 ? 'admin' : 'user';
|
||||
|
||||
// Sections and panels are shared components: both pages render through the
|
||||
// same renderSection()/renderPanel() below. SECTION_ORDER fixes each page's
|
||||
// section order; a section with no services renders nothing.
|
||||
const SECTION_ORDER = {
|
||||
user: ['AI', 'Web Apps', 'Media Library Apps'],
|
||||
admin: ['Media Library Apps', 'Observability', 'AI', 'Infrastructure'],
|
||||
};
|
||||
|
||||
const BADGE_LABEL = {
|
||||
up: 'UP',
|
||||
down: 'DOWN',
|
||||
'wip-open': 'WIP: OPEN',
|
||||
'wip-closed': 'WIP: CLOSED',
|
||||
};
|
||||
|
||||
function load(url) {
|
||||
return fetch(url, { cache: 'no-store' }).then(function (r) { return r.json(); });
|
||||
}
|
||||
@@ -15,32 +31,56 @@ function esc(s) {
|
||||
});
|
||||
}
|
||||
|
||||
// Shared panel component: one TUI panel for a service in a given state.
|
||||
function renderPanel(svc, state) {
|
||||
const panel = document.createElement('div');
|
||||
panel.className = 'panel ' + state;
|
||||
const links = [];
|
||||
if (svc.showLink && svc.url) links.push('<a class="open" href="' + esc(svc.url) + '">open</a>');
|
||||
if (svc.docs) links.push('<a class="docs" href="' + esc(svc.docs) + '">docs</a>');
|
||||
panel.innerHTML =
|
||||
'<div class="panel-head">' +
|
||||
' <span class="badge ' + state + '">' + BADGE_LABEL[state] + '</span>' +
|
||||
' <span class="name">' + esc(svc.name) + '</span>' +
|
||||
'</div>' +
|
||||
'<div class="desc">' + esc(svc.description || '') + '</div>' +
|
||||
'<div class="links">' + links.join('') + '</div>';
|
||||
return panel;
|
||||
}
|
||||
|
||||
// Shared section component: a heading over a grid of panels.
|
||||
function renderSection(title, panels) {
|
||||
const section = document.createElement('section');
|
||||
section.className = 'section';
|
||||
const heading = document.createElement('h2');
|
||||
heading.className = 'section-title';
|
||||
heading.textContent = title;
|
||||
const grid = document.createElement('div');
|
||||
grid.className = 'grid';
|
||||
for (const panel of panels) grid.appendChild(panel);
|
||||
section.appendChild(heading);
|
||||
section.appendChild(grid);
|
||||
return section;
|
||||
}
|
||||
|
||||
function renderPanels(services, status) {
|
||||
const stateByName = {};
|
||||
for (const s of (status.services || [])) stateByName[s.name] = s.state;
|
||||
|
||||
const grid = document.getElementById('grid');
|
||||
grid.innerHTML = '';
|
||||
const root = document.getElementById('grid');
|
||||
root.innerHTML = '';
|
||||
let up = 0, down = 0, wip = 0;
|
||||
|
||||
const bySection = {};
|
||||
for (const svc of services) {
|
||||
if (svc.category !== CATEGORY) continue;
|
||||
const state = svc.wip ? 'wip' : (stateByName[svc.name] || 'down');
|
||||
const state = svc.wip ? 'wip-' + svc.wip : (stateByName[svc.name] || 'down');
|
||||
if (state === 'up') up++; else if (state === 'down') down++; else wip++;
|
||||
(bySection[svc.section] = bySection[svc.section] || []).push(renderPanel(svc, state));
|
||||
}
|
||||
|
||||
const panel = document.createElement('div');
|
||||
panel.className = 'panel ' + state;
|
||||
const links = [];
|
||||
if (svc.showLink && svc.url) links.push('<a class="open" href="' + esc(svc.url) + '">open</a>');
|
||||
if (svc.docs) links.push('<a class="docs" href="' + esc(svc.docs) + '">docs</a>');
|
||||
panel.innerHTML =
|
||||
'<div class="panel-head">' +
|
||||
' <span class="badge ' + state + '">' + state.toUpperCase() + '</span>' +
|
||||
' <span class="name">' + esc(svc.name) + '</span>' +
|
||||
'</div>' +
|
||||
'<div class="desc">' + esc(svc.description || '') + '</div>' +
|
||||
'<div class="links">' + links.join('') + '</div>';
|
||||
grid.appendChild(panel);
|
||||
for (const title of SECTION_ORDER[CATEGORY]) {
|
||||
if (bySection[title]) root.appendChild(renderSection(title, bySection[title]));
|
||||
}
|
||||
|
||||
document.getElementById('summary').textContent = 'UP ' + up + ' DOWN ' + down + ' WIP ' + wip;
|
||||
|
||||
Reference in new issue
Block a user