import { CheckCircle2, Circle, Loader2, AlertTriangle, Power, LogIn } from "lucide-react"; import { useNavigate } from "react-router-dom"; import { useStatus } from "../hooks/useStatus"; import { Connection } from "@bindings/services"; import type { SystemEvent } from "@bindings/services/models.js"; import { Button } from "../components/Button"; import { Card } from "../components/Card"; import { cn } from "../lib/cn"; import { NetBirdConnectToggle, ConnectionState } from "../components/NetBirdConnectToggle"; export default function Status() { const { status, error } = useStatus(); const navigate = useNavigate(); const connState = status?.status ?? "Idle"; const connected = connState === "Connected"; const connecting = connState === "Connecting"; // The daemon reports "NeedsLogin" on a fresh install or after a session // expires; "SessionExpired" once a previously good session lapses. In both // cases Connect would fail without a fresh SSO login. const needsLogin = connState === "NeedsLogin" || connState === "SessionExpired" || connState === "LoginFailed"; // DaemonUnavailable is the synthetic status the UI emits when the gRPC // socket is unreachable; Up/Down would just error, so the toggle is dead. const unreachable = connState === "DaemonUnavailable"; // Always offer Login while we aren't Connected — including Connecting, // because a stuck Login on the daemon leaves us in Connecting forever and // the user has no other way out. Disconnect is the manual unstick path. const showLogin = !connected; const toggleState: ConnectionState = connected ? ConnectionState.Connected : connecting ? ConnectionState.Connecting : ConnectionState.Disconnected; const login = () => navigate("/login"); const connect = () => Connection.Up({ profileName: "", username: "" }).catch(console.error); const disconnect = () => Connection.Down().catch(console.error); const toggleConnection = () => { if (needsLogin) { navigate("/login"); return; } if (connected) { disconnect(); return; } connect(); }; return (

{connState}

{status?.local.fqdn || "—"}

{needsLogin ? ( ) : ( )} {showLogin && !needsLogin && ( )}
{error && (
{error}
)}

Recent events

{(() => { const events = dedupEvents(status?.events ?? []).slice(0, 8); if (events.length === 0) { return

No recent events.

; } return ( ); })()}
); } function StateIcon({ state }: { state: string }) { const cls = "h-7 w-7"; switch (state) { case "Connected": return ; case "Connecting": return ; case "Error": return ; default: return ; } } function InfoCard({ label, value }: { label: string; value: string }) { return (

{label}

{value}

); } // dedupEvents collapses repeated daemon events that carry the same logical // content. The daemon emits one "new_version_available" event per check tick, // so its 10-event ring buffer fills with duplicates after a quiet hour. Same // goes for periodic "DNS unreachable" or "auth retry" events. We key by // message + a small set of identity-bearing metadata fields and keep the // newest occurrence (the events array is already in publish order). function dedupEvents(events: SystemEvent[]): SystemEvent[] { const seen = new Set(); const out: SystemEvent[] = []; for (let i = events.length - 1; i >= 0; i--) { const e = events[i]; const md = e.metadata ?? {}; const key = [ e.severity, e.category, e.userMessage || e.message, md["new_version_available"] ?? "", md["enforced"] ?? "", ].join("|"); // eslint-disable-next-line no-console console.log("[dedup]", { key, event: e }); if (seen.has(key)) continue; seen.add(key); out.unshift(e); } return out; } function LinkCard({ label, link, }: { label: string; link?: { url: string; connected: boolean; error?: string }; }) { return (

{label}

{link?.url || "—"}

{link?.error && (

{link.error}

)}
); }