"use client"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { Activity, Boxes, ScrollText, Server, SlidersHorizontal, } from "lucide-react"; import { AlgaLogo } from "./AlgaLogo"; import { LogoutButton } from "./auth/LogoutButton"; import styles from "./status.module.css"; type RawTierMap = Record< string, boolean | { ready?: boolean; status?: string } >; type Blocker = { severity?: string; component?: string; layer?: string; reason?: string; nextAction?: string; loginBlocking?: boolean; }; type EventItem = { type?: string; reason?: string; namespace?: string; involvedObject?: string; message?: string; timestamp?: string | null; }; type SetupConfigResponse = { mode?: string }; type StatusResponse = { status?: string; // True when setup is blocked on a correctable install code; the UI offers a // "re-enter your install code" action and the /setup form is reachable again. setupReEditable?: boolean; rollup?: { state?: string; message?: string; nextAction?: string } | null; currentPhase?: string; urls?: { statusUrl?: string | null; loginUrl?: string | null }; activeOperations?: Array<{ component?: string; image?: string | null; message?: string; elapsedSeconds?: number | null; }>; tiers?: RawTierMap; readinessTiers?: RawTierMap; topBlockers?: Blocker[]; failures?: Array<{ category?: string; phase?: string; suspectedCause?: string; suggestedNextStep?: string; }>; bootstrap?: { job?: { name?: string | null; state?: string; failed?: boolean; completed?: boolean; }; logs?: { available?: boolean; tail?: string[]; detectedErrors?: string[] }; }; recentEvents?: EventItem[]; installState?: { status?: string; phase?: string; lastAction?: string; updatedAt?: string; }; kubernetes?: { nodes?: Array<{ name?: string; ready?: boolean }>; podCount?: number; jobCount?: number; helmReleaseCount?: number; warnings?: string[]; }; diagnostics?: Array<{ name?: string; ok?: boolean; status?: number; command?: string; stdout?: string; stderr?: string; }>; }; type NamespaceItem = { name: string; phase?: string }; type Deployment = { namespace: string; name: string; readyReplicas: number; replicas: number; updatedReplicas: number; availableReplicas: number; generation: number; observedGeneration: number; strategy?: string; images?: string[]; revision?: string | null; conditions?: Array<{ type?: string; status?: string; reason?: string; message?: string; }>; replicaSets?: Array<{ name: string; revision?: string | null; replicas: number; readyReplicas: number; availableReplicas: number; createdAt?: string | null; images?: string[]; }>; }; type Pod = { namespace: string; name: string; phase: string; reason?: string | null; ready: boolean; readyContainers: number; totalContainers: number; restarts: number; node?: string | null; podIP?: string | null; createdAt?: string | null; containers: Array<{ name: string; image?: string; ready?: boolean; restarts?: number; state?: Record | null; }>; }; type Tab = "overview" | "deployments" | "pods" | "logs"; type LogLoadOptions = { preserveScroll?: boolean; scrollToEnd?: boolean }; function apiPath( path: string, params: Record = {}, ) { const search = new URLSearchParams(); for (const [key, value] of Object.entries(params)) { if (value !== null && value !== undefined && value !== "") search.set(key, String(value)); } const qs = search.toString(); return qs ? `${path}?${qs}` : path; } function badgeClass(value?: string | boolean) { const normalized = String(value ?? "unknown"); if (["loading"].includes(normalized)) return styles.loading; if ( [ "true", "fully_healthy", "ready_to_log_in", "ready_with_background_issues", "healthy", "Running", "Succeeded", "ready", "True", ].includes(normalized) ) return styles.ready; if ( [ "installing", "progressing", "unknown", "Pending", "ContainerCreating", "PodInitializing", "setup-queued", "not_fully_healthy", ].includes(normalized) ) return styles.installing; if ( [ "false", "not ready", "warning", "background", "degraded_background_services", "Unknown", ].includes(normalized) ) return styles.warning; return styles.failed; } function tierEntries(status: StatusResponse | null) { const source = status?.readinessTiers || status?.tiers || {}; return Object.entries(source).map(([name, value]) => { if (typeof value === "boolean") return [ name, { ready: value, status: value ? "ready" : "not ready" }, ] as const; return [ name, { ready: Boolean(value?.ready), status: value?.status || (value?.ready ? "ready" : "not ready"), }, ] as const; }); } function blockers(status: StatusResponse | null): Blocker[] { if (status?.topBlockers?.length) return status.topBlockers; return (status?.failures || []).map((failure) => ({ severity: failure.category === "background-services" ? "background" : "critical", component: failure.category, layer: failure.phase, reason: failure.suspectedCause, nextAction: failure.suggestedNextStep, loginBlocking: failure.category !== "background-services", })); } function ageFrom(date?: string | null) { if (!date) return "—"; const seconds = Math.max( 0, Math.floor((Date.now() - new Date(date).getTime()) / 1000), ); if (seconds < 60) return `${seconds}s`; if (seconds < 3600) return `${Math.floor(seconds / 60)}m`; if (seconds < 86400) return `${Math.floor(seconds / 3600)}h`; return `${Math.floor(seconds / 86400)}d`; } function elapsedLabel(seconds?: number | null) { if (seconds === null || seconds === undefined) return "elapsed time unavailable"; if (seconds < 60) return `${Math.max(0, Math.floor(seconds))}s elapsed`; if (seconds < 3600) return `${Math.floor(seconds / 60)}m elapsed`; return `${Math.floor(seconds / 3600)}h ${Math.floor((seconds % 3600) / 60)}m elapsed`; } const statusTabs = [ { value: "overview", label: "Overview", Icon: Activity }, { value: "deployments", label: "Deployments", Icon: Boxes }, { value: "pods", label: "Pods", Icon: Server }, { value: "logs", label: "Logs", Icon: ScrollText }, ] satisfies Array<{ value: Tab; label: string; Icon: typeof Activity }>; function escapeRegExp(value: string) { return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } function highlightLine(line: string, search: string) { if (!search) return line; const parts = line.split(new RegExp(`(${escapeRegExp(search)})`, "ig")); return ( <> {parts.map((part, index) => part.toLowerCase() === search.toLowerCase() ? ( {part} ) : ( part ), )} ); } function SkeletonRows({ rows = 6, columns = 6, }: { rows?: number; columns?: number; }) { return ( <> {Array.from({ length: rows }).map((_, row) => ( {Array.from({ length: columns }).map((__, col) => ( ))} ))} ); } function SkeletonBlock({ lines = 6 }: { lines?: number }) { return (
{Array.from({ length: lines }).map((_, index) => ( ))}
); } export default function StatusPage() { const [activeTab, setActiveTab] = useState("overview"); const [status, setStatus] = useState(null); const [setupMode, setSetupMode] = useState(null); const [error, setError] = useState(null); const [recovering, setRecovering] = useState(false); const [recoverMsg, setRecoverMsg] = useState(null); const [confirmRecover, setConfirmRecover] = useState(false); const [namespaces, setNamespaces] = useState([]); const [namespace, setNamespace] = useState("msp"); const [deployments, setDeployments] = useState([]); const [pods, setPods] = useState([]); const [selectedPod, setSelectedPod] = useState(""); const [selectedContainer, setSelectedContainer] = useState(""); const [deploymentFilter, setDeploymentFilter] = useState(""); const [podFilter, setPodFilter] = useState(""); const [logFilter, setLogFilter] = useState(""); const [logSearch, setLogSearch] = useState(""); const [activeMatch, setActiveMatch] = useState(0); const [logTail, setLogTail] = useState(200); const [logLines, setLogLines] = useState([]); const [logError, setLogError] = useState(null); const [loadingStatus, setLoadingStatus] = useState(true); const [loadingNamespaces, setLoadingNamespaces] = useState(true); const [loadingDeployments, setLoadingDeployments] = useState(false); const [loadingPods, setLoadingPods] = useState(false); const [loadingLogs, setLoadingLogs] = useState(false); const logPaneRef = useRef(null); const lineRefs = useRef>([]); const pendingLogScroll = useRef(null); const statusRequestInFlight = useRef(false); const statusAbortController = useRef(null); const loadStatus = useCallback(async () => { if (statusRequestInFlight.current) return; const controller = new AbortController(); statusRequestInFlight.current = true; statusAbortController.current = controller; setLoadingStatus(true); try { const response = await fetch(apiPath("/api/status"), { cache: "no-store", signal: controller.signal, }); if (response.status === 401) { window.location.reload(); return; } if (!response.ok) throw new Error("Status API unavailable."); setStatus(await response.json()); setError(null); } catch (err) { if (err instanceof DOMException && err.name === "AbortError") return; setError(err instanceof Error ? err.message : String(err)); } finally { if (statusAbortController.current === controller) { statusRequestInFlight.current = false; statusAbortController.current = null; setLoadingStatus(false); } } }, []); const recoverBootstrap = useCallback(async () => { if (recovering) return; setRecovering(true); setRecoverMsg(null); try { const response = await fetch(apiPath("/api/recover"), { method: "POST", cache: "no-store", }); const data = await response.json().catch(() => ({})); if (!response.ok) throw new Error(data.error || "Failed to trigger recovery."); setRecoverMsg(data.message || "Recovery triggered."); loadStatus(); } catch (err) { setRecoverMsg(err instanceof Error ? err.message : String(err)); } finally { setRecovering(false); } }, [recovering, loadStatus]); const loadNamespaces = useCallback(async () => { setLoadingNamespaces(true); try { const response = await fetch(apiPath("/api/k8s/namespaces"), { cache: "no-store", }); if (!response.ok) return; const data = await response.json(); setNamespaces(data.namespaces || []); } catch { /* cluster may not exist yet */ } finally { setLoadingNamespaces(false); } }, []); const loadPods = useCallback(async () => { setLoadingPods(true); try { const response = await fetch(apiPath("/api/k8s/pods", { namespace }), { cache: "no-store", }); if (!response.ok) return; const data = await response.json(); const nextPods = data.pods || []; setPods(nextPods); if (!selectedPod && nextPods.length) { setSelectedPod(nextPods[0].name); setSelectedContainer(nextPods[0].containers?.[0]?.name || ""); } } catch { /* ignore transient Kubernetes failures */ } finally { setLoadingPods(false); } }, [namespace, selectedPod]); const loadDeployments = useCallback(async () => { setLoadingDeployments(true); try { const response = await fetch( apiPath("/api/k8s/deployments", { namespace }), { cache: "no-store" }, ); if (!response.ok) return; const data = await response.json(); setDeployments(data.deployments || []); } catch { /* ignore transient Kubernetes failures */ } finally { setLoadingDeployments(false); } }, [namespace]); function applyPendingLogScroll() { const pending = pendingLogScroll.current; const pane = logPaneRef.current; if (!pending || !pane) return; if (pending.mode === "end") { pane.scrollTop = pane.scrollHeight; } else { pane.scrollTop = pane.scrollHeight - pending.previousScrollHeight + pending.previousScrollTop; } pendingLogScroll.current = null; } const loadLogs = useCallback( async (tail = logTail, options: LogLoadOptions = {}) => { if (!selectedPod) return; const pane = logPaneRef.current; const previousScrollHeight = pane?.scrollHeight || 0; const previousScrollTop = pane?.scrollTop || 0; setLoadingLogs(true); try { setLogError(null); const response = await fetch( apiPath("/api/k8s/logs", { namespace, pod: selectedPod, container: selectedContainer, tail, }), { cache: "no-store" }, ); if (!response.ok) throw new Error( (await response.json()).error || "Unable to read logs.", ); const data = await response.json(); if (options.preserveScroll) { pendingLogScroll.current = { mode: "preserve", previousScrollHeight, previousScrollTop, }; } else if (options.scrollToEnd) { pendingLogScroll.current = { mode: "end", previousScrollHeight, previousScrollTop, }; } setLogLines(data.lines || []); setLogTail(tail); window.setTimeout(applyPendingLogScroll, 50); window.setTimeout(applyPendingLogScroll, 250); } catch (err) { setLogError(err instanceof Error ? err.message : String(err)); } finally { setLoadingLogs(false); } }, [logTail, namespace, selectedContainer, selectedPod], ); useEffect(() => { let cancelled = false; async function loadSetupMode() { try { const response = await fetch(apiPath("/api/setup/config"), { cache: "no-store", }); if (response.status === 401) { window.location.reload(); return; } if (!response.ok) return; const data = (await response.json()) as SetupConfigResponse; if (cancelled) return; setSetupMode(data.mode || null); if (data.mode === "setup") { window.location.replace("/setup/"); } } catch { // Keep the status page usable if setup config is temporarily unavailable. } } loadSetupMode(); return () => { cancelled = true; }; }, []); useEffect(() => { loadStatus(); loadNamespaces(); const timer = setInterval(loadStatus, 15000); return () => { clearInterval(timer); statusAbortController.current?.abort(); }; }, [loadNamespaces, loadStatus]); useEffect(() => { if (activeTab === "deployments") loadDeployments(); if (activeTab === "pods" || activeTab === "logs") loadPods(); }, [activeTab, loadDeployments, loadPods]); useEffect(() => { if (activeTab === "logs") loadLogs(200, { scrollToEnd: true }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [activeTab, namespace, selectedPod, selectedContainer]); useEffect(() => { requestAnimationFrame(applyPendingLogScroll); }, [logLines, loadingLogs, activeTab]); const selectedPodData = pods.find((pod) => pod.name === selectedPod); const visibleDeployments = deployments.filter((deployment) => `${deployment.namespace}/${deployment.name} ${deployment.images?.join(" ")}` .toLowerCase() .includes(deploymentFilter.toLowerCase()), ); const visiblePods = pods.filter((pod) => `${pod.namespace}/${pod.name} ${pod.phase} ${pod.containers.map((c) => c.image).join(" ")}` .toLowerCase() .includes(podFilter.toLowerCase()), ); const filteredLogLines = logLines.filter( (line) => !logFilter || line.toLowerCase().includes(logFilter.toLowerCase()), ); const matchLineIndexes = useMemo( () => logSearch ? filteredLogLines.reduce((matches, line, index) => { if (line.toLowerCase().includes(logSearch.toLowerCase())) matches.push(index); return matches; }, []) : [], [filteredLogLines, logSearch], ); const searchMatches = matchLineIndexes.length; const state = status?.rollup?.state || status?.status || status?.installState?.status || "loading"; const blockerList = blockers(status); const runningOperations = status?.activeOperations || []; const primaryOperation = runningOperations[0]; const currentPhase = status?.currentPhase || status?.installState?.phase || "loading"; const nextAction = status?.rollup?.nextAction || status?.installState?.lastAction || "Waiting for the next appliance update"; useEffect(() => { setActiveMatch(0); }, [logSearch, logFilter, logLines]); function handleLogScroll() { const pane = logPaneRef.current; if (!pane || loadingLogs || pane.scrollTop > 80 || logTail >= 10000) return; loadLogs(logTail + 200, { preserveScroll: true }); } function jumpMatch(direction: 1 | -1) { if (searchMatches === 0) return; const next = (activeMatch + direction + searchMatches) % searchMatches; setActiveMatch(next); window.setTimeout(() => { lineRefs.current[matchLineIndexes[next]]?.scrollIntoView({ block: "center", }); }, 0); } return (
Alga PSA appliance

{status?.rollup?.message || error || "Reading live appliance status…"}

{state}
{setupMode === "setup" ? (
Initial setup is waiting for you

Enter the setup token, then your install code, to finish configuring this appliance.

Continue setup
) : null} {status?.setupReEditable ? (
Install code needs attention

The install code could not be redeemed — it may be invalid, expired, or already used. Re-issue a fresh code from the portal, then re-enter it to continue.

Re-enter install code
) : null} {activeTab === "overview" ? (

What is running now

{loadingStatus && !status ? ( ) : (
{primaryOperation?.component || currentPhase}

{primaryOperation?.message || nextAction}

{state}
Phase: {currentPhase} {elapsedLabel(primaryOperation?.elapsedSeconds)} Login:{" "} {status?.urls?.loginUrl ? "available" : "not available yet"} {status?.kubernetes?.podCount ?? 0} pods
{runningOperations.length > 1 ? (
    {runningOperations.slice(1, 4).map((op, index) => (
  • {op.component}: {op.message}
  • ))}
) : null}
)}

Next checkpoint

{loadingStatus && !status ? ( ) : (
Current phase
{currentPhase}
Next action
{nextAction}
Login URL
{status?.urls?.loginUrl || "Not available yet"}
Cluster objects
{status?.kubernetes?.podCount ?? 0} pods ·{" "} {status?.kubernetes?.helmReleaseCount ?? 0} releases
)}

Blockers

{loadingStatus && !status ? ( ) : blockerList.length === 0 ? (

No action-required blockers detected.

) : ( blockerList.map((blocker, index) => (
{blocker.component || blocker.layer}

{blocker.reason}

{blocker.nextAction}
)) )}

Readiness tiers

{loadingStatus && !status ? ( ) : (
{tierEntries(status).map(([name, tier]) => (
{name} {tier.ready ? "ready" : "not ready"} {tier.status}
))}
)}

Active operations

{loadingStatus && !status ? ( ) : runningOperations.length === 0 ? (

No active image pull or long-running pod operation detected.

) : ( runningOperations.map((op, index) => (
{op.component}

{op.message}

)) )}

Recovery

Re-runs the application bootstrap — database migrations, onboarding seeds, and creation of the initial tenant and admin user (only when no user exists yet). Use this if setup finished but you cannot log in because the initial account was never created.

{confirmRecover && !recovering ? ( ) : null}
{recoverMsg ? (

{recoverMsg}

) : null}

Recent Kubernetes events

{loadingStatus && !status ? ( ) : (
{(status?.recentEvents || []) .slice(-10) .reverse() .map((event, index) => (
{event.type} {event.reason} {event.namespace} · {event.involvedObject}

{event.message}

))} {(status?.recentEvents || []).length === 0 ? (

Kubernetes events are not available yet.

) : null}
)}
Advanced support diagnostics

Use this raw payload when support asks for exact appliance state.

{loadingStatus && !status ? ( ) : (
                  {JSON.stringify(status || { error }, null, 2)}
                
)}
) : null} {activeTab === "deployments" ? (
{loadingDeployments ? ( ) : visibleDeployments.length === 0 ? ( ) : ( visibleDeployments.map((deployment) => ( )) )}
Deployment Ready Revision Strategy Images History
No deployments found.
{deployment.name} {deployment.namespace} {deployment.readyReplicas}/{deployment.replicas} {deployment.revision || "—"} {deployment.strategy} {deployment.images?.map((image) => ( {image} ))}
{deployment.replicaSets?.slice(0, 4).map((rs) => ( r{rs.revision || "?"} {rs.readyReplicas}/ {rs.replicas} · {ageFrom(rs.createdAt)} ))}
) : null} {activeTab === "pods" ? (
{loadingPods ? ( ) : visiblePods.length === 0 ? ( ) : ( visiblePods.map((pod) => ( { setNamespace(pod.namespace); setSelectedPod(pod.name); setSelectedContainer(pod.containers[0]?.name || ""); setActiveTab("logs"); }} > )) )}
Pod Status Ready Restarts Node/IP Containers Action
No pods found.
{pod.name} {pod.namespace} {pod.phase} {pod.readyContainers}/{pod.totalContainers} {pod.restarts} {pod.node || "—"}
{pod.podIP || "—"}
{pod.containers.map((container) => ( {container.name} ))}
) : null} {activeTab === "logs" ? (
{ setNamespace(value); setSelectedPod(""); }} options={[ { value: "msp", label: "msp" }, ...namespaces .filter((ns) => ns.name !== "msp") .map((ns) => ({ value: ns.name, label: ns.name })), ]} /> { setSelectedPod(value); setSelectedContainer(""); }} options={pods.map((pod) => ({ value: pod.name, label: pod.name, }))} /> ({ value: container.name, label: container.name, }), )} /> tail {logTail} · scroll up for older lines
setLogFilter(event.target.value)} placeholder="Filter visible log lines" /> setLogSearch(event.target.value)} placeholder="Search and highlight" /> {searchMatches ? `${activeMatch + 1}/${searchMatches}` : "0 matches"}
{logError ?
{logError}
: null} {loadingLogs && logLines.length === 0 ? (
) : (
                {filteredLogLines.map((line, index) => {
                  const matchIndex = matchLineIndexes.indexOf(index);
                  const isMatch = matchIndex >= 0;
                  const isActive = isMatch && matchIndex === activeMatch;
                  return (
                    
{ lineRefs.current[index] = el; }} key={`${index}-${line.slice(0, 20)}`} className={`${isMatch ? styles.matchLine : ""} ${isActive ? styles.activeMatchLine : ""}`} > {highlightLine(line, logSearch)}
); })}
)}
) : null}
); } type DropdownOption = { value: string; label: string }; // Custom dropdown used instead of a native onFilter(event.target.value)} placeholder="Filter by name, image, state…" /> ); }