204 lines
7.4 KiB
TypeScript
204 lines
7.4 KiB
TypeScript
'use client';
|
|
|
|
import React from 'react';
|
|
import { OperationsHealth, ComponentHealthStatus, HealthComponentKey } from './types';
|
|
import { HEALTH_COMPONENT_METADATA } from '../../lib/ops-data';
|
|
|
|
interface OperationsOverviewProps {
|
|
health: OperationsHealth;
|
|
onRefresh: () => Promise<void> | void;
|
|
isRefreshing: boolean;
|
|
}
|
|
|
|
export function OperationsOverview({
|
|
health,
|
|
onRefresh,
|
|
isRefreshing,
|
|
}: OperationsOverviewProps) {
|
|
const componentKeys: HealthComponentKey[] = [
|
|
'operations_api',
|
|
'operations_database',
|
|
'session_validation',
|
|
'audit_persistence',
|
|
];
|
|
|
|
function getStatusBadge(status: ComponentHealthStatus) {
|
|
switch (status) {
|
|
case 'available':
|
|
return {
|
|
label: 'AVAILABLE',
|
|
icon: 'check_circle',
|
|
classes: 'bg-emerald-950/60 text-emerald-400 border-emerald-800/60',
|
|
};
|
|
case 'degraded':
|
|
return {
|
|
label: 'DEGRADED',
|
|
icon: 'warning',
|
|
classes: 'bg-amber-950/60 text-amber-400 border-amber-800/60',
|
|
};
|
|
case 'unavailable':
|
|
return {
|
|
label: 'UNAVAILABLE',
|
|
icon: 'cancel',
|
|
classes: 'bg-rose-950/60 text-rose-400 border-rose-800/60',
|
|
};
|
|
case 'unknown':
|
|
default:
|
|
return {
|
|
label: 'UNKNOWN',
|
|
icon: 'help',
|
|
classes: 'bg-surface-container-highest text-on-surface-variant border-outline-variant/40',
|
|
};
|
|
}
|
|
}
|
|
|
|
const formattedCheckedAt = health.checked_at
|
|
? new Date(health.checked_at).toLocaleString('en-US', {
|
|
year: 'numeric',
|
|
month: 'short',
|
|
day: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
second: '2-digit',
|
|
timeZoneName: 'short',
|
|
})
|
|
: '--';
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Header Row: Title & Manual Refresh */}
|
|
<div className="flex flex-col sm:flex-row sm:items-end justify-between gap-4 pb-4 border-b border-outline-variant/30">
|
|
<div>
|
|
<span className="text-[11px] font-mono uppercase tracking-widest text-primary-fixed-dim">
|
|
SYSTEM HEALTH
|
|
</span>
|
|
<h1 className="font-headline-xl text-2xl sm:text-3xl font-bold text-on-surface tracking-tight mt-1">
|
|
Operations Overview
|
|
</h1>
|
|
<p className="text-xs text-on-surface-variant mt-1">
|
|
Real-time health telemetry across core Operations subsystems.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-3 shrink-0">
|
|
<div className="text-right">
|
|
<span className="block text-[10px] uppercase font-mono text-outline">
|
|
Last checked
|
|
</span>
|
|
<time
|
|
dateTime={health.checked_at}
|
|
className="text-xs font-mono text-on-surface font-medium"
|
|
>
|
|
{formattedCheckedAt}
|
|
</time>
|
|
</div>
|
|
|
|
<button
|
|
type="button"
|
|
onClick={() => void onRefresh()}
|
|
disabled={isRefreshing}
|
|
aria-label="Refresh Operations health status"
|
|
className="inline-flex items-center gap-1.5 px-3.5 py-2 rounded-lg bg-surface-container-high hover:bg-surface-bright text-primary-container border border-primary-container/40 text-xs font-semibold shadow-sm transition-all focus-visible:ring-2 focus-visible:ring-primary-container focus-visible:outline-none disabled:opacity-50 disabled:cursor-not-allowed"
|
|
>
|
|
<span
|
|
className={`material-symbols-outlined text-[16px] ${
|
|
isRefreshing ? 'animate-spin' : ''
|
|
}`}
|
|
>
|
|
refresh
|
|
</span>
|
|
<span>{isRefreshing ? 'Checking...' : 'Refresh'}</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 4 Health Status Cards */}
|
|
<div
|
|
className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4"
|
|
aria-label="Subsystem Health Telemetry"
|
|
>
|
|
{componentKeys.map((key) => {
|
|
const meta = HEALTH_COMPONENT_METADATA[key];
|
|
const comp = health.components?.[key];
|
|
const status = comp?.status ?? 'unknown';
|
|
const badge = getStatusBadge(status);
|
|
|
|
return (
|
|
<article
|
|
key={key}
|
|
className="p-4 rounded-xl bg-surface-container-low border border-outline-variant/30 flex flex-col justify-between gap-3 relative overflow-hidden group hover:border-outline-variant/60 transition-colors"
|
|
>
|
|
<div className="space-y-1">
|
|
<span className="text-[10px] font-mono uppercase tracking-wider text-outline">
|
|
Subsystem
|
|
</span>
|
|
<h2 className="text-sm font-bold text-on-surface tracking-tight">
|
|
{meta.name}
|
|
</h2>
|
|
<p className="text-[11px] text-on-surface-variant leading-relaxed">
|
|
{comp?.description || meta.description}
|
|
</p>
|
|
</div>
|
|
|
|
<div className="pt-2 border-t border-outline-variant/20 flex items-center justify-between">
|
|
<span className="text-[10px] text-outline font-mono uppercase">
|
|
Status
|
|
</span>
|
|
<div
|
|
className={`inline-flex items-center gap-1 px-2 py-0.5 rounded text-[10px] font-mono font-bold tracking-wider border uppercase ${badge.classes}`}
|
|
role="status"
|
|
aria-live="polite"
|
|
>
|
|
<span className="material-symbols-outlined text-[12px]">
|
|
{badge.icon}
|
|
</span>
|
|
<span>{badge.label}</span>
|
|
</div>
|
|
</div>
|
|
</article>
|
|
);
|
|
})}
|
|
</div>
|
|
|
|
{/* Operational Policy & Architecture Boundary */}
|
|
<div className="p-5 rounded-xl bg-surface-container-lowest border border-outline-variant/30 space-y-3">
|
|
<div className="flex items-center gap-2">
|
|
<span className="material-symbols-outlined text-primary-container text-[18px]">
|
|
verified_user
|
|
</span>
|
|
<h3 className="text-xs font-bold text-on-surface uppercase tracking-wider">
|
|
Operational Architecture & Data Boundary
|
|
</h3>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 text-xs text-on-surface-variant pt-2 border-t border-outline-variant/20">
|
|
<div>
|
|
<span className="block font-semibold text-on-surface mb-0.5">
|
|
Authority Source
|
|
</span>
|
|
<p className="leading-normal">
|
|
PostgreSQL <code className="text-primary-fixed-dim">ops.operators</code> schema. Consumer roles and Firebase custom claims strictly bypassed.
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<span className="block font-semibold text-on-surface mb-0.5">
|
|
Privacy Isolation
|
|
</span>
|
|
<p className="leading-normal">
|
|
Zero cross-rider data access. Personal rides, GPS points, vehicles, and billing accounts are structurally inaccessible.
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<span className="block font-semibold text-on-surface mb-0.5">
|
|
Session Integrity
|
|
</span>
|
|
<p className="leading-normal">
|
|
Governed by host-prefixed <code className="text-primary-fixed-dim">__Host-motorsignal_ops</code> cookies with strict same-origin isolation.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|