MotorSignal-App/website/components/ops/operations-infrastructure.tsx

733 lines
32 KiB
TypeScript

'use client';
import React, { useState, useEffect, useRef, useCallback } from 'react';
import {
OperationsInfrastructureData,
ServiceStatusValue,
ComponentHealthStatus,
OperationsRole,
InfrastructureSpeedtestResult,
} from './types';
import { normalizeInfrastructureResponse } from '../../lib/ops-data';
import { OperationsActions } from './operations-actions';
interface OperationsInfrastructureProps {
role: OperationsRole;
}
export function OperationsInfrastructure({ role }: OperationsInfrastructureProps) {
const [data, setData] = useState<OperationsInfrastructureData | null>(null);
const [isRefreshing, setIsRefreshing] = useState(false);
const [lastFetchedAt, setLastFetchedAt] = useState<string | null>(null);
const [fetchError, setFetchError] = useState<string | null>(null);
const [copiedSha, setCopiedSha] = useState(false);
const [refreshInterval, setRefreshInterval] = useState<number>(30);
// Speedtest state
const [isRunningSpeedtest, setIsRunningSpeedtest] = useState(false);
const [speedtestResult, setSpeedtestResult] = useState<InfrastructureSpeedtestResult | null>(null);
const [speedtestError, setSpeedtestError] = useState<string | null>(null);
const isFetchingRef = useRef(false);
const pollTimerRef = useRef<NodeJS.Timeout | null>(null);
const fetchData = useCallback(async () => {
if (isFetchingRef.current) return;
isFetchingRef.current = true;
setIsRefreshing(true);
try {
const resp = await fetch('/api/ops/infrastructure', {
method: 'GET',
headers: { accept: 'application/json' },
cache: 'no-store',
});
if (resp.ok) {
const raw = await resp.json();
const normalized = normalizeInfrastructureResponse(raw);
setData(normalized);
setLastFetchedAt(new Date().toLocaleTimeString());
setFetchError(null);
} else if (resp.status === 401 || resp.status === 403) {
setFetchError('Authorization failed. Access restricted.');
} else {
const raw = await resp.json().catch(() => null);
setData(normalizeInfrastructureResponse(raw));
setFetchError('Metrics collector degraded or partially unavailable.');
}
} catch {
setFetchError('Network error reaching infrastructure endpoint.');
} finally {
isFetchingRef.current = false;
setIsRefreshing(false);
}
}, []);
const handleRunSpeedtest = async () => {
if (isRunningSpeedtest) return;
setIsRunningSpeedtest(true);
setSpeedtestError(null);
try {
const resp = await fetch('/api/ops/infrastructure/speedtest', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
});
if (!resp.ok) {
const data = await resp.json().catch(() => null);
setSpeedtestError(data?.detail || 'Failed to complete speedtest');
return;
}
const data = (await resp.json()) as InfrastructureSpeedtestResult;
setSpeedtestResult(data);
} catch {
setSpeedtestError('Network error executing speedtest');
} finally {
setIsRunningSpeedtest(false);
}
};
// Initial fetch and user-selectable visibility-gated polling loop (5s, 10s, 30s)
useEffect(() => {
void fetchData();
const handleVisibilityChange = () => {
if (document.visibilityState === 'visible') {
void fetchData();
}
};
document.addEventListener('visibilitychange', handleVisibilityChange);
const pollIntervalMs = refreshInterval ? refreshInterval * 1000 : 30000;
pollTimerRef.current = setInterval(() => {
if (document.visibilityState === 'visible') {
void fetchData();
}
}, pollIntervalMs || 30000);
return () => {
document.removeEventListener('visibilitychange', handleVisibilityChange);
if (pollTimerRef.current) {
clearInterval(pollTimerRef.current);
}
};
}, [fetchData, refreshInterval]);
const handleCopySha = (sha: string) => {
if (!sha) return;
navigator.clipboard.writeText(sha).then(() => {
setCopiedSha(true);
setTimeout(() => setCopiedSha(false), 2000);
});
};
const formatBytes = (bytes: number | null): string => {
if (bytes === null || bytes === undefined) return 'N/A';
if (bytes >= 1e12) return `${(bytes / 1e12).toFixed(2)} TB`;
if (bytes >= 1e9) return `${(bytes / 1e9).toFixed(2)} GB`;
if (bytes >= 1e6) return `${(bytes / 1e6).toFixed(1)} MB`;
return `${(bytes / 1e3).toFixed(1)} KB`;
};
const getServiceStatusBadge = (status: ServiceStatusValue) => {
switch (status) {
case 'active':
return {
label: 'Active (Running)',
icon: 'check_circle',
classes: 'bg-emerald-950/60 text-emerald-400 border-emerald-800/60',
};
case 'inactive':
return {
label: 'Inactive (Stopped)',
icon: 'pause_circle',
classes: 'bg-amber-950/60 text-amber-400 border-amber-800/60',
};
case 'failed':
return {
label: 'Failed',
icon: 'cancel',
classes: 'bg-rose-950/60 text-rose-400 border-rose-800/60',
};
case 'unknown':
default:
return {
label: 'Unknown / Unreachable',
icon: 'help',
classes: 'bg-slate-900/60 text-slate-400 border-slate-700/60',
};
}
};
const getComponentHealthBadge = (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-slate-900/60 text-slate-400 border-slate-700/60',
};
}
};
const resources = data?.resources;
const services = data?.services ?? [];
const network = data?.network;
const deployment = data?.deployment;
return (
<div className="space-y-8">
{/* Header & Controls */}
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 pb-6 border-b border-outline-variant/20">
<div>
<div className="flex items-center gap-2 mb-1">
<span className="material-symbols-outlined text-primary-container text-2xl">
dns
</span>
<h1 className="text-xl font-bold tracking-tight text-on-surface">
Infrastructure & Runtime
</h1>
<span className="px-2 py-0.5 rounded text-[11px] font-mono bg-surface-container-high border border-outline-variant/30 text-primary-fixed-dim">
Phase 2 Operations
</span>
</div>
<p className="text-xs text-on-surface-variant">
Host system resources, systemd core services, network totals, and deployment runtime health.
</p>
</div>
<div className="flex items-center gap-3">
{/* Selectable Auto-refresh interval (5s, 10s, 30s) */}
<div className="flex items-center gap-1.5 text-[11px] text-on-surface-variant font-mono bg-surface-container-low px-2 py-1 rounded-lg border border-outline-variant/20">
<span className="w-2 h-2 rounded-full bg-emerald-400 animate-pulse mr-0.5" />
<span className="text-outline">Auto:</span>
{[5, 10, 30].map((sec) => (
<button
key={sec}
type="button"
onClick={() => setRefreshInterval(sec)}
className={`px-1.5 py-0.5 rounded text-[11px] font-semibold transition-colors ${
refreshInterval === sec
? 'bg-primary-container text-on-primary-container font-bold'
: 'text-on-surface-variant hover:text-on-surface'
}`}
title={`Auto-refresh every ${sec} seconds`}
>
{sec}s
</button>
))}
</div>
{/* Last refreshed */}
{lastFetchedAt && (
<span className="text-[11px] text-outline font-mono hidden md:inline">
Updated: {lastFetchedAt}
</span>
)}
{/* Manual Refresh Button */}
<button
type="button"
onClick={() => void fetchData()}
disabled={isRefreshing}
className="flex items-center gap-2 px-3 py-1.5 rounded-lg text-xs font-semibold bg-surface-container-high hover:bg-surface-container-highest border border-outline-variant/40 text-on-surface disabled:opacity-50 transition-all focus-visible:ring-2 focus-visible:ring-primary-container"
>
<span
className={`material-symbols-outlined text-[16px] ${
isRefreshing ? 'animate-spin' : ''
}`}
>
refresh
</span>
<span>{isRefreshing ? 'Refreshing...' : 'Refresh'}</span>
</button>
</div>
</div>
{/* Optional Warning Banner */}
{fetchError && (
<div className="p-3.5 rounded-lg bg-amber-950/40 border border-amber-800/50 flex items-center gap-2.5 text-xs text-amber-300">
<span className="material-symbols-outlined text-amber-400 text-base">
warning
</span>
<span>{fetchError}</span>
</div>
)}
{/* SECTION 1: System Resources */}
<div>
<div className="flex items-center justify-between mb-3">
<h2 className="text-xs font-bold uppercase tracking-wider text-outline">
Server Resources
</h2>
<span className="text-[10px] text-outline font-mono">
Target Host: Ubuntu VPS
</span>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-4">
{/* CPU Usage */}
<div className="p-4 rounded-xl bg-surface-container-low border border-outline-variant/20 space-y-3">
<div className="flex items-center justify-between">
<span className="text-xs font-medium text-on-surface-variant">CPU Usage</span>
<span className="material-symbols-outlined text-base text-primary-fixed-dim">
memory
</span>
</div>
<div>
<div className="text-2xl font-bold font-mono tracking-tight text-on-surface">
{resources?.cpu_percent !== null && resources?.cpu_percent !== undefined
? `${resources.cpu_percent}%`
: 'Unavailable'}
</div>
<p className="text-[11px] text-outline font-mono mt-0.5">
Load: {resources?.load_avg ? resources.load_avg.join(', ') : 'N/A'}
</p>
</div>
{/* Progress Bar */}
<div className="w-full bg-surface-container-highest h-1.5 rounded-full overflow-hidden">
<div
className="bg-primary-container h-full rounded-full transition-all duration-300"
style={{ width: `${Math.min(100, Math.max(0, resources?.cpu_percent ?? 0))}%` }}
/>
</div>
</div>
{/* Memory (RAM) */}
<div className="p-4 rounded-xl bg-surface-container-low border border-outline-variant/20 space-y-3">
<div className="flex items-center justify-between">
<span className="text-xs font-medium text-on-surface-variant">Memory (RAM)</span>
<span className="material-symbols-outlined text-base text-primary-fixed-dim">
developer_board
</span>
</div>
<div>
<div className="text-xl font-bold font-mono tracking-tight text-on-surface">
{resources?.memory
? `${formatBytes(resources.memory.used)} / ${formatBytes(resources.memory.total)}`
: 'Unavailable'}
</div>
<p className="text-[11px] text-outline font-mono mt-0.5">
{resources?.memory
? `${resources.memory.percent}% used (${formatBytes(resources.memory.available)} free)`
: 'N/A'}
</p>
</div>
{/* Progress Bar */}
<div className="w-full bg-surface-container-highest h-1.5 rounded-full overflow-hidden">
<div
className={`h-full rounded-full transition-all duration-300 ${
(resources?.memory?.percent ?? 0) >= 90
? 'bg-rose-500'
: (resources?.memory?.percent ?? 0) >= 80
? 'bg-amber-500'
: 'bg-primary-container'
}`}
style={{ width: `${Math.min(100, Math.max(0, resources?.memory?.percent ?? 0))}%` }}
/>
</div>
</div>
{/* Swap Memory */}
<div className="p-4 rounded-xl bg-surface-container-low border border-outline-variant/20 space-y-3">
<div className="flex items-center justify-between">
<span className="text-xs font-medium text-on-surface-variant">Swap Space</span>
<span className="material-symbols-outlined text-base text-primary-fixed-dim">
swap_horiz
</span>
</div>
<div>
<div className="text-xl font-bold font-mono tracking-tight text-on-surface">
{resources?.swap
? `${formatBytes(resources.swap.used)} / ${formatBytes(resources.swap.total)}`
: 'Unavailable'}
</div>
<p className="text-[11px] text-outline font-mono mt-0.5">
{resources?.swap ? `${resources.swap.percent}% utilized` : 'N/A'}
</p>
</div>
{/* Progress Bar */}
<div className="w-full bg-surface-container-highest h-1.5 rounded-full overflow-hidden">
<div
className="bg-primary-container/70 h-full rounded-full transition-all duration-300"
style={{ width: `${Math.min(100, Math.max(0, resources?.swap?.percent ?? 0))}%` }}
/>
</div>
</div>
{/* Disk Storage (/) */}
<div className="p-4 rounded-xl bg-surface-container-low border border-outline-variant/20 space-y-3">
<div className="flex items-center justify-between">
<div className="flex items-center gap-1.5">
<span className="text-xs font-medium text-on-surface-variant">Root Disk (/)</span>
{resources?.disk?.critical ? (
<span className="px-1.5 py-0.2 rounded text-[9px] font-mono font-bold bg-rose-950 text-rose-300 border border-rose-800">
CRITICAL
</span>
) : resources?.disk?.warning ? (
<span className="px-1.5 py-0.2 rounded text-[9px] font-mono font-bold bg-amber-950 text-amber-300 border border-amber-800">
WARNING
</span>
) : null}
</div>
<span className="material-symbols-outlined text-base text-primary-fixed-dim">
hard_drive
</span>
</div>
<div>
<div className="text-xl font-bold font-mono tracking-tight text-on-surface">
{resources?.disk
? `${formatBytes(resources.disk.used)} / ${formatBytes(resources.disk.total)}`
: 'Unavailable'}
</div>
<p className="text-[11px] text-outline font-mono mt-0.5">
{resources?.disk
? `${resources.disk.percent}% used (${formatBytes(resources.disk.available)} free)`
: 'N/A'}
</p>
</div>
{/* Progress Bar */}
<div className="w-full bg-surface-container-highest h-1.5 rounded-full overflow-hidden">
<div
className={`h-full rounded-full transition-all duration-300 ${
resources?.disk?.critical
? 'bg-rose-500'
: resources?.disk?.warning
? 'bg-amber-500'
: 'bg-primary-container'
}`}
style={{ width: `${Math.min(100, Math.max(0, resources?.disk?.percent ?? 0))}%` }}
/>
</div>
</div>
{/* Server Uptime */}
<div className="p-4 rounded-xl bg-surface-container-low border border-outline-variant/20 space-y-3">
<div className="flex items-center justify-between">
<span className="text-xs font-medium text-on-surface-variant">Server Uptime</span>
<span className="material-symbols-outlined text-base text-primary-fixed-dim">
schedule
</span>
</div>
<div>
<div className="text-lg font-bold font-mono tracking-tight text-on-surface truncate">
{resources?.uptime_formatted || 'Unavailable'}
</div>
<p className="text-[11px] text-outline font-mono mt-0.5">
{resources?.uptime_seconds
? `${resources.uptime_seconds.toLocaleString()}s total uptime`
: 'N/A'}
</p>
</div>
<div className="text-[10px] text-primary-fixed-dim font-mono">
System running continuously
</div>
</div>
</div>
</div>
{/* SECTION 2: Core Systemd Services */}
<div>
<div className="flex items-center justify-between mb-3">
<h2 className="text-xs font-bold uppercase tracking-wider text-outline">
Core Services (systemd)
</h2>
<span className="text-[10px] text-outline font-mono">
Strict Allowlist: 4 Services
</span>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
{services.map((svc) => {
const badge = getServiceStatusBadge(svc.status);
return (
<div
key={svc.name}
className="p-4 rounded-xl bg-surface-container-low border border-outline-variant/20 flex flex-col justify-between space-y-3"
>
<div>
<div className="flex items-start justify-between gap-2">
<div>
<h3 className="text-sm font-semibold text-on-surface">
{svc.display_name}
</h3>
<p className="text-[11px] font-mono text-outline mt-0.5">
{svc.name}.service
</p>
</div>
</div>
<p className="text-xs text-on-surface-variant mt-2 leading-relaxed">
{svc.description}
</p>
</div>
<div className="pt-3 border-t border-outline-variant/10 flex items-center justify-between">
<span className="text-[11px] text-outline">Service Status</span>
<div
className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-[11px] font-mono font-medium border ${badge.classes}`}
>
<span className="material-symbols-outlined text-[14px]">
{badge.icon}
</span>
<span>{badge.label}</span>
</div>
</div>
</div>
);
})}
</div>
</div>
{/* SECTION 3 & 4: Network Cumulative Totals & Deployment Health */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Network Cumulative Totals */}
<div className="p-5 rounded-xl bg-surface-container-low border border-outline-variant/20 space-y-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="material-symbols-outlined text-primary-container text-xl">
network_check
</span>
<div>
<h3 className="text-sm font-bold text-on-surface">
Network Cumulative Totals
</h3>
<p className="text-[11px] text-outline">
Aggregated across non-loopback interfaces since host boot
</p>
</div>
</div>
<div className="flex items-center gap-2">
<span className="px-2 py-0.5 rounded text-[10px] font-mono bg-surface-container-high text-primary-fixed-dim border border-outline-variant/30">
Not a rate
</span>
<button
type="button"
onClick={() => void handleRunSpeedtest()}
disabled={isRunningSpeedtest}
className="flex items-center gap-1.5 px-2.5 py-1 rounded text-xs font-semibold bg-primary-container text-on-primary-container hover:opacity-90 disabled:opacity-50 transition-all shadow-sm"
>
<span className={`material-symbols-outlined text-[15px] ${isRunningSpeedtest ? 'animate-spin' : ''}`}>
{isRunningSpeedtest ? 'sync' : 'speed'}
</span>
<span>{isRunningSpeedtest ? 'Testing...' : 'Run Speedtest'}</span>
</button>
</div>
</div>
<div className="grid grid-cols-2 gap-4 pt-2">
<div className="p-3.5 rounded-lg bg-surface-container-lowest border border-outline-variant/15 space-y-1">
<div className="flex items-center gap-1.5 text-xs text-on-surface-variant">
<span className="material-symbols-outlined text-[15px] text-emerald-400">
download
</span>
<span>Total Received</span>
</div>
<div className="text-xl font-bold font-mono text-on-surface">
{formatBytes(network?.bytes_received ?? null)}
</div>
<p className="text-[10px] text-outline font-mono">
{network?.bytes_received ? `${network.bytes_received.toLocaleString()} bytes` : 'N/A'}
</p>
</div>
<div className="p-3.5 rounded-lg bg-surface-container-lowest border border-outline-variant/15 space-y-1">
<div className="flex items-center gap-1.5 text-xs text-on-surface-variant">
<span className="material-symbols-outlined text-[15px] text-primary-container">
upload
</span>
<span>Total Transmitted</span>
</div>
<div className="text-xl font-bold font-mono text-on-surface">
{formatBytes(network?.bytes_transmitted ?? null)}
</div>
<p className="text-[10px] text-outline font-mono">
{network?.bytes_transmitted ? `${network.bytes_transmitted.toLocaleString()} bytes` : 'N/A'}
</p>
</div>
</div>
{isRunningSpeedtest && (
<div className="p-3.5 rounded-lg bg-surface-container-lowest border border-primary-container/30 flex items-center justify-between animate-pulse text-xs font-mono">
<div className="flex items-center gap-2 text-primary-fixed-dim">
<span className="material-symbols-outlined text-[18px] animate-spin">network_check</span>
<span>Benchmarking host latency and network throughput...</span>
</div>
<span className="text-outline">Live test</span>
</div>
)}
{speedtestError && (
<div className="p-3 rounded-lg bg-rose-950/40 border border-rose-800/40 text-xs text-rose-300 flex items-center gap-2">
<span className="material-symbols-outlined text-[16px]">error</span>
<span>{speedtestError}</span>
</div>
)}
{speedtestResult && !isRunningSpeedtest && (
<div className="p-3.5 rounded-lg bg-surface-container-lowest border border-outline-variant/20 space-y-2.5">
<div className="flex items-center justify-between text-xs">
<div className="flex items-center gap-1.5 font-bold text-on-surface">
<span className="material-symbols-outlined text-[16px] text-emerald-400">speed</span>
<span>Live Network Speedtest</span>
</div>
<span className="text-[10px] font-mono text-outline">
{new Date(speedtestResult.tested_at).toLocaleTimeString()}
</span>
</div>
<div className="grid grid-cols-3 gap-2 text-center">
<div className="p-2 rounded bg-surface-container-low border border-outline-variant/10">
<span className="block text-[10px] text-outline font-mono">LATENCY</span>
<span className="text-sm font-bold font-mono text-emerald-400">{speedtestResult.latency_ms} ms</span>
</div>
<div className="p-2 rounded bg-surface-container-low border border-outline-variant/10">
<span className="block text-[10px] text-outline font-mono">DOWNLOAD</span>
<span className="text-sm font-bold font-mono text-cyan-400">{speedtestResult.download_mbps} Mbps</span>
</div>
<div className="p-2 rounded bg-surface-container-low border border-outline-variant/10">
<span className="block text-[10px] text-outline font-mono">UPLOAD</span>
<span className="text-sm font-bold font-mono text-primary-container">{speedtestResult.upload_mbps} Mbps</span>
</div>
</div>
<div className="text-[10px] font-mono text-outline flex items-center justify-between pt-1">
<span>Target: {speedtestResult.server_target}</span>
{speedtestResult.client_ip && <span>IP: {speedtestResult.client_ip}</span>}
</div>
</div>
)}
<div className="p-2.5 rounded-lg bg-surface-container-lowest/60 border border-outline-variant/10 text-[11px] text-outline flex items-center gap-2">
<span className="material-symbols-outlined text-[14px] text-primary-fixed-dim">
info
</span>
<span>
Cumulative Total Since Boot across physical network interfaces. Instantaneous bandwidth rates are not tracked.
</span>
</div>
</div>
{/* Deployment Health Foundations */}
<div className="p-5 rounded-xl bg-surface-container-low border border-outline-variant/20 space-y-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="material-symbols-outlined text-primary-container text-xl">
deployed_code
</span>
<div>
<h3 className="text-sm font-bold text-on-surface">
Deployment Health Foundations
</h3>
<p className="text-[11px] text-outline">
Active code revision and subsystem runtime readiness
</p>
</div>
</div>
<span className="px-2 py-0.5 rounded text-[10px] font-mono bg-surface-container-high text-on-surface-variant border border-outline-variant/30">
Foundation
</span>
</div>
<div className="space-y-3 pt-1">
{/* Active Git Revision */}
<div className="p-3 rounded-lg bg-surface-container-lowest border border-outline-variant/15 flex items-center justify-between">
<div>
<div className="text-xs text-on-surface-variant">Active Git Revision</div>
<div className="flex items-center gap-2 mt-1">
<span className="font-mono text-sm font-bold text-on-surface">
{deployment?.short_revision || 'Unavailable'}
</span>
{deployment?.git_revision && (
<button
type="button"
onClick={() => handleCopySha(deployment.git_revision!)}
title="Copy full SHA"
className="p-1 rounded text-outline hover:text-on-surface hover:bg-surface-container transition-colors"
>
<span className="material-symbols-outlined text-[14px]">
{copiedSha ? 'check' : 'content_copy'}
</span>
</button>
)}
</div>
</div>
<div className="flex items-center gap-1.5 font-mono text-[11px]">
<span
className={`w-2 h-2 rounded-full ${
deployment?.revision_status === 'known' ? 'bg-emerald-400' : 'bg-slate-500'
}`}
/>
<span className="capitalize text-outline">
{deployment?.revision_status ?? 'unavailable'}
</span>
</div>
</div>
{/* Health indicators */}
<div className="grid grid-cols-2 gap-3">
<div className="p-3 rounded-lg bg-surface-container-lowest border border-outline-variant/15">
<div className="text-xs text-on-surface-variant mb-1">Website Ingress</div>
{(() => {
const badge = getComponentHealthBadge(deployment?.website_health ?? 'unknown');
return (
<div
className={`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-[10px] font-mono border ${badge.classes}`}
>
<span className="material-symbols-outlined text-[12px]">{badge.icon}</span>
<span>{badge.label}</span>
</div>
);
})()}
</div>
<div className="p-3 rounded-lg bg-surface-container-lowest border border-outline-variant/15">
<div className="text-xs text-on-surface-variant mb-1">API Readiness</div>
{(() => {
const badge = getComponentHealthBadge(deployment?.api_health ?? 'unknown');
return (
<div
className={`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-[10px] font-mono border ${badge.classes}`}
>
<span className="material-symbols-outlined text-[12px]">{badge.icon}</span>
<span>{badge.label}</span>
</div>
);
})()}
</div>
</div>
{/* Deployment History Status */}
<div className="p-2.5 rounded-lg bg-surface-container-lowest/60 border border-outline-variant/10 text-[11px] text-outline flex items-center justify-between">
<span className="font-mono">Deployment History:</span>
<span className="text-primary-fixed-dim font-mono">Audited in Phase 2</span>
</div>
</div>
</div>
</div>
{/* Operations Actions & Deployment Controls */}
<div className="mt-8 pt-6 border-t border-outline-variant/20">
<OperationsActions role={role} onActionCompleted={fetchData} />
</div>
</div>
);
}