import type { Node } from "../types"; import { StatusBadge } from "./StatusBadge"; interface NodeCardProps { node: Node; onClick?: (node: Node) => void; } function formatUptime(secs: number | null): string { if (secs == null) return "--"; if (secs < 60) return `${secs}s`; if (secs < 3600) return `${Math.floor(secs / 60)}m`; if (secs < 86400) return `${Math.floor(secs / 3600)}h ${Math.floor((secs % 3600) / 60)}m`; return `${Math.floor(secs / 86400)}d ${Math.floor((secs % 86400) / 3600)}h`; } function formatLastSeen(iso: string): string { try { const d = new Date(iso); const diffMs = Date.now() - d.getTime(); if (diffMs < 60_000) return "just now"; if (diffMs < 3_600_000) return `${Math.floor(diffMs / 60_000)}m ago`; if (diffMs < 86_400_000) return `${Math.floor(diffMs / 3_600_000)}h ago`; return d.toLocaleDateString(); } catch { return "--"; } } export function NodeCard({ node, onClick }: NodeCardProps) { const isOnline = node.health === "online"; return (
onClick?.(node)} style={{ background: "var(--bg-elevated)", border: "1px solid var(--border)", borderRadius: 8, padding: "var(--space-4)", cursor: onClick ? "pointer" : "default", opacity: isOnline ? 1 : 0.6, transition: "border-color 0.15s, background 0.15s", }} onMouseEnter={(e) => { e.currentTarget.style.borderColor = "var(--accent)"; e.currentTarget.style.background = "var(--bg-hover)"; }} onMouseLeave={(e) => { e.currentTarget.style.borderColor = "var(--border)"; e.currentTarget.style.background = "var(--bg-elevated)"; }} > {/* Header */}
{node.friendly_name || node.hostname || `Node ${node.node_id}`}
{node.ip}
{/* Details grid */}
); } function DetailRow({ label, value, mono = false, }: { label: string; value: string; mono?: boolean; }) { return (
{label}
{value}
); }