"use client"; import { useEffect, useState } from "react"; import { c2api } from "@/lib/c2api"; import type { NodeRecord } from "@/lib/types"; // node-26#9. OP25 always keeps its own SDR; every other SDR on the node runs // the next enabled item here, top first — so a 3-SDR node runs both. const MODES: { mode: string; name: string; hint: string }[] = [ { mode: "adsb", name: "ADS-B", hint: "Aircraft · 1090 MHz" }, { mode: "ais", name: "AIS", hint: "Vessels · 162 MHz" }, ]; type Row = { mode: string; enabled: boolean }; function rowsFrom(priority: string[]): Row[] { return [ ...priority.filter((m) => MODES.some((x) => x.mode === m)).map((mode) => ({ mode, enabled: true })), ...MODES.filter((x) => !priority.includes(x.mode)).map((x) => ({ mode: x.mode, enabled: false })), ]; } export function SecondarySdrPriority({ node, canEdit }: { node: NodeRecord; canEdit: boolean }) { const priority = node.secondary_sdr_priority ?? []; // null/absent = the node has never reported (older firmware, container down // at checkin): unknown, not "nothing running" (server-26#187). const reported = node.secondary_sdr_running != null; const running = node.secondary_sdr_running ?? []; const [rows, setRows] = useState(() => rowsFrom(priority)); const [dirty, setDirty] = useState(false); const [saving, setSaving] = useState(false); const [message, setMessage] = useState(null); // Follow the node's live checkin unless there are unsaved edits. const priorityKey = priority.join(","); useEffect(() => { if (!dirty) setRows(rowsFrom(priorityKey ? priorityKey.split(",") : [])); }, [priorityKey, dirty]); function edit(next: Row[]) { setRows(next); setDirty(true); setMessage(null); } function move(i: number, delta: number) { const next = [...rows]; [next[i], next[i + delta]] = [next[i + delta], next[i]]; edit(next); } async function save() { setSaving(true); setMessage(null); try { await c2api.updateNode(node.node_id, { secondary_sdr_priority: rows.filter((r) => r.enabled).map((r) => r.mode), }); setDirty(false); setMessage("Sent to the node. Status updates when it checks in."); } catch (err) { setMessage(err instanceof Error ? err.message : "Save failed."); } finally { setSaving(false); } } // Only quote a count the node actually sent alongside its running list; a // bare sdr_count may be the Firestore default, not a report. const sdrCount = reported ? node.sdr_count : undefined; let rank = 0; return (

Secondary SDRs

OP25 always keeps its own SDR. Every other SDR runs the next enabled item, top first. {" "} {sdrCount != null ? `This node reports ${sdrCount} SDR${sdrCount === 1 ? "" : "s"} (${Math.max(sdrCount - 1, 0)} spare).` : "This node hasn't reported its SDRs yet."}

{rows.map((row, i) => { const meta = MODES.find((x) => x.mode === row.mode)!; const isRunning = running.includes(row.mode); const state = !row.enabled ? "Off" : dirty ? "Unsaved" : !reported ? "Not reported" : isRunning ? "Running" : "Waiting for SDR"; return (
{row.enabled ? ++rank : ""} edit(rows.map((r, j) => (j === i ? { ...r, enabled: e.target.checked } : r)))} className="rounded bg-gray-800 border-gray-700 text-indigo-600 focus:ring-indigo-500 focus:ring-offset-gray-900" />
{meta.name}
{meta.hint}
{canEdit && (
)} {state}
); })}
{canEdit && (
{message && {message}}
)}
); }