Secondary SDR panel: never present an unreported SDR count or run state as fact
QA (drb-qa-review) blockers: sdr_count defaulted to 1 for nodes that never sent it, so the panel claimed 'reports 1 SDR (0 spare)'. It is now None until reported, and the count is only quoted alongside a real secondary_sdr_running report. Rows read 'Not reported' instead of 'Waiting for SDR' when the node hasn't said what's running (closes #187). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
3cbb0828ae
commit
012cca402a
@@ -114,7 +114,7 @@ class MQTTHandler:
|
|||||||
"secondary_sdr_mode": payload.get("secondary_sdr_mode", "none"),
|
"secondary_sdr_mode": payload.get("secondary_sdr_mode", "none"),
|
||||||
"secondary_sdr_priority": payload.get("secondary_sdr_priority", []),
|
"secondary_sdr_priority": payload.get("secondary_sdr_priority", []),
|
||||||
"secondary_sdr_running": payload.get("secondary_sdr_running"),
|
"secondary_sdr_running": payload.get("secondary_sdr_running"),
|
||||||
"sdr_count": payload.get("sdr_count", 1),
|
"sdr_count": payload.get("sdr_count"), # None until reported, never a guessed 1
|
||||||
"enforce_override_timeout": payload.get("enforce_override_timeout", True),
|
"enforce_override_timeout": payload.get("enforce_override_timeout", True),
|
||||||
"is_overridden": False,
|
"is_overridden": False,
|
||||||
"override_system_id": None,
|
"override_system_id": None,
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ class NodeRecord(BaseModel):
|
|||||||
# checkin, which is the source of truth; set via PATCH /nodes/{id}.
|
# checkin, which is the source of truth; set via PATCH /nodes/{id}.
|
||||||
secondary_sdr_priority: List[str] = []
|
secondary_sdr_priority: List[str] = []
|
||||||
secondary_sdr_running: Optional[List[str]] = None # what the node reports actually running
|
secondary_sdr_running: Optional[List[str]] = None # what the node reports actually running
|
||||||
sdr_count: int = 1 # self-reported by the node's checkin, best-effort
|
sdr_count: Optional[int] = None # self-reported by the node's checkin; None = never reported
|
||||||
enforce_override_timeout: bool = True
|
enforce_override_timeout: bool = True
|
||||||
is_overridden: bool = False
|
is_overridden: bool = False
|
||||||
override_system_id: Optional[str] = None
|
override_system_id: Optional[str] = None
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ function rowsFrom(priority: string[]): Row[] {
|
|||||||
|
|
||||||
export function SecondarySdrPriority({ node, canEdit }: { node: NodeRecord; canEdit: boolean }) {
|
export function SecondarySdrPriority({ node, canEdit }: { node: NodeRecord; canEdit: boolean }) {
|
||||||
const priority = node.secondary_sdr_priority ?? [];
|
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 running = node.secondary_sdr_running ?? [];
|
||||||
const [rows, setRows] = useState<Row[]>(() => rowsFrom(priority));
|
const [rows, setRows] = useState<Row[]>(() => rowsFrom(priority));
|
||||||
const [dirty, setDirty] = useState(false);
|
const [dirty, setDirty] = useState(false);
|
||||||
@@ -62,8 +65,9 @@ export function SecondarySdrPriority({ node, canEdit }: { node: NodeRecord; canE
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const sdrCount = node.sdr_count ?? 1;
|
// Only quote a count the node actually sent alongside its running list; a
|
||||||
const spare = Math.max(sdrCount - 1, 0);
|
// bare sdr_count may be the Firestore default, not a report.
|
||||||
|
const sdrCount = reported ? node.sdr_count : undefined;
|
||||||
let rank = 0;
|
let rank = 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -71,13 +75,24 @@ export function SecondarySdrPriority({ node, canEdit }: { node: NodeRecord; canE
|
|||||||
<h2 className="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-1">Secondary SDRs</h2>
|
<h2 className="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-1">Secondary SDRs</h2>
|
||||||
<p className="text-xs text-gray-500 font-mono mb-3">
|
<p className="text-xs text-gray-500 font-mono mb-3">
|
||||||
OP25 always keeps its own SDR. Every other SDR runs the next enabled item, top first.
|
OP25 always keeps its own SDR. Every other SDR runs the next enabled item, top first.
|
||||||
{" "}This node reports {sdrCount} SDR{sdrCount === 1 ? "" : "s"} ({spare} spare).
|
{" "}
|
||||||
|
{sdrCount != null
|
||||||
|
? `This node reports ${sdrCount} SDR${sdrCount === 1 ? "" : "s"} (${Math.max(sdrCount - 1, 0)} spare).`
|
||||||
|
: "This node hasn't reported its SDRs yet."}
|
||||||
</p>
|
</p>
|
||||||
<div className="bg-gray-900 border border-gray-800 rounded-lg divide-y divide-gray-800 font-mono text-sm">
|
<div className="bg-gray-900 border border-gray-800 rounded-lg divide-y divide-gray-800 font-mono text-sm">
|
||||||
{rows.map((row, i) => {
|
{rows.map((row, i) => {
|
||||||
const meta = MODES.find((x) => x.mode === row.mode)!;
|
const meta = MODES.find((x) => x.mode === row.mode)!;
|
||||||
const isRunning = running.includes(row.mode);
|
const isRunning = running.includes(row.mode);
|
||||||
const state = !row.enabled ? "Off" : dirty ? "Unsaved" : isRunning ? "Running" : "Waiting for SDR";
|
const state = !row.enabled
|
||||||
|
? "Off"
|
||||||
|
: dirty
|
||||||
|
? "Unsaved"
|
||||||
|
: !reported
|
||||||
|
? "Not reported"
|
||||||
|
: isRunning
|
||||||
|
? "Running"
|
||||||
|
: "Waiting for SDR";
|
||||||
return (
|
return (
|
||||||
<div key={row.mode} className="flex items-center gap-3 px-4 py-2.5">
|
<div key={row.mode} className="flex items-center gap-3 px-4 py-2.5">
|
||||||
<span className="w-4 text-right text-gray-600 text-xs">{row.enabled ? ++rank : ""}</span>
|
<span className="w-4 text-right text-gray-600 text-xs">{row.enabled ? ++rank : ""}</span>
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export interface NodeRecord {
|
|||||||
secondary_sdr_priority?: string[];
|
secondary_sdr_priority?: string[];
|
||||||
/** What the node's last checkin reported actually running. */
|
/** What the node's last checkin reported actually running. */
|
||||||
secondary_sdr_running?: string[] | null;
|
secondary_sdr_running?: string[] | null;
|
||||||
sdr_count?: number;
|
sdr_count?: number | null; // null = never reported
|
||||||
enforce_override_timeout?: boolean;
|
enforce_override_timeout?: boolean;
|
||||||
is_overridden?: boolean;
|
is_overridden?: boolean;
|
||||||
override_system_id?: string | null;
|
override_system_id?: string | null;
|
||||||
|
|||||||
Reference in New Issue
Block a user