CxWebApp/static/js/panes/about.js
CxAI Agent 75153b7fe9
Some checks are pending
build-and-push / image (push) Waiting to run
feat(cxwebapp): comprehensive pane enhancements
- 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.
2026-05-17 09:36:19 -05:00

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/&lt;service&gt;/*</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'); }
});
},
});