Files
Velocity-OS/webos/src/control-room/ControlRoom.tsx

299 lines
12 KiB
TypeScript

import { motion, AnimatePresence } from 'framer-motion';
import { useLocation, useNavigate } from 'react-router-dom';
import styles from './ControlRoom.module.css';
/**
* ControlRoom
* Admin-only surface. Accessible at /control-room.
* Protected by AdminGuard at router level AND FastAPI RBAC middleware.
*
* Panels (left sidebar → content area):
* System Health — service status, GPU utilization, queue depths
* Oracle Admin — schema catalog, canvas management, MCP tools
* Comms Config — WAHA/Evolution provider, webhook setup
* Users & Roles — broker roster, role assignment, audit log
* Model Hydration — s5cmd sync status, model inventory
* Meta Integration — OAuth, ad account, Lookalike sync
*
* Design: deliberately more "operator" than "broker" — still glassmorphic
* but denser information, monospace for technical values.
*/
type Panel =
| 'system'
| 'oracle-admin'
| 'comms-config'
| 'users'
| 'models'
| 'meta';
const PANELS: { id: Panel; label: string; icon: string }[] = [
{ id: 'system', label: 'System Health', icon: '⬡' },
{ id: 'oracle-admin', label: 'Oracle Admin', icon: '◈' },
{ id: 'comms-config', label: 'Comms Config', icon: '⟐' },
{ id: 'users', label: 'Users & Roles', icon: '⊛' },
{ id: 'models', label: 'Model Hydration', icon: '⊗' },
{ id: 'meta', label: 'Meta Integration', icon: '⊕' },
];
export default function ControlRoom() {
const navigate = useNavigate();
const location = useLocation();
const routePanel = location.pathname.split('/').filter(Boolean)[1] as Panel | undefined;
const active: Panel = PANELS.some((panel) => panel.id === routePanel) ? routePanel! : 'system';
return (
<div className={styles.root}>
{/* Header */}
<div className={styles.header}>
<div className={styles.headerLeft}>
<span className={styles.headerIcon}></span>
<div>
<h1 className={styles.title}>Control Room</h1>
<p className={styles.subtitle}>System Administration · Admin Access Only</p>
</div>
</div>
<button className="btn-ghost" onClick={() => navigate('/command')}>
Back to Command
</button>
</div>
<div className={styles.body}>
{/* Left sidebar */}
<nav className={styles.sidebar}>
{PANELS.map(({ id, label, icon }) => (
<button
key={id}
className={`${styles.sideItem} ${active === id ? styles.sideActive : ''}`}
onClick={() => navigate(`/control-room/${id}`)}
aria-current={active === id ? 'page' : undefined}
>
<span className={styles.sideIcon}>{icon}</span>
<span>{label}</span>
{active === id && (
<motion.div
layoutId="cr-indicator"
className={styles.indicator}
transition={{ type: 'spring', stiffness: 400, damping: 35 }}
/>
)}
</button>
))}
</nav>
{/* Panel content */}
<main className={styles.content}>
<AnimatePresence mode="wait">
<motion.div
key={active}
initial={{ opacity: 0, x: 12 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -8 }}
transition={{ duration: 0.2, ease: [0.4, 0, 0.2, 1] }}
className={styles.panelWrap}
>
{active === 'system' && <SystemHealthPanel />}
{active === 'oracle-admin' && <OracleAdminPanel />}
{active === 'comms-config' && <CommsConfigPanel />}
{active === 'users' && <UsersPanel />}
{active === 'models' && <ModelHydrationPanel />}
{active === 'meta' && <MetaIntegrationPanel />}
</motion.div>
</AnimatePresence>
</main>
</div>
</div>
);
}
// ── System Health ─────────────────────────────────────────────
function SystemHealthPanel() {
const services = [
{ name: 'core-api', status: 'healthy', latency: '42ms', replicas: '2/2' },
{ name: 'webos', status: 'healthy', latency: '—', replicas: '2/2' },
{ name: 'media-engine', status: 'healthy', latency: '—', replicas: '1/1' },
{ name: 'postgres', status: 'healthy', latency: '3ms', replicas: '1/1' },
{ name: 'redis', status: 'healthy', latency: '0.4ms', replicas: '1/1' },
];
return (
<div className={styles.panel}>
<h2 className={styles.panelTitle}>System Health</h2>
<div className={styles.serviceGrid}>
{services.map(svc => (
<div key={svc.name} className={`${styles.svcCard} glass-card`}>
<div className={styles.svcTop}>
<span
className={styles.statusDot}
style={{ background: svc.status === 'healthy' ? 'var(--color-green)' : 'var(--color-red)' }}
/>
<code className={styles.svcName}>{svc.name}</code>
</div>
<div className={styles.svcMeta}>
<span>Replicas: <code>{svc.replicas}</code></span>
{svc.latency !== '—' && <span>Latency: <code>{svc.latency}</code></span>}
</div>
</div>
))}
</div>
{/* GPU MIG status */}
<div className={`${styles.gpuCard} glass-card`}>
<h3 className={styles.subTitle}>GPU · RTX 6000 Blackwell · MIG Active</h3>
<div className={styles.migSlices}>
<div className={styles.migSlice}>
<span className={styles.migLabel}>Slice 0 · 48GB</span>
<code className={styles.migService}>SGLang Qwen3.6 35B</code>
<span className={styles.migStatus} style={{ color: 'var(--color-green)' }}> Loaded</span>
</div>
<div className={styles.migSlice}>
<span className={styles.migLabel}>Slice 1 · 48GB</span>
<code className={styles.migService}>ComfyUI Wan 2.2 + Qwen-Image</code>
<span className={styles.migStatus} style={{ color: 'var(--color-green)' }}> Loaded</span>
</div>
</div>
</div>
</div>
);
}
// ── Oracle Admin ──────────────────────────────────────────────
function OracleAdminPanel() {
return (
<div className={styles.panel}>
<h2 className={styles.panelTitle}>Oracle Administration</h2>
<p className={styles.panelSubtitle}>
Canvas management, schema catalog, and MCP tools.
These controls are not visible to broker-role users.
</p>
<div className={styles.adminSection}>
<h3 className={styles.subTitle}>Active Canvas Sessions</h3>
<p className={styles.muted}>Query canvas history, fork/merge, and revision management available here.</p>
<button className="btn-ghost">Open Canvas Manager</button>
</div>
<div className={styles.adminSection}>
<h3 className={styles.subTitle}>Schema Catalog</h3>
<button className="btn-ghost">Browse Schema </button>
<button className="btn-ghost">Run Data Health Check </button>
</div>
<div className={styles.adminSection}>
<h3 className={styles.subTitle}>MCP Tools</h3>
<button className="btn-ghost">View Registered Tools </button>
</div>
</div>
);
}
// ── Comms Config ──────────────────────────────────────────────
function CommsConfigPanel() {
return (
<div className={styles.panel}>
<h2 className={styles.panelTitle}>Comms Configuration</h2>
<p className={styles.panelSubtitle}>
WhatsApp provider setup. Never visible to sales brokers.
</p>
<div className={styles.formGroup}>
<label className={styles.formLabel}>Provider</label>
<select className={styles.formSelect}>
<option value="waha">WAHA</option>
<option value="evolution">Evolution API</option>
</select>
</div>
<div className={styles.formGroup}>
<label className={styles.formLabel}>API Base URL</label>
<input type="text" className={styles.formInput} placeholder="https://waha.internal:3000" />
</div>
<div className={styles.formGroup}>
<label className={styles.formLabel}>API Key</label>
<input type="password" className={styles.formInput} placeholder="••••••••••••••••" />
</div>
<button className="btn-primary">Save Configuration</button>
</div>
);
}
// ── Users & Roles ─────────────────────────────────────────────
function UsersPanel() {
const roles = ['ADMIN', 'SALES_DIRECTOR', 'SALES_BROKER'];
return (
<div className={styles.panel}>
<h2 className={styles.panelTitle}>Users & Roles</h2>
<div className={styles.userToolbar}>
<button className="btn-primary">+ Invite User</button>
</div>
<table className={styles.table}>
<thead>
<tr>
<th>Name</th><th>Email</th><th>Role</th><th>Last Active</th><th></th>
</tr>
</thead>
<tbody>
<tr>
<td>Sagnik</td>
<td>sagnik@desineuron.in</td>
<td><code>ADMIN</code></td>
<td>Now</td>
<td><button className={styles.tableAction}>Edit</button></td>
</tr>
</tbody>
</table>
</div>
);
}
// ── Model Hydration ───────────────────────────────────────────
function ModelHydrationPanel() {
const models = [
{ name: 'Wan 2.2', size: '15 GB', status: 'synced', path: '/opt/models/comfy/wan2.2' },
{ name: 'Qwen-Image 2512', size: '20 GB', status: 'synced', path: '/opt/models/comfy/qwen-image-2512' },
{ name: 'Qwen3.6 35B A3B', size: '70 GB', status: 'synced', path: '/opt/models/llm/qwen3.6-35b-a3b' },
];
return (
<div className={styles.panel}>
<h2 className={styles.panelTitle}>Model Hydration</h2>
<p className={styles.panelSubtitle}>NVMe-backed model store. Re-sync from S3 via s5cmd.</p>
{models.map(m => (
<div key={m.name} className={`${styles.modelRow} glass-card`}>
<div>
<span className={styles.modelName}>{m.name}</span>
<code className={styles.modelPath}>{m.path}</code>
</div>
<div className={styles.modelRight}>
<span className={styles.modelSize}>{m.size}</span>
<span style={{ color: 'var(--color-green)', fontSize: 'var(--text-xs)' }}>
{m.status}
</span>
<button className="btn-ghost">Re-sync</button>
</div>
</div>
))}
<button className="btn-ghost" style={{ marginTop: 'var(--space-4)' }}>
Run Full Hydration
</button>
</div>
);
}
// ── Meta Integration ──────────────────────────────────────────
function MetaIntegrationPanel() {
return (
<div className={styles.panel}>
<h2 className={styles.panelTitle}>Meta Business Integration</h2>
<p className={styles.panelSubtitle}>
OAuth, Ad Account, and Lookalike Audience configuration.
Never visible to broker-role users.
</p>
<div className={styles.metaStatus}>
<span className={styles.statusDot} style={{ background: 'var(--color-amber)' }} />
<span>OAuth not connected</span>
<button className="btn-primary">Connect Meta Business</button>
</div>
<div className={styles.adminSection}>
<h3 className={styles.subTitle}>Ad Account</h3>
<input className={styles.formInput} placeholder="Meta Ad Account ID" />
</div>
<button className="btn-ghost">Save</button>
</div>
);
}