Some checks are pending
build-and-push / image (push) Waiting to run
- All 14 panes rewritten with rich controls: KPIs, sparklines, meter gauges, presets, history sidebars, search/filter, bulk actions, schema-driven forms, copy buttons, auto-refresh. - Backend /api/system enriched with cpu_count, rss_bytes, mem_total, mem_used_pct, loadavg[], user/system CPU times, max_rss_kb. - CSS additions: .meter/.gauge, .seg, .chip(s), .spark, .slider, .dl, .link-card, details.card-d plus utility classes. - ui.js helpers: copyToClipboard, withCopy/bindCopyButtons, sparkline, meterRow, fmtBytes/fmtNum, historyStore, inputFromSchema, collectSchemaForm. - Files pane added; iCloud duplicate '*2' files removed.
148 lines
5.2 KiB
JavaScript
148 lines
5.2 KiB
JavaScript
// app.js — boot, router, theme, command palette wiring.
|
|
// Each pane self-registers via registerPane(); only the active pane is initialized.
|
|
|
|
import { jget } from './lib/api.js';
|
|
import { initPalette, registerCommand, setHealthPill, setBrandVer, setFooterBuild, ok, err } from './lib/ui.js';
|
|
|
|
// Pane registry: id -> { label, init(host), refresh?(host), inited:false }
|
|
const PANES = new Map();
|
|
const MAIN = document.getElementById('main');
|
|
|
|
export function registerPane(id, def) {
|
|
PANES.set(id, { ...def, inited: false });
|
|
registerCommand({
|
|
id: `go:${id}`,
|
|
label: `Go to ${def.label}`,
|
|
group: 'Navigate',
|
|
keywords: `tab ${id} ${def.label}`,
|
|
run: () => activate(id),
|
|
});
|
|
}
|
|
|
|
// dynamic imports so each pane is its own ES module
|
|
const PANE_LOADERS = {
|
|
dashboard: () => import('./panes/dashboard.js'),
|
|
agent: () => import('./panes/agent.js'),
|
|
inbox: () => import('./panes/inbox.js'),
|
|
tools: () => import('./panes/tools.js'),
|
|
items: () => import('./panes/items.js'),
|
|
diffusion: () => import('./panes/diffusion.js'),
|
|
demand: () => import('./panes/demand.js'),
|
|
lang: () => import('./panes/lang.js'),
|
|
slack: () => import('./panes/slack.js'),
|
|
mac: () => import('./panes/mac.js'),
|
|
files: () => import('./panes/files.js'),
|
|
api: () => import('./panes/api.js'),
|
|
websocket: () => import('./panes/websocket.js'),
|
|
system: () => import('./panes/system.js'),
|
|
about: () => import('./panes/about.js'),
|
|
};
|
|
|
|
const NAV_LABELS = {
|
|
dashboard: 'Dashboard', agent: 'Agent', inbox: 'Inbox', tools: 'Tools',
|
|
items: 'Items', diffusion: 'Diffusion', demand: 'Demand', lang: 'Lang',
|
|
slack: 'Slack', mac: 'macOS', files: 'Files · Platform', api: 'API Explorer', websocket: 'WebSocket',
|
|
system: 'System', about: 'About',
|
|
};
|
|
|
|
async function activate(id) {
|
|
if (!PANE_LOADERS[id]) id = 'dashboard';
|
|
|
|
// mark nav
|
|
document.querySelectorAll('.nav-item').forEach(el => {
|
|
el.classList.toggle('active', el.dataset.tab === id);
|
|
});
|
|
// mark pane
|
|
document.querySelectorAll('.pane').forEach(el => {
|
|
el.classList.toggle('active', el.dataset.pane === id);
|
|
});
|
|
const crumb = document.getElementById('crumb-tab');
|
|
if (crumb) crumb.textContent = NAV_LABELS[id] || id;
|
|
document.title = `${NAV_LABELS[id] || id} · CxWebApp`;
|
|
|
|
if (location.hash !== `#${id}`) history.replaceState(null, '', `#${id}`);
|
|
|
|
// lazy-load pane module
|
|
if (!PANES.has(id)) {
|
|
try { await PANE_LOADERS[id](); }
|
|
catch (e) {
|
|
console.error(`pane ${id} failed to load`, e);
|
|
const host = document.querySelector(`[data-pane="${id}"]`);
|
|
if (host) host.innerHTML = `<div class="card"><div class="muted">Failed to load ${id}: ${e.message}</div></div>`;
|
|
return;
|
|
}
|
|
}
|
|
const pane = PANES.get(id);
|
|
if (!pane) return;
|
|
const host = document.querySelector(`[data-pane="${id}"]`);
|
|
if (!host) return;
|
|
if (!pane.inited) {
|
|
try { await pane.init(host); pane.inited = true; }
|
|
catch (e) { err(`init ${id}: ${e.message}`); console.error(e); }
|
|
} else if (pane.refresh) {
|
|
try { await pane.refresh(host); } catch (e) { console.warn(`refresh ${id}`, e); }
|
|
}
|
|
}
|
|
|
|
// ---------------- theme ----------------
|
|
const themeBtn = document.getElementById('theme-toggle');
|
|
themeBtn?.addEventListener('click', () => {
|
|
const dark = document.documentElement.classList.toggle('dark');
|
|
localStorage.setItem('cx_theme', dark ? 'dark' : 'light');
|
|
});
|
|
|
|
// ---------------- nav ----------------
|
|
document.querySelectorAll('.nav-item').forEach(el => {
|
|
el.addEventListener('click', () => activate(el.dataset.tab));
|
|
});
|
|
|
|
window.addEventListener('hashchange', () => {
|
|
const id = (location.hash || '').replace('#', '');
|
|
if (id) activate(id);
|
|
});
|
|
|
|
// ---------------- header health + version ----------------
|
|
async function refreshHealth() {
|
|
try {
|
|
const j = await jget('/api/health');
|
|
setHealthPill('ok', 'healthy');
|
|
return j;
|
|
} catch (e) {
|
|
setHealthPill('err', 'down');
|
|
}
|
|
}
|
|
async function refreshVersion() {
|
|
try {
|
|
const j = await jget('/api/version');
|
|
setBrandVer(`${j.name || 'cxwebapp'} ${j.version || ''}`.trim());
|
|
setFooterBuild(`${(j.git_sha || '').slice(0, 7) || '—'} · ${j.build_time || ''}`.trim());
|
|
} catch {
|
|
setBrandVer('—');
|
|
}
|
|
}
|
|
|
|
// ---------------- palette commands (global) ----------------
|
|
function registerGlobalCommands() {
|
|
registerCommand({ id: 'theme:toggle', label: 'Toggle theme', group: 'View', run: () => themeBtn?.click() });
|
|
registerCommand({ id: 'reload', label: 'Reload page', group: 'View', run: () => location.reload() });
|
|
registerCommand({ id: 'open:health', label: 'Open /api/health', group: 'API', run: () => window.open('/api/health', '_blank') });
|
|
registerCommand({ id: 'open:version', label: 'Open /api/version', group: 'API', run: () => window.open('/api/version', '_blank') });
|
|
registerCommand({ id: 'open:system', label: 'Open /api/system', group: 'API', run: () => window.open('/api/system', '_blank') });
|
|
}
|
|
|
|
// ---------------- boot ----------------
|
|
(async function boot() {
|
|
initPalette();
|
|
registerGlobalCommands();
|
|
|
|
const initial = (location.hash || '#dashboard').replace('#', '');
|
|
await activate(initial);
|
|
|
|
refreshHealth();
|
|
refreshVersion();
|
|
setInterval(refreshHealth, 10_000);
|
|
|
|
// greet once
|
|
ok('Control center ready', 'CxWebApp');
|
|
})();
|