Wire all integrations + production hardening: 15 recommendations
Integration & Wiring: - useStore/useAppState wired into App.tsx (replaces 8 useState calls) - React Router wired at app root (URL-based navigation) - SparklineChart/MetricCard/BarChart integrated into Admin + Ethics pages - useNotifications.handleWSEvent wired into WebSocket handler - Notification center dropdown in header with unread badge - Locale selector added to Settings page (6 languages) - Dashboard data fetching with 10s polling into MetricCards - File drag-and-drop support on chat area Production Hardening: - PostgresStateBackend with connection pooling (psycopg2) - App lifespan wires backend from FUSIONAGI_DB_BACKEND env (memory|sqlite|postgres) - Redis cache wired from FUSIONAGI_REDIS_URL env at startup - Multi-process uvicorn config for horizontal scaling Testing: - Playwright visual regression tests (12 stories x 2 viewports) - k6 load test script with ramp/spike/ramp-down stages - 7 new Python tests (postgres fallback, app wiring) 575 Python tests + 45 frontend tests = 620 total, 0 ruff errors. Co-Authored-By: Nakamoto, S <defi@defi-oracle.io>
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import { MetricCard, Sparkline, BarChart } from '../components/SparklineChart'
|
||||
import { t } from '../i18n'
|
||||
import type { SystemStatus, VoiceProfile } from '../types'
|
||||
|
||||
function StatusCard({ label, value, unit, statusClass }: {
|
||||
@@ -15,6 +17,13 @@ function StatusCard({ label, value, unit, statusClass }: {
|
||||
)
|
||||
}
|
||||
|
||||
interface StatusHistory {
|
||||
cpu: number[]
|
||||
memory: number[]
|
||||
tasks: number[]
|
||||
sessions: number[]
|
||||
}
|
||||
|
||||
export function AdminPage({ authHeaders }: { authHeaders: () => Record<string, string> }) {
|
||||
const [status, setStatus] = useState<SystemStatus | null>(null)
|
||||
const [voices, setVoices] = useState<VoiceProfile[]>([])
|
||||
@@ -23,11 +32,21 @@ export function AdminPage({ authHeaders }: { authHeaders: () => Record<string, s
|
||||
const [newVoiceName, setNewVoiceName] = useState('')
|
||||
const [newVoiceLang, setNewVoiceLang] = useState('en-US')
|
||||
const [tab, setTab] = useState<'overview' | 'voices' | 'agents' | 'governance'>('overview')
|
||||
const [history, setHistory] = useState<StatusHistory>({ cpu: [], memory: [], tasks: [], sessions: [] })
|
||||
|
||||
const fetchStatus = useCallback(async () => {
|
||||
try {
|
||||
const r = await fetch('/v1/admin/status', { headers: authHeaders() })
|
||||
if (r.ok) setStatus(await r.json())
|
||||
if (r.ok) {
|
||||
const data = await r.json()
|
||||
setStatus(data)
|
||||
setHistory((h) => ({
|
||||
cpu: [...h.cpu, data.cpu_usage_percent ?? 0].slice(-20),
|
||||
memory: [...h.memory, data.memory_usage_mb ?? 0].slice(-20),
|
||||
tasks: [...h.tasks, data.active_tasks ?? 0].slice(-20),
|
||||
sessions: [...h.sessions, data.active_sessions ?? 0].slice(-20),
|
||||
}))
|
||||
}
|
||||
} catch { /* offline */ }
|
||||
}, [authHeaders])
|
||||
|
||||
@@ -70,21 +89,24 @@ export function AdminPage({ authHeaders }: { authHeaders: () => Record<string, s
|
||||
|
||||
const statusClass = status?.status === 'healthy' ? 'healthy' : status?.status === 'degraded' ? 'degraded' : status?.status === 'offline' ? 'offline' : ''
|
||||
|
||||
if (loading) return <div className="page-loading" role="status" aria-live="polite">Loading admin dashboard...</div>
|
||||
const cpuTrend = history.cpu.length >= 2 ? (history.cpu[history.cpu.length - 1] > history.cpu[history.cpu.length - 2] ? 'up' : history.cpu[history.cpu.length - 1] < history.cpu[history.cpu.length - 2] ? 'down' : 'flat') as 'up' | 'down' | 'flat' : undefined
|
||||
const memTrend = history.memory.length >= 2 ? (history.memory[history.memory.length - 1] > history.memory[history.memory.length - 2] ? 'up' : 'down') as 'up' | 'down' : undefined
|
||||
|
||||
if (loading) return <div className="page-loading" role="status" aria-live="polite">{t('common.loading')}</div>
|
||||
|
||||
return (
|
||||
<div className="admin-page" role="main" aria-label="Admin Dashboard">
|
||||
<div className="admin-page" role="main" aria-label={t('admin.title')}>
|
||||
<div className="admin-tabs" role="tablist" aria-label="Admin sections">
|
||||
{(['overview', 'voices', 'agents', 'governance'] as const).map((t) => (
|
||||
{(['overview', 'voices', 'agents', 'governance'] as const).map((tb) => (
|
||||
<button
|
||||
key={t}
|
||||
className={tab === t ? 'active' : ''}
|
||||
onClick={() => setTab(t)}
|
||||
key={tb}
|
||||
className={tab === tb ? 'active' : ''}
|
||||
onClick={() => setTab(tb)}
|
||||
role="tab"
|
||||
aria-selected={tab === t}
|
||||
aria-controls={`panel-${t}`}
|
||||
aria-selected={tab === tb}
|
||||
aria-controls={`panel-${tb}`}
|
||||
>
|
||||
{t.charAt(0).toUpperCase() + t.slice(1)}
|
||||
{tb.charAt(0).toUpperCase() + tb.slice(1)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
@@ -93,22 +115,62 @@ export function AdminPage({ authHeaders }: { authHeaders: () => Record<string, s
|
||||
|
||||
{tab === 'overview' && (
|
||||
<div className="admin-section" role="tabpanel" id="panel-overview" aria-label="System Overview">
|
||||
<h2>System Overview</h2>
|
||||
<div className="status-grid" role="group" aria-label="System metrics">
|
||||
<h2>{t('admin.status')}</h2>
|
||||
<div className="metrics-grid">
|
||||
<MetricCard
|
||||
title="CPU Usage"
|
||||
value={status?.cpu_usage_percent ?? 0}
|
||||
unit="%"
|
||||
data={history.cpu}
|
||||
trend={cpuTrend}
|
||||
color="var(--color-warning, #ff9800)"
|
||||
/>
|
||||
<MetricCard
|
||||
title="Memory"
|
||||
value={status?.memory_usage_mb ?? 0}
|
||||
unit=" MB"
|
||||
data={history.memory}
|
||||
trend={memTrend}
|
||||
color="var(--accent)"
|
||||
/>
|
||||
<MetricCard
|
||||
title="Active Tasks"
|
||||
value={status?.active_tasks ?? 0}
|
||||
data={history.tasks}
|
||||
color="var(--color-success, #4caf50)"
|
||||
/>
|
||||
<MetricCard
|
||||
title="Sessions"
|
||||
value={status?.active_sessions ?? 0}
|
||||
data={history.sessions}
|
||||
color="var(--color-info, #2196f3)"
|
||||
/>
|
||||
</div>
|
||||
<div className="status-grid" role="group" aria-label="System metrics" style={{ marginTop: '1rem' }}>
|
||||
<StatusCard label="Status" value={status?.status ?? 'unknown'} statusClass={statusClass} />
|
||||
<StatusCard label="Uptime" value={status ? formatUptime(status.uptime_seconds) : 'N/A'} />
|
||||
<StatusCard label="Active Tasks" value={status?.active_tasks ?? 0} />
|
||||
<StatusCard label="Active Agents" value={status?.active_agents ?? 0} />
|
||||
<StatusCard label="Sessions" value={status?.active_sessions ?? 0} />
|
||||
<StatusCard label="Memory" value={status?.memory_usage_mb} unit=" MB" />
|
||||
<StatusCard label="CPU" value={status?.cpu_usage_percent} unit="%" />
|
||||
</div>
|
||||
{status && (
|
||||
<div style={{ marginTop: '1rem' }}>
|
||||
<h3>Agent Distribution</h3>
|
||||
<BarChart
|
||||
data={[
|
||||
{ label: 'Tasks', value: status.active_tasks ?? 0, color: 'var(--color-success, #4caf50)' },
|
||||
{ label: 'Agents', value: status.active_agents ?? 0, color: 'var(--accent)' },
|
||||
{ label: 'Sessions', value: status.active_sessions ?? 0, color: 'var(--color-info, #2196f3)' },
|
||||
]}
|
||||
width={300}
|
||||
height={80}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{tab === 'voices' && (
|
||||
<div className="admin-section" role="tabpanel" id="panel-voices" aria-label="Voice Library">
|
||||
<h2>Voice Library</h2>
|
||||
<h2>{t('admin.voices')}</h2>
|
||||
<div className="add-form" role="form" aria-label="Add voice">
|
||||
<label htmlFor="voice-name" className="sr-only">Voice name</label>
|
||||
<input id="voice-name" placeholder="Voice name" value={newVoiceName} onChange={(e) => setNewVoiceName(e.target.value)} />
|
||||
@@ -138,7 +200,7 @@ export function AdminPage({ authHeaders }: { authHeaders: () => Record<string, s
|
||||
|
||||
{tab === 'agents' && (
|
||||
<div className="admin-section" role="tabpanel" id="panel-agents" aria-label="Agent Configuration">
|
||||
<h2>Agent Configuration</h2>
|
||||
<h2>{t('admin.agents')}</h2>
|
||||
<div className="agent-grid" role="list" aria-label="Active agents">
|
||||
{['Planner', 'Reasoner', 'Executor', 'Critic', '12 Heads', 'Witness'].map((a) => (
|
||||
<div key={a} className="agent-card" role="listitem">
|
||||
@@ -152,7 +214,7 @@ export function AdminPage({ authHeaders }: { authHeaders: () => Record<string, s
|
||||
|
||||
{tab === 'governance' && (
|
||||
<div className="admin-section" role="tabpanel" id="panel-governance" aria-label="Governance Mode">
|
||||
<h2>Governance Mode</h2>
|
||||
<h2>{t('admin.governance')}</h2>
|
||||
<div className="governance-info">
|
||||
<div className="governance-mode" role="status" aria-label="Current governance mode: Advisory">
|
||||
<span className="mode-label">Current Mode:</span>
|
||||
|
||||
Reference in New Issue
Block a user