'use client';
import { useEffect, useMemo, useState } from 'react';
import Link from 'next/link';
import { AlertTriangle, CheckCircle2, Cloud, DatabaseBackup, ExternalLink, Gauge, HardDrive, RefreshCw, ShieldCheck, Timer, Wifi } from 'lucide-react';
import { Badge, Button } from '@/components/ui/Primitives';
import { SectionCard } from '@/components/hermes-shell';
import { api, type HermesOpsInstance, type HermesOpsSnapshot } from '@/lib/api';
function boolTone(value: boolean): 'success' | 'error' {
return value ? 'success' : 'error';
}
function boolText(value: boolean) {
return value ? 'OK' : 'Needs attention';
}
function formatDate(value: string | null) {
if (!value) return 'unknown';
const date = new Date(value);
if (Number.isNaN(date.getTime())) return value;
return new Intl.DateTimeFormat('en', {
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: '2-digit',
}).format(date);
}
function StatusRow({ label, value, ok }: { label: string; value: string; ok: boolean }) {
return (
{label}
{value}
);
}
function InstanceCard({ instance }: { instance: HermesOpsInstance }) {
const score = [
instance.gateway.active,
instance.gateway.enabled,
instance.dashboard.active,
instance.backup.timer.active,
instance.backup.repo.clean,
instance.google.workspaceToken,
].filter(Boolean).length;
return (
{instance.label}
{instance.hermesHome}
= 4 ? 'warning' : 'error'}>{score}/6 healthy
Backup repo
HEAD {instance.backup.repo.head ?? 'unknown'}
Last commit {formatDate(instance.backup.repo.lastCommitAt)}
{instance.backup.repo.clean ? 'Clean working tree' : 'Uncommitted changes present'}
Restore payload
{instance.backup.restoredFileCount ?? 'unknown'} tracked files
{instance.backup.restoredCronJobs ?? 'unknown'} cron job definitions
{instance.backup.repo.size ?? 'size unknown'}
);
}
export function HermesOpsPanel() {
const [snapshot, setSnapshot] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const load = async () => {
setLoading(true);
setError(null);
try {
setSnapshot(await api.getHermesOps());
} catch (err) {
setError(err instanceof Error ? err.message : 'Unable to load Hermes operations status');
} finally {
setLoading(false);
}
};
useEffect(() => {
void load();
const id = window.setInterval(() => void load(), 60_000);
return () => window.clearInterval(id);
}, []);
const allHealthy = useMemo(() => snapshot ? snapshot.warnings.length === 0 : false, [snapshot]);
return (
{snapshot ? {allHealthy ? 'All green' : `${snapshot.warnings.length} warning(s)`} : null}
)}
>
{error ? (
{error}
) : null}
{snapshot ? (
Tailscale IP
{snapshot.tailscaleIp ?? 'unknown'}
Emergency Drive
{snapshot.emergencyDriveUpload.active ? 'active' : 'inactive'}
Next Drive bundle
{snapshot.emergencyDriveUpload.nextRun ?? 'unknown'}
Generated
{formatDate(snapshot.generatedAt)}
{snapshot.warnings.length ? (
{snapshot.warnings.map((warning) => (
{warning}
))}
) : (
Vijay and Bheem recovery paths are healthy.
)}
{snapshot.instances.map((instance) => (
))}
Disaster recovery details live in{' '}
Hermes settings
{' '}and the tracked runbook in docs/hermes-disaster-recovery.md.
) : (
Loading live Hermes operations status...
)}
);
}