// 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 `
No providers configured.
`; 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.
' : ` ${rows.map(r => ` `).join('')}
capturedoverallmspgmcphfgitea
${ts(r.captured_at)} ${statusPill(r.overall_status)} ${r.total_latency_ms ?? ''} ${r.supabase?.ok ? '✓' : '·'} ${r.mcp?.ok ? '✓' : '·'} ${r.huggingface?.ok ? '✓' : '·'} ${r.gitea?.ok ? '✓' : '·'}
`}
`; } function renderDemand(rows) { if (!rows.length) return `
No demand runs received. Configure CXCLOUD_MCP_URL in cxai-demand.
`; return `
${rows.map(r => ` `).join('')}
receiveddatemodedesignsokrejqualityplatforms
${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(', ') || '—')}
`; } function renderWatcher(rows) { if (!rows.length) return `
No watcher events yet.
`; return `
${rows.map(e => ` `).join('')}
whenkindpathtoolstatusms
${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 ?? ''}
`; } 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); }, });