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.
167 lines
8.6 KiB
JavaScript
167 lines
8.6 KiB
JavaScript
// panes/about.js — runtime metadata, shortcut grid, public links, credits.
|
|
import { registerPane } from '../app.js';
|
|
import { MCP_BASE, jget, escapeHtml } from '../lib/api.js';
|
|
import { fmtJSON, copyToClipboard, ok } from '../lib/ui.js';
|
|
|
|
const SHORTCUTS = [
|
|
['⌘K / Ctrl-K', 'Open command palette'],
|
|
['Esc', 'Close palette / modal'],
|
|
['↑ ↓ Enter', 'Navigate palette results'],
|
|
['1 … 9', 'Quick-jump to a sidebar tab (when palette open)'],
|
|
];
|
|
|
|
const PLATFORM = [
|
|
{ tab: 'dashboard', label: 'Dashboard', sub: 'KPIs, service health, activity' },
|
|
{ tab: 'agent', label: 'Agent', sub: 'MCP status + live events + search' },
|
|
{ tab: 'inbox', label: 'Inbox', sub: 'Sweep & route artifacts' },
|
|
{ tab: 'tools', label: 'Tools', sub: 'Browse and invoke MCP tools' },
|
|
{ tab: 'items', label: 'Items', sub: 'CRUD against /api/items' },
|
|
];
|
|
const SERVICES = [
|
|
{ tab: 'diffusion', label: 'Diffusion', sub: 'Stable Diffusion sidecar' },
|
|
{ tab: 'demand', label: 'Demand', sub: 'Trend-driven design jobs' },
|
|
{ tab: 'lang', label: 'Lang', sub: 'Language pipelines' },
|
|
{ tab: 'slack', label: 'Slack', sub: 'Slack sidecar' },
|
|
{ tab: 'mac', label: 'macOS', sub: 'Native app distribution' },
|
|
{ tab: 'files', label: 'Files', sub: 'Platform Studio mirror' },
|
|
];
|
|
const DEVELOP = [
|
|
{ tab: 'api', label: 'API Explorer', sub: 'Hit any backend route' },
|
|
{ tab: 'websocket', label: 'WebSocket', sub: 'Live frame console' },
|
|
{ tab: 'system', label: 'System', sub: 'Runtime & host facts' },
|
|
];
|
|
|
|
const PUBLIC_LINKS = [
|
|
{ url: 'https://webapp.cxllm.io/', label: 'webapp.cxllm.io', sub: 'production' },
|
|
{ url: 'https://auth.cxllm.io/if/user/#/library', label: 'App library', sub: 'all CxAI tiles' },
|
|
{ url: 'https://api.cxllm.io/', label: 'api.cxllm.io', sub: 'public API gateway' },
|
|
{ url: 'https://mcp.cxllm.io/', label: 'mcp.cxllm.io', sub: 'MCP control plane' },
|
|
{ url: 'https://files.cxllm.io/', label: 'files.cxllm.io', sub: 'Files Platform Studio' },
|
|
{ url: 'https://code.cxllm.io/', label: 'code.cxllm.io', sub: 'code-server' },
|
|
{ url: 'https://monitor.cxllm.io/', label: 'monitor.cxllm.io', sub: 'Grafana' },
|
|
{ url: 'https://registry.cxllm.io/v2/_catalog', label: 'Container registry', sub: 'cxai/* images' },
|
|
];
|
|
|
|
function linkCardInternal(t) {
|
|
return `<a class="link-card" href="#${t.tab}">
|
|
<div class="lc-ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg></div>
|
|
<div class="grow"><div class="lc-title">${escapeHtml(t.label)}</div><div class="lc-sub">${escapeHtml(t.sub)}</div></div>
|
|
</a>`;
|
|
}
|
|
function linkCardExternal(t) {
|
|
return `<a class="link-card" href="${escapeHtml(t.url)}" target="_blank" rel="noopener">
|
|
<div class="lc-ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 3h7v7"/><path d="M10 14L21 3"/><path d="M5 5v14a2 2 0 0 0 2 2h14"/></svg></div>
|
|
<div class="grow"><div class="lc-title">${escapeHtml(t.label)}</div><div class="lc-sub">${escapeHtml(t.sub)}</div></div>
|
|
</a>`;
|
|
}
|
|
|
|
const TPL = `
|
|
<div class="pane-head">
|
|
<div><div class="title">About</div><div class="sub">CxWebApp control center — single-page UI over the Crow backend and all sidecars.</div></div>
|
|
<div class="grow"></div>
|
|
<button class="btn btn-secondary" id="ab-share">Share build info</button>
|
|
</div>
|
|
|
|
<div class="grid cols-3 mb-3">
|
|
<div class="card">
|
|
<div class="card-title"><h2>Build</h2></div>
|
|
<dl class="dl" id="ab-build"></dl>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-title"><h2>Host</h2></div>
|
|
<dl class="dl" id="ab-host"></dl>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-title"><h2>Connectivity</h2></div>
|
|
<dl class="dl">
|
|
<dt>MCP base</dt><dd>${escapeHtml(MCP_BASE)}</dd>
|
|
<dt>Origin</dt><dd>${escapeHtml(location.origin)}</dd>
|
|
<dt>Theme</dt><dd id="ab-theme">—</dd>
|
|
<dt>User agent</dt><dd class="truncate" title="${escapeHtml(navigator.userAgent)}">${escapeHtml(navigator.userAgent)}</dd>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid cols-2 mb-3">
|
|
<div class="card">
|
|
<div class="card-title"><h2>Platform panes</h2></div>
|
|
<div class="grid cols-2 gap-2">${PLATFORM.map(linkCardInternal).join('')}</div>
|
|
<div class="divider"></div>
|
|
<div class="card-title"><h2 style="font-size:14px">Services</h2></div>
|
|
<div class="grid cols-2 gap-2">${SERVICES.map(linkCardInternal).join('')}</div>
|
|
<div class="divider"></div>
|
|
<div class="card-title"><h2 style="font-size:14px">Develop</h2></div>
|
|
<div class="grid cols-2 gap-2">${DEVELOP.map(linkCardInternal).join('')}</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-title"><h2>Public links</h2></div>
|
|
<div class="grid cols-2 gap-2">${PUBLIC_LINKS.map(linkCardExternal).join('')}</div>
|
|
|
|
<div class="divider"></div>
|
|
<div class="card-title"><h2 style="font-size:14px">Keyboard shortcuts</h2></div>
|
|
<table class="table">
|
|
<tbody>${SHORTCUTS.map(([k, d]) => `<tr><td style="width:140px"><kbd>${escapeHtml(k)}</kbd></td><td class="muted small">${escapeHtml(d)}</td></tr>`).join('')}</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid cols-2">
|
|
<div class="card">
|
|
<div class="card-title"><h2>Architecture</h2></div>
|
|
<p class="muted small">Crow (HTTP/WebSocket) + cpp-httplib (reverse proxy) compiled into a single C++ binary. Static UI is plain ES modules (no build step). All sidecars are reached through <code>/api/<service>/*</code> reverse-proxy routes registered from <code>src/routes/proxy.cpp</code>.</p>
|
|
<ul class="muted small" style="line-height:1.9">
|
|
<li><code>src/routes/api.cpp</code> — items CRUD</li>
|
|
<li><code>src/routes/system.cpp</code> — /api/version, /api/system, /ws/echo</li>
|
|
<li><code>src/routes/proxy.cpp</code> — diffusion / demand / lang / slack / files</li>
|
|
<li><code>src/routes/mac.cpp</code> — /api/mac/* native app distribution</li>
|
|
<li><code>src/routes/pages.cpp</code> — static SPA serving</li>
|
|
</ul>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-title"><h2>Configuration env-vars</h2></div>
|
|
<table class="table">
|
|
<thead><tr><th>variable</th><th>purpose</th></tr></thead>
|
|
<tbody>
|
|
<tr><td><code>CXWEBAPP_VERSION</code></td><td>Version string returned by /api/version.</td></tr>
|
|
<tr><td><code>CXWEBAPP_GIT_SHA</code></td><td>Build SHA.</td></tr>
|
|
<tr><td><code>CXWEBAPP_BUILD_TIME</code></td><td>RFC3339 build timestamp.</td></tr>
|
|
<tr><td><code>CXWEBAPP_STATIC_DIR</code></td><td>Override static asset directory.</td></tr>
|
|
<tr><td><code>CXAI_DIFFUSION_UPSTREAM</code></td><td>Diffusion sidecar URL.</td></tr>
|
|
<tr><td><code>CXAI_DEMAND_UPSTREAM</code></td><td>Demand sidecar URL.</td></tr>
|
|
<tr><td><code>CXAI_LANG_UPSTREAM</code></td><td>Lang sidecar URL.</td></tr>
|
|
<tr><td><code>CXAI_SLACK_UPSTREAM</code></td><td>Slack sidecar URL.</td></tr>
|
|
<tr><td><code>CXAI_FILES_UPSTREAM</code></td><td>Files PostgREST URL.</td></tr>
|
|
<tr><td><code>FILES_ANON_KEY</code></td><td>PostgREST anon key (injected by proxy).</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
function row(k, v) { return `<dt>${escapeHtml(k)}</dt><dd>${escapeHtml(v ?? '—')}</dd>`; }
|
|
|
|
registerPane('about', {
|
|
label: 'About',
|
|
async init(host) {
|
|
host.innerHTML = TPL;
|
|
host.querySelector('#ab-theme').textContent = document.documentElement.classList.contains('dark') ? 'dark' : 'light';
|
|
try {
|
|
const [v, s] = await Promise.all([jget('/api/version'), jget('/api/system')]);
|
|
host.querySelector('#ab-build').innerHTML =
|
|
row('Name', v.name) + row('Version', v.version) + row('Git SHA', (v.git_sha || '').slice(0, 12)) + row('Build time', v.build_time);
|
|
host.querySelector('#ab-host').innerHTML =
|
|
row('Hostname', s.hostname) + row('OS', `${s.sysname || ''} ${s.release || ''}`.trim()) +
|
|
row('Arch', s.machine) + row('PID', s.pid) + row('CPUs', s.cpu_count);
|
|
} catch (e) {
|
|
host.querySelector('#ab-build').innerHTML = `<dt>error</dt><dd>${escapeHtml(e.message)}</dd>`;
|
|
}
|
|
host.querySelector('#ab-share').addEventListener('click', async () => {
|
|
try {
|
|
const [v, s] = await Promise.all([jget('/api/version'), jget('/api/system')]);
|
|
copyToClipboard(fmtJSON({ ...v, host: s.hostname, sys: `${s.sysname} ${s.release}` }), 'build info copied');
|
|
} catch (e) { copyToClipboard(location.href, 'url copied'); }
|
|
});
|
|
},
|
|
});
|