"use client"; import { useTranslation } from "react-i18next"; import { Badge } from "@kandev/ui/badge"; import { Button } from "@kandev/ui/button"; import { CardContent } from "@kandev/ui/card"; import { IconLoader2 } from "@tabler/icons-react"; import type { SSHReachabilityRecord, SSHReachabilityReason } from "@/lib/types/http-ssh"; import { formatRelative } from "@/lib/i18n/formats"; import { useSSHReachability } from "@/hooks/domains/settings/use-ssh-reachability"; import { SettingsCard } from "@/components/settings/settings-card"; import { SettingsCardHeader } from "@/components/settings/settings-card-header"; import { settingsActionClassName } from "@/components/settings/settings-control"; export interface SSHReachabilityCardProps { executorId: string; } const STALE_INTERVAL_MULTIPLIER = 3; /** * A record older than three probe intervals is stale rather than current. * Disabled probing has no cadence to measure against, and a never-probed * record has nothing to be stale relative to, so both read as not-stale. */ export function isReachabilityStale( checkedAt: string | null, probeIntervalSeconds: number, probingEnabled: boolean, now: number = Date.now(), ): boolean { if (!probingEnabled || !checkedAt || probeIntervalSeconds <= 0) return false; const checkedMs = Date.parse(checkedAt); if (Number.isNaN(checkedMs)) return false; return now - checkedMs > probeIntervalSeconds * 1000 * STALE_INTERVAL_MULTIPLIER; } export function reachabilityReasonKey(reason: SSHReachabilityReason): string | null { switch (reason) { case "config": return "executors:sshReachabilityReasonConfig"; case "timeout": return "executors:sshReachabilityReasonTimeout"; case "host_key": return "executors:sshReachabilityReasonHostKey"; case "auth": return "executors:sshReachabilityReasonAuth"; case "network": return "executors:sshReachabilityReasonNetwork"; case "unknown": return "executors:sshReachabilityReasonUnknown"; default: return null; } } export function reachabilityStateKey(state: SSHReachabilityRecord["state"]): string { switch (state) { case "reachable": return "executors:sshReachabilityStateReachable"; case "unreachable": return "executors:sshReachabilityStateUnreachable"; default: return "executors:sshReachabilityStateUnknown"; } } /** * Renders the most recent SSH reachability record for one executor: state, * probed host, failure reason/message, consecutive-failure count, and the * age of the last completed and last successful probe. Never fetches * directly into local-only state — every fetch result is written through the * store so a live executor.reachability.changed push and this card's own * refresh share one reconciliation path. */ export function SSHReachabilityCard({ executorId }: SSHReachabilityCardProps) { const { t } = useTranslation(); const { record, loadError, probing, probeNow, now } = useSSHReachability(executorId); return ( void probeNow()} disabled={probing} data-testid="ssh-reachability-probe-now" className={settingsActionClassName("cursor-pointer")} > {probing ? : null} {t("executors:sshReachabilityProbeNow")} } /> ); } function ReachabilityBody({ record, loadError, now, }: { record: SSHReachabilityRecord | undefined; loadError: boolean; now: number; }) { const { t } = useTranslation(); if (!record) { if (loadError) { return (

{t("executors:sshReachabilityLoadFailed")}

); } return null; } const stale = isReachabilityStale( record.checked_at, record.probe_interval_seconds, record.probing_enabled, now, ); const reasonKey = record.reason ? reachabilityReasonKey(record.reason) : null; return (
{stale ? ( {t("executors:sshReachabilityStale")} ) : null}
{record.host ? (

{t("executors:sshReachabilityHost", { host: record.host })}

) : null} {record.state === "unreachable" && reasonKey ? (

{t(reasonKey)}

) : null} {record.state === "unreachable" && record.message ? (

{record.message}

) : null}

{t("executors:sshReachabilityConsecutiveFailures", { count: record.consecutive_failures })}

{record.checked_at ? t("executors:sshReachabilityAgeLastProbe", { age: formatRelative(record.checked_at) }) : t("executors:sshReachabilityNeverProbed")}

{record.last_success_at ? t("executors:sshReachabilityAgeLastSuccess", { age: formatRelative(record.last_success_at), }) : t("executors:sshReachabilityNoSuccess")}

{!record.probing_enabled ? (

{t("executors:sshReachabilityProbingOff")}

) : null}
); } const STATE_BADGE_CLASSNAMES: Record = { reachable: "border-green-500/30 bg-green-500/10 text-green-700", unreachable: "border-red-500/30 bg-red-500/10 text-red-700", unknown: "border-muted-foreground/30 bg-muted text-muted-foreground", }; function StateBadge({ state }: { state: SSHReachabilityRecord["state"] }) { const { t } = useTranslation(); const variantClass = STATE_BADGE_CLASSNAMES[state]; return ( {t(reachabilityStateKey(state))} ); }