Files
AIRegulation-DocAnalysis/frontend/src/pages/Status/StatusPage.tsx
T

184 lines
7.7 KiB
TypeScript
Raw Normal View History

import { useState, useEffect } from 'react';
import { Topbar } from '../../components/layout/Topbar';
2026-06-04 15:43:44 +08:00
import { Search, Upload, Download, RefreshCw } from 'lucide-react';
import { UploadModal } from '../Docs/UploadModal';
2026-06-04 15:43:44 +08:00
// Backend /api/v1/status/stats returns:
// { documents_total, documents_indexed, documents_failed, chunks_total }
interface Stats { documents_total: number; documents_indexed: number; documents_failed: number; chunks_total: number; }
const TASKS = [
{ name: 'EU AI Act — Article 13 check', status: 'ok', progress: 88, cta: 'View report' },
{ name: 'GB/T 42118 compliance scan', status: 'warn', progress: 54, cta: 'Continue' },
{ name: 'MIIT Draft — automotive AI embedding', status: 'info', progress: 12, cta: 'Start' },
];
const PROGRAMS = [
{ name: 'EU AI Act Readiness', status: 'ok', coverage: 88 },
{ name: 'China MIIT Compliance', status: 'warn', coverage: 54 },
{ name: 'ISO/SAE 21434 Audit', status: 'info', coverage: 32 },
];
const KPIS = [
{ label: 'Retrieval hit rate', value: 94, unit: '%' },
{ label: 'Evidence coverage', value: 78, unit: '%' },
{ label: 'Reviewer SLA', value: 91, unit: '%' },
];
const SERVICES = [
{ name: 'Vector store (Chroma)', status: 'ok' },
{ name: 'LLM gateway (Claude)', status: 'ok' },
{ name: 'Document parser', status: 'ok' },
{ name: 'SSE stream endpoint', status: 'ok' },
{ name: 'Regulation feed sync', status: 'warn' },
];
const EVENTS = [
{ date: '2025-11-18', title: 'EU AI Act — Delegated acts published', summary: 'European Commission releases implementing rules for high-risk AI classification under Annex III.' },
{ date: '2025-10-30', title: 'MIIT Draft — automotive AI', summary: 'New draft regulation covers in-vehicle AI training data provenance and OTA update governance.' },
{ date: '2025-10-05', title: 'ISO/SAE 21434 amendment', summary: 'Amendment 1 clarifies cybersecurity management system scope for software-only updates.' },
];
const STATUS_LABEL: Record<string, string> = { ok: 'Complete', warn: 'In progress', info: 'Pending' };
export function StatusPage() {
const [stats, setStats] = useState<Stats | null>(null);
2026-06-04 15:43:44 +08:00
const [loading, setLoading] = useState(true);
const [refreshKey, setRefreshKey] = useState(0);
const [showUpload, setShowUpload] = useState(false);
useEffect(() => {
2026-06-04 15:43:44 +08:00
setLoading(true);
fetch('/api/v1/status/stats')
.then(r => r.json())
2026-06-04 15:43:44 +08:00
.then(d => { setStats(d); setLoading(false); })
.catch(() => {
setStats({ documents_total: 42, documents_indexed: 38, documents_failed: 1, chunks_total: 3841 });
setLoading(false);
});
}, [refreshKey]);
return (
<div className="status-page">
<Topbar
title="System Status"
actions={
<>
<div className="search-box">
<Search size={13} />
<input placeholder="Search..." />
</div>
2026-06-04 15:43:44 +08:00
<button
className="btn sm"
onClick={() => {
const blob = new Blob([JSON.stringify(stats, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url; a.download = 'regulation-hub-status.json'; a.click();
URL.revokeObjectURL(url);
}}
>
<Download size={13} />Export status
</button>
<button className="btn sm" onClick={() => setRefreshKey(k => k + 1)}>
<RefreshCw size={13} />Refresh
</button>
<button className="btn sm primary" onClick={() => setShowUpload(true)}>
<Upload size={13} />New upload
</button>
</>
}
/>
<div className="page-content">
<div className="stats-grid">
<div className="stat-cell">
2026-06-04 15:43:44 +08:00
{loading ? <span className="loading-shimmer stat-value-loading" /> : <div className="stat-value">{stats?.documents_total ?? '—'}</div>}
<div className="stat-label">Documents total</div>
</div>
<div className="stat-cell">
2026-06-04 15:43:44 +08:00
{loading ? <span className="loading-shimmer stat-value-loading" /> : <div className="stat-value">{stats?.documents_indexed ?? '—'}</div>}
<div className="stat-label">Indexed</div>
</div>
<div className="stat-cell danger">
2026-06-04 15:43:44 +08:00
{loading ? <span className="loading-shimmer stat-value-loading" /> : <div className="stat-value">{stats?.documents_failed ?? '—'}</div>}
<div className="stat-label">Failed</div>
</div>
<div className="stat-cell">
2026-06-04 15:43:44 +08:00
{loading ? <span className="loading-shimmer stat-value-loading" /> : <div className="stat-value">{stats?.chunks_total?.toLocaleString() ?? '—'}</div>}
<div className="stat-label">Vector chunks</div>
</div>
</div>
<div className="panel-grid">
<div className="panel-left">
<div className="card">
<div className="card-header">Workflow queue</div>
{TASKS.map(t => (
<div key={t.name} className="task-row">
<div className="task-info">
<div className="task-name">{t.name}</div>
<div className="task-progress-bar">
<div className="task-progress-fill" style={{ width: `${t.progress}%` }} />
</div>
</div>
<span className={`status ${t.status}`}>{STATUS_LABEL[t.status]}</span>
<button className="btn sm">{t.cta}</button>
</div>
))}
</div>
<div className="card">
<div className="card-header">Active compliance programs</div>
{PROGRAMS.map(p => (
<div key={p.name} className="program-row">
<span className={`status ${p.status}`} style={{ marginRight: 'auto' }}>{p.name}</span>
<span className="program-pct">{p.coverage}%</span>
</div>
))}
<div className="kpi-strip">
{KPIS.map(k => (
<div key={k.label} className="kpi-item">
<div className="kpi-label">{k.label}</div>
<div className="kpi-bar"><div className="kpi-fill" style={{ width: `${k.value}%` }} /></div>
<div className="kpi-value">{k.value}{k.unit}</div>
</div>
))}
</div>
</div>
</div>
<div className="panel-right">
<div className="card">
<div className="card-header">System health</div>
{SERVICES.map(s => (
<div key={s.name} className="service-row">
<span className="service-name">{s.name}</span>
<span className={`status ${s.status}`}>{s.status === 'ok' ? 'Online' : 'Degraded'}</span>
</div>
))}
</div>
<div className="card">
<div className="card-header">Regulatory watch</div>
{EVENTS.map(e => (
<div key={e.title} className="event-row">
<div className="event-date">{e.date}</div>
<div className="event-title">{e.title}</div>
<div className="event-summary">{e.summary}</div>
</div>
))}
</div>
</div>
</div>
</div>
<footer className="page-footer">
<div className="live-dot" />
<span>Regulation Hub · T-Systems AI · Online</span>
</footer>
2026-06-04 15:43:44 +08:00
{showUpload && <UploadModal onClose={() => setShowUpload(false)} />}
</div>
);
}