fix(cli): nodes list uses node.list as primary source to fix k8s pairing store inconsistency
`nodes list` called node.pair.list as the primary data source, which returns an empty result in environments where the pairing store (file/DB) is not shared between the gateway and the node host (e.g. k8s volumes, Docker). This caused `nodes list` to show 'Paired: 0' while `nodes status` (which calls node.list) correctly showed the paired node. Fix: make `nodes list` use node.list as the authoritative source and node.pair.list as a secondary enrichment source. This aligns both commands to show consistent results regardless of pairing store availability. Fixes: #50847
This commit is contained in:
parent
002011527c
commit
6f42fb105f
@ -304,54 +304,89 @@ export function registerNodesStatusCommands(nodes: Command) {
|
||||
await runNodesCommand("list", async () => {
|
||||
const connectedOnly = Boolean(opts.connected);
|
||||
const sinceMs = parseSinceMs(opts.lastConnected, "Invalid --last-connected");
|
||||
const result = await callGatewayCli("node.pair.list", opts, {});
|
||||
const { pending, paired } = parsePairingList(result);
|
||||
// Call both sources in parallel:
|
||||
// - node.list: device pairing (always returns known nodes)
|
||||
// - node.pair.list: gateway-owned node-pairing store — needed because `nodes approve`
|
||||
// writes here and those nodes may not appear in the device pairing list (k8s volumes,
|
||||
// separate data directories). Showing both sources fixes the "Paired: 0" bug.
|
||||
const [nodeListResult, pairingResult] = await Promise.all([
|
||||
callGatewayCli("node.list", opts, {}),
|
||||
callGatewayCli("node.pair.list", opts, {}),
|
||||
]);
|
||||
const { pending, paired: pairingPaired } = parsePairingList(pairingResult);
|
||||
const deviceNodes = parseNodeList(nodeListResult);
|
||||
|
||||
// Build lookup maps keyed by nodeId
|
||||
const pairingById = new Map(pairingPaired.map((n) => [n.nodeId, n]));
|
||||
const deviceById = new Map(deviceNodes.map((n) => [n.nodeId, n]));
|
||||
|
||||
// Merge both sources: start with device nodes, then add pairing-only nodes
|
||||
const allNodesMap = new Map(deviceById);
|
||||
for (const pNode of pairingPaired) {
|
||||
if (!allNodesMap.has(pNode.nodeId)) {
|
||||
allNodesMap.set(pNode.nodeId, {
|
||||
nodeId: pNode.nodeId,
|
||||
displayName: pNode.displayName,
|
||||
platform: pNode.platform,
|
||||
version: pNode.version,
|
||||
coreVersion: pNode.coreVersion,
|
||||
uiVersion: pNode.uiVersion,
|
||||
remoteIp: pNode.remoteIp,
|
||||
paired: true,
|
||||
connected: false,
|
||||
connectedAtMs: pNode.lastConnectedAtMs,
|
||||
});
|
||||
}
|
||||
}
|
||||
const allNodes = Array.from(allNodesMap.values());
|
||||
|
||||
const { heading, muted, warn } = getNodesTheme();
|
||||
const tableWidth = getTerminalTableWidth();
|
||||
const now = Date.now();
|
||||
const hasFilters = connectedOnly || sinceMs !== undefined;
|
||||
const pendingRows = hasFilters ? [] : pending;
|
||||
const connectedById = hasFilters
|
||||
? new Map(
|
||||
parseNodeList(await callGatewayCli("node.list", opts, {})).map((node) => [
|
||||
node.nodeId,
|
||||
node,
|
||||
]),
|
||||
)
|
||||
: null;
|
||||
const filteredPaired = paired.filter((node) => {
|
||||
if (connectedOnly) {
|
||||
const live = connectedById?.get(node.nodeId);
|
||||
if (!live?.connected) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const filteredPaired = allNodes.filter((node) => {
|
||||
if (!node.paired && !pairingById.has(node.nodeId)) return false;
|
||||
if (connectedOnly && !node.connected) return false;
|
||||
if (sinceMs !== undefined) {
|
||||
const live = connectedById?.get(node.nodeId);
|
||||
const pData = pairingById.get(node.nodeId);
|
||||
const lastConnectedAtMs =
|
||||
typeof node.lastConnectedAtMs === "number"
|
||||
? node.lastConnectedAtMs
|
||||
: typeof live?.connectedAtMs === "number"
|
||||
? live.connectedAtMs
|
||||
typeof pData?.lastConnectedAtMs === "number"
|
||||
? pData.lastConnectedAtMs
|
||||
: typeof node.connectedAtMs === "number"
|
||||
? node.connectedAtMs
|
||||
: undefined;
|
||||
if (typeof lastConnectedAtMs !== "number") {
|
||||
return false;
|
||||
}
|
||||
if (now - lastConnectedAtMs > sinceMs) {
|
||||
return false;
|
||||
}
|
||||
if (typeof lastConnectedAtMs !== "number") return false;
|
||||
if (now - lastConnectedAtMs > sinceMs) return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
const totalPairedCount = allNodes.filter(
|
||||
(n) => n.paired || pairingById.has(n.nodeId),
|
||||
).length;
|
||||
const filteredLabel =
|
||||
hasFilters && filteredPaired.length !== paired.length ? ` (of ${paired.length})` : "";
|
||||
hasFilters && filteredPaired.length !== totalPairedCount
|
||||
? ` (of ${totalPairedCount})`
|
||||
: "";
|
||||
defaultRuntime.log(
|
||||
`Pending: ${pendingRows.length} · Paired: ${filteredPaired.length}${filteredLabel}`,
|
||||
);
|
||||
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(
|
||||
JSON.stringify({ pending: pendingRows, paired: filteredPaired }, null, 2),
|
||||
JSON.stringify(
|
||||
{
|
||||
pending: pendingRows,
|
||||
paired: filteredPaired.map((n) => ({
|
||||
...n,
|
||||
...(pairingById.get(n.nodeId) ?? {}),
|
||||
})),
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
@ -370,12 +405,12 @@ export function registerNodesStatusCommands(nodes: Command) {
|
||||
|
||||
if (filteredPaired.length > 0) {
|
||||
const pairedRows = filteredPaired.map((n) => {
|
||||
const live = connectedById?.get(n.nodeId);
|
||||
const pData = pairingById.get(n.nodeId);
|
||||
const lastConnectedAtMs =
|
||||
typeof n.lastConnectedAtMs === "number"
|
||||
? n.lastConnectedAtMs
|
||||
: typeof live?.connectedAtMs === "number"
|
||||
? live.connectedAtMs
|
||||
typeof pData?.lastConnectedAtMs === "number"
|
||||
? pData.lastConnectedAtMs
|
||||
: typeof n.connectedAtMs === "number"
|
||||
? n.connectedAtMs
|
||||
: undefined;
|
||||
return {
|
||||
Node: n.displayName?.trim() ? n.displayName.trim() : n.nodeId,
|
||||
@ -402,6 +437,7 @@ export function registerNodesStatusCommands(nodes: Command) {
|
||||
}).trimEnd(),
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user