// panes/files.js — Surfaces the files.cxllm.io platform data (Providers /
// Edge Functions / Demand / Watcher / Health / File-manager) directly inside
// CxWebApp. All requests go through the C++ backend at /api/files/* which
// injects the PostgREST anon key so the browser never sees the secret.
import { jget, jpost, escapeHtml } from '../lib/api.js';
import { ok, err } from '../lib/ui.js';
import { registerPane } from '../app.js';
const TABS = [
{ id: 'providers', label: 'Providers', endpoint: '/mcp_providers?select=*&order=id.asc' },
{ id: 'functions', label: 'Edge Functions', endpoint: '/platform_functions?select=*&order=slug.asc' },
{ id: 'health', label: 'Health', endpoint: '/platform_health?select=*&order=captured_at.desc&limit=20' },
{ id: 'demand', label: 'Demand Runs', endpoint: '/demand_runs?select=*&order=received_at.desc&limit=20' },
{ id: 'watcher', label: 'Watcher Events', endpoint: '/watcher_events?select=*&order=occurred_at.desc&limit=50' },
{ id: 'categories', label: 'File Manager', endpoint: '/file_manager_categories?select=*&order=category.asc' },
];
let activeTab = 'providers';
const rest = (path) => jget(`/api/files${path}`);
const restPost = (path, body) => jpost(`/api/files${path}`, body, { headers: { Prefer: 'return=minimal' } });
const TPL = `
Files · Platform
Live providers, edge functions, demand, watcher and health from files.cxllm.io.
Open Studio ↗
${TABS.map(t => ``).join('')}
`;
function tag(kind, txt) {
return `${escapeHtml(txt ?? '')}`;
}
function statusPill(s) {
if (!s) return tag('muted', '—');
const v = String(s).toLowerCase();
if (['ok', 'ready', 'healthy', 'success'].includes(v)) return tag('ok', s);
if (['degraded', 'warn', 'pending', 'running'].includes(v)) return tag('warn', s);
if (['down', 'failed', 'error'].includes(v)) return tag('err', s);
return tag('info', s);
}
function ts(s) { return escapeHtml((s || '').replace('T', ' ').slice(0, 19)); }
// ---- renderers ----
function renderProviders(rows) {
if (!rows.length) return ``;
return `${rows.map(p => `
${escapeHtml(p.name || p.id)}
${statusPill(p.status)}
${escapeHtml(p.description || '')}
id: ${escapeHtml(p.id || '')}
model: ${escapeHtml(p.default_model || '-')}
source: ${escapeHtml(p.source_file || '')}
base: ${escapeHtml(p.base_url || '-')}
env: ${(p.requires_env || []).join(', ') || '(none)'}
`).join('')}
`;
}
function renderFunctions(rows) {
if (!rows.length) return `No edge functions registered.
`;
return `${rows.map(f => `
${escapeHtml(f.title || f.slug)}
${tag('info', `${f.method || 'GET'} · ${f.runtime || ''}`)}
${escapeHtml(f.description || '')}
route: ${escapeHtml(f.route || '')}
source: ${escapeHtml(f.source_file || '')}
env: ${(f.requires_env || []).join(', ') || '(none)'}
`).join('')}
`;
}
function renderHealth(rows) {
return `
Probes Postgres / MCP / HF / Gitea from the browser, then inserts a row.
Recent snapshots
${rows.length} rows
${rows.length === 0 ? '
No snapshots yet.
' : `
| captured | overall | ms | pg | mcp | hf | gitea |
${rows.map(r => `
| ${ts(r.captured_at)} |
${statusPill(r.overall_status)} |
${r.total_latency_ms ?? ''} |
${r.supabase?.ok ? '✓' : '·'} |
${r.mcp?.ok ? '✓' : '·'} |
${r.huggingface?.ok ? '✓' : '·'} |
${r.gitea?.ok ? '✓' : '·'} |
`).join('')}
`}
`;
}
function renderDemand(rows) {
if (!rows.length) return `No demand runs received. Configure CXCLOUD_MCP_URL in cxai-demand.
`;
return `
| received | date | mode | designs | ok | rej | quality | platforms |
${rows.map(r => `
| ${ts(r.received_at)} |
${escapeHtml(r.run_date || '')} |
${r.dry_run ? tag('warn', 'dry') : tag('ok', 'live')} |
${r.total_designs || 0} |
${r.accepted_designs || 0} |
${r.rejected_designs || 0} |
${r.average_quality_score != null ? Number(r.average_quality_score).toFixed(2) : '—'} |
${escapeHtml(Object.keys(r.by_platform || {}).join(', ') || '—')} |
`).join('')}
`;
}
function renderWatcher(rows) {
if (!rows.length) return ``;
return `
| when | kind | path | tool | status | ms |
${rows.map(e => `
| ${escapeHtml((e.occurred_at || '').replace('T', ' ').slice(11, 19))} |
${tag(e.event_kind === 'created' ? 'ok' : e.event_kind === 'removed' ? 'err' : 'info', e.event_kind)} |
${escapeHtml(e.path || '')} |
${escapeHtml(e.action_tool || '-')} |
${statusPill(e.status)} |
${e.latency_ms ?? ''} |
`).join('')}
`;
}
function renderCategories(rows) {
if (!rows.length) return `No file-manager categories.
`;
return `${rows.map(c => `
${escapeHtml(c.category || '')}
${(c.extensions || []).length} ext
${(c.extensions || []).map(x => `.${escapeHtml(x)}`).join(' ')}
`).join('')}
`;
}
const RENDERERS = {
providers: renderProviders,
functions: renderFunctions,
health: renderHealth,
demand: renderDemand,
watcher: renderWatcher,
categories: renderCategories,
};
async function loadTab(host, tabId) {
activeTab = tabId;
const tab = TABS.find(t => t.id === tabId);
const body = host.querySelector('#files-body');
body.innerHTML = `Loading ${escapeHtml(tab.label)}…
`;
host.querySelectorAll('#files-tabs .tab').forEach(b => b.classList.toggle('active', b.dataset.tab === tabId));
try {
const data = await rest(tab.endpoint);
const rows = Array.isArray(data) ? data : [];
body.innerHTML = RENDERERS[tabId](rows);
bindActions(host);
} catch (e) {
body.innerHTML = `
Failed to load ${escapeHtml(tab.label)}
${escapeHtml(e.message || String(e))}
Confirm CXAI_FILES_UPSTREAM and FILES_ANON_KEY are set on the backend.
`;
}
}
function bindActions(host) {
const capture = host.querySelector('#files-capture-health');
if (!capture) return;
capture.onclick = async () => {
capture.disabled = true; capture.textContent = 'Capturing…';
const t0 = performance.now();
const probe = async (name, fn) => {
const s = performance.now();
try { const r = await fn(); return { name, ok: !!r, latency_ms: Math.round(performance.now() - s) }; }
catch { return { name, ok: false, latency_ms: Math.round(performance.now() - s) }; }
};
const probes = await Promise.all([
probe('supabase', () => rest('/mcp_providers?select=id&limit=1')),
probe('mcp', () => fetch('/api/health').then(r => r.ok)),
probe('huggingface', () => fetch('https://router.huggingface.co/', { mode: 'no-cors' })),
probe('gitea', () => fetch('https://cxai-studio.com/git/api/v1/version').then(r => r.ok)),
]);
const okCount = probes.filter(p => p.ok).length;
const overall = okCount === 4 ? 'ok' : okCount >= 2 ? 'degraded' : 'down';
try {
await restPost('/platform_health', {
overall_status: overall,
total_latency_ms: Math.round(performance.now() - t0),
supabase: probes[0], mcp: probes[1], huggingface: probes[2], gitea: probes[3],
raw: { source: 'cxwebapp', probes },
});
ok('Snapshot captured', 'Files');
} catch (e) {
err(`Capture failed: ${e.message}`, 'Files');
}
await loadTab(host, 'health');
};
}
registerPane('files', {
label: 'Files · Platform',
async init(host) {
host.innerHTML = TPL;
host.querySelectorAll('#files-tabs .tab').forEach(b => {
b.addEventListener('click', () => loadTab(host, b.dataset.tab));
});
host.querySelector('#files-refresh').addEventListener('click', () => loadTab(host, activeTab));
await loadTab(host, activeTab);
},
async refresh(host) {
await loadTab(host, activeTab);
},
});