Some checks are pending
build-and-push / image (push) Waiting to run
- Implemented demand pane for managing trend-driven design jobs. - Created diffusion pane for generating images via Stable Diffusion. - Added inbox pane for sweeping and routing artifacts through the CxAI inbox classifier. - Developed items pane for CRUD operations against /api/items. - Introduced lang pane for running language pipelines. - Established mac pane for macOS app distribution information. - Integrated slack pane for sending messages and displaying diagnostics. - Built system pane for process introspection and version information. - Launched tools pane for browsing and invoking MCP tools. - Set up websocket pane for connecting to the /ws/echo service.
116 lines
4.3 KiB
JavaScript
116 lines
4.3 KiB
JavaScript
// panes/dashboard.js — overview: multi-service health, KPIs, recent items, version.
|
|
import { registerPane } from '../app.js';
|
|
import { jget, services, probeService, formatUptime, escapeHtml } from '../lib/api.js';
|
|
|
|
const TPL = `
|
|
<div class="pane-head">
|
|
<div>
|
|
<div class="title">Control center</div>
|
|
<div class="sub">Live status across every CxWebApp service.</div>
|
|
</div>
|
|
<div class="grow"></div>
|
|
<button class="btn btn-secondary" id="db-refresh">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 12a9 9 0 0 1 15-6.7L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-15 6.7L3 16"/><path d="M3 21v-5h5"/></svg>
|
|
Refresh
|
|
</button>
|
|
</div>
|
|
|
|
<div class="grid cols-4 mb-3" id="db-kpis"></div>
|
|
|
|
<div class="grid cols-2 mb-3">
|
|
<div class="card">
|
|
<div class="card-title"><h2>Services</h2><span class="muted mono" id="db-svc-meta">—</span></div>
|
|
<div id="db-services"></div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-title"><h2>Recent items</h2><a href="#items" class="muted">view all →</a></div>
|
|
<div id="db-items"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-title"><h2>Activity</h2><span class="muted mono" id="db-log-meta">stream</span></div>
|
|
<pre class="console" id="db-log">[boot] dashboard ready\n</pre>
|
|
</div>
|
|
`;
|
|
|
|
function kpi(label, value, sub, accent = '★') {
|
|
return `
|
|
<div class="kpi">
|
|
<div class="label">${escapeHtml(label)}</div>
|
|
<div class="value">${escapeHtml(value)}</div>
|
|
<div class="sub">${escapeHtml(sub || '')}</div>
|
|
<div class="accent">${accent}</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function svcRow(svc, res) {
|
|
const cls = res.ok ? 'ok' : 'err';
|
|
const text = res.ok ? `up · ${res.ms}ms` : `down · ${escapeHtml(String(res.error))}`;
|
|
return `
|
|
<div class="row">
|
|
<span class="pill ${cls}"><span class="dot"></span>${escapeHtml(svc.label)}</span>
|
|
<div class="grow"></div>
|
|
<span class="mono muted">${text}</span>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function log(host, msg) {
|
|
const pre = host.querySelector('#db-log');
|
|
if (!pre) return;
|
|
const ts = new Date().toISOString().slice(11, 19);
|
|
pre.textContent = `[${ts}] ${msg}\n` + pre.textContent;
|
|
if (pre.textContent.length > 8000) pre.textContent = pre.textContent.slice(0, 8000);
|
|
}
|
|
|
|
async function refresh(host) {
|
|
// probe all services in parallel
|
|
const results = await Promise.all(services.map(s => probeService(s).then(r => ({ s, r }))));
|
|
host.querySelector('#db-services').innerHTML = results.map(({ s, r }) => svcRow(s, r)).join('');
|
|
const up = results.filter(x => x.r.ok).length;
|
|
host.querySelector('#db-svc-meta').textContent = `${up}/${results.length} up`;
|
|
|
|
// KPIs from /api/system + /api/items
|
|
let sys = {}, ver = {}, itemsResp = { items: [] };
|
|
try { [sys, ver, itemsResp] = await Promise.all([
|
|
jget('/api/system'), jget('/api/version'), jget('/api/items'),
|
|
]); } catch (_) {}
|
|
|
|
host.querySelector('#db-kpis').innerHTML = [
|
|
kpi('Health', up === results.length ? 'All up' : `${up}/${results.length}`,
|
|
up === results.length ? 'all systems normal' : 'some services degraded', '✓'),
|
|
kpi('Uptime', formatUptime(sys.uptime_seconds), 'since process boot', '⏱'),
|
|
kpi('Version', ver.version || '—', `${(ver.git_sha || '').slice(0, 7) || '—'}`, '⌥'),
|
|
kpi('Items', String(itemsResp.count ?? (itemsResp.items || []).length), 'stored in API', '☷'),
|
|
].join('');
|
|
|
|
const items = (itemsResp.items || []).slice(0, 6);
|
|
host.querySelector('#db-items').innerHTML = items.length
|
|
? items.map(it => `
|
|
<div class="row">
|
|
<span class="mono muted">#${it.id}</span>
|
|
<div class="grow">
|
|
<div class="ttl">${escapeHtml(it.name)}</div>
|
|
<div class="desc">${escapeHtml(it.description || '')}</div>
|
|
</div>
|
|
</div>
|
|
`).join('')
|
|
: '<div class="muted" style="padding:12px">No items yet.</div>';
|
|
|
|
log(host, `refreshed · ${up}/${results.length} services up`);
|
|
}
|
|
|
|
registerPane('dashboard', {
|
|
label: 'Dashboard',
|
|
init(host) {
|
|
host.innerHTML = TPL;
|
|
host.querySelector('#db-refresh').addEventListener('click', () => refresh(host));
|
|
refresh(host);
|
|
// auto-refresh every 15s while pane is visible
|
|
setInterval(() => { if (host.classList.contains('active')) refresh(host); }, 15_000);
|
|
},
|
|
refresh,
|
|
});
|