merge: 修复 HWPOD 控制台 L0/L1 问题
This commit is contained in:
@@ -28,7 +28,7 @@ test("typed topology covers multi-device, offline, unconfigured, invalid, mismat
|
||||
runtimeKind: "python-tkinter-gui",
|
||||
installed: true,
|
||||
desktopVisible: true,
|
||||
capabilities: ["workspace.ls"],
|
||||
capabilities: ["workspace.ls", "node.health"],
|
||||
inFlightCount: 1,
|
||||
maxInFlight: 1,
|
||||
lastDiagnostic: { level: "ERROR", source: "ws", message: "connect failed: Bearer private-token?token=still-secret" },
|
||||
@@ -44,15 +44,24 @@ test("typed topology covers multi-device, offline, unconfigured, invalid, mismat
|
||||
assert.equal(devicePayload.summary.nodeCount, 3);
|
||||
assert.equal(devicePayload.summary.deviceCount, 3);
|
||||
assert.equal(devicePayload.summary.invalidSpecCount, 1);
|
||||
assert.equal(devicePayload.items.find((item: any) => item.hwpodId === "hwpod-a").status, "busy");
|
||||
assert.equal(devicePayload.items.find((item: any) => item.hwpodId === "hwpod-b").status, "capability-mismatch");
|
||||
assert.deepEqual(devicePayload.items.find((item: any) => item.hwpodId === "hwpod-b").missingCapabilities, ["debug.download"]);
|
||||
const deviceA = devicePayload.items.find((item: any) => item.hwpodId === "hwpod-a");
|
||||
const deviceB = devicePayload.items.find((item: any) => item.hwpodId === "hwpod-b");
|
||||
assert.equal(deviceA.status, "online");
|
||||
assert.equal(deviceA.busy, true);
|
||||
assert.equal(deviceA.available, false);
|
||||
assert.equal(deviceB.status, "online");
|
||||
assert.equal(deviceB.capabilityMismatch, true);
|
||||
assert.deepEqual(deviceB.missingCapabilities, ["debug.download"]);
|
||||
assert.deepEqual(deviceB.capabilityCoverage, { matched: 1, declared: 2, actual: 2, missing: 1, extra: 1 });
|
||||
assert.equal(deviceB.blockers.some((item: any) => item.code === "hwpod_capability_mismatch"), true);
|
||||
assert.equal(devicePayload.items.find((item: any) => item.hwpodId === "hwpod-c").status, "offline");
|
||||
|
||||
const nodePayload = buildHwpodTopologyReadModel(specs, registry, { resource: "node", observedAt: "2026-07-13T01:00:00.000Z" });
|
||||
const nodeA = nodePayload.items.find((item: any) => item.nodeId === "node-a");
|
||||
assert.equal(nodeA.deviceCount, 2);
|
||||
assert.equal(nodeA.status, "online");
|
||||
assert.equal(nodeA.inFlight.busy, true);
|
||||
assert.deepEqual(nodeA.capabilityCoverage, { matched: 1, declared: 2, actual: 2, missing: 1, extra: 1 });
|
||||
assert.equal(nodeA.diagnostic.message.includes("private-token"), false);
|
||||
assert.equal(nodeA.diagnostic.message.includes("still-secret"), false);
|
||||
assert.equal(nodeA.diagnostic.message.includes("[REDACTED]"), true);
|
||||
@@ -61,7 +70,8 @@ test("typed topology covers multi-device, offline, unconfigured, invalid, mismat
|
||||
assert.equal(nodeA.readiness.find((stage: any) => stage.id === "desktop-visible").nextAction, null);
|
||||
assert.equal(nodeA.readiness.find((stage: any) => stage.id === "hwpod-ready").nextAction.entrypoint, "hwpod-topology");
|
||||
assert.equal(nodePayload.items.find((item: any) => item.nodeId === "node-b").readiness.find((stage: any) => stage.id === "workspace-ready").blocker.code, "hwpod_node_spec_missing");
|
||||
assert.equal(nodePayload.items.find((item: any) => item.nodeId === "node-b").status, "unconfigured");
|
||||
assert.equal(nodePayload.items.find((item: any) => item.nodeId === "node-b").status, "online");
|
||||
assert.equal(nodePayload.summary.unconfiguredNodeCount, 1);
|
||||
assert.equal(nodePayload.items.find((item: any) => item.nodeId === "node-c").status, "offline");
|
||||
assert.equal(nodePayload.invalidSpecs[0].blocker.code, "invalid_hwpod_spec");
|
||||
});
|
||||
@@ -74,11 +84,11 @@ test("typed topology keeps server-side status filtering and cursor bounded", ()
|
||||
{ nodeId: "node-b", status: "online", capabilities: ["workspace.ls"], inFlightCount: 0, maxInFlight: 1 }
|
||||
]
|
||||
};
|
||||
const first = buildHwpodTopologyReadModel(specs, registry, { resource: "device", status: "available", limit: 1 });
|
||||
const first = buildHwpodTopologyReadModel(specs, registry, { resource: "device", status: "online", limit: 1 });
|
||||
assert.equal(first.items.length, 1);
|
||||
assert.equal(first.page.total, 2);
|
||||
assert.ok(first.page.nextCursor);
|
||||
const second = buildHwpodTopologyReadModel(specs, registry, { resource: "device", status: "available", limit: 1, cursor: first.page.nextCursor });
|
||||
const second = buildHwpodTopologyReadModel(specs, registry, { resource: "device", status: "online", limit: 1, cursor: first.page.nextCursor });
|
||||
assert.equal(second.items.length, 1);
|
||||
assert.notEqual(first.items[0].hwpodId, second.items[0].hwpodId);
|
||||
assert.equal(second.page.previousCursor, "b2Zmc2V0OjA");
|
||||
|
||||
@@ -52,9 +52,9 @@ export function buildHwpodTopologyReadModel(specs: any[], registrySnapshot: any
|
||||
nodeCount: nodes.length,
|
||||
onlineNodeCount: nodes.filter((node) => node.online).length,
|
||||
offlineNodeCount: nodes.filter((node) => !node.online).length,
|
||||
unconfiguredNodeCount: nodes.filter((node) => node.status === "unconfigured").length,
|
||||
unconfiguredNodeCount: nodes.filter((node) => node.deviceCount === 0).length,
|
||||
deviceCount: devices.length,
|
||||
availableDeviceCount: devices.filter((device) => device.status === "available").length,
|
||||
availableDeviceCount: devices.filter((device) => device.available).length,
|
||||
busyDeviceCount: devices.filter((device) => device.busy).length,
|
||||
capabilityMismatchCount: devices.filter((device) => device.capabilityMismatch).length,
|
||||
invalidSpecCount: invalidSpecs.length
|
||||
@@ -105,12 +105,14 @@ function buildDevice(spec: any, node: any, observedAt: string) {
|
||||
const declaredCapabilities = declaredCapabilitiesFor(body);
|
||||
const actualCapabilities = node?.actualCapabilities ?? [];
|
||||
const missingCapabilities = node ? declaredCapabilities.filter((capability) => !actualCapabilities.includes(capability)) : declaredCapabilities;
|
||||
const capabilityCoverage = buildCapabilityCoverage(declaredCapabilities, actualCapabilities);
|
||||
const availability = availabilitySummary(spec.availability);
|
||||
const online = Boolean(node?.online);
|
||||
const busy = Boolean(node?.busy);
|
||||
const capabilityMismatch = online && missingCapabilities.length > 0;
|
||||
const blockers = deviceBlockers({ online, busy, capabilityMismatch, missingCapabilities, availability });
|
||||
const status = deviceStatus({ online, busy, capabilityMismatch, availability });
|
||||
const status = online ? "online" : "offline";
|
||||
const available = online && !busy && !capabilityMismatch && availability.ok === true;
|
||||
return {
|
||||
resourceType: "hwpod",
|
||||
hwpodId: text(spec.hwpodId ?? spec.name),
|
||||
@@ -120,12 +122,13 @@ function buildDevice(spec: any, node: any, observedAt: string) {
|
||||
nodeName: node?.name ?? text(spec.nodeId),
|
||||
status,
|
||||
online,
|
||||
available: status === "available",
|
||||
available,
|
||||
busy,
|
||||
capabilityMismatch,
|
||||
declaredCapabilities,
|
||||
actualCapabilities,
|
||||
missingCapabilities,
|
||||
capabilityCoverage,
|
||||
elements: hwpodElements(body),
|
||||
availability,
|
||||
operation: node?.latestOperation?.hwpodId === text(spec.hwpodId ?? spec.name) ? node.latestOperation : null,
|
||||
@@ -144,11 +147,12 @@ function buildNode(nodeId: string, onlineNode: any, devices: any[], observedAt:
|
||||
const declaredCapabilities = uniqueStrings(devices.flatMap((device) => device.declaredCapabilities));
|
||||
const actualCapabilities = onlineNode?.actualCapabilities ?? [];
|
||||
const missingCapabilities = onlineNode ? declaredCapabilities.filter((capability) => !actualCapabilities.includes(capability)) : declaredCapabilities;
|
||||
const capabilityCoverage = buildCapabilityCoverage(declaredCapabilities, actualCapabilities);
|
||||
const busy = Boolean(onlineNode?.busy);
|
||||
const online = Boolean(onlineNode?.online);
|
||||
const capabilityMismatch = online && missingCapabilities.length > 0;
|
||||
const hasSpec = devices.length > 0;
|
||||
const status = !online ? "offline" : !hasSpec ? "unconfigured" : capabilityMismatch ? "capability-mismatch" : busy ? "busy" : "online";
|
||||
const status = online ? "online" : "offline";
|
||||
const blockers = nodeBlockers({ online, hasSpec, busy, capabilityMismatch, missingCapabilities, devices });
|
||||
const workspaceReady = devices.some((device) => device.availability.ok === true);
|
||||
const hwpodReady = devices.some((device) => device.available);
|
||||
@@ -175,6 +179,7 @@ function buildNode(nodeId: string, onlineNode: any, devices: any[], observedAt:
|
||||
declaredCapabilities,
|
||||
actualCapabilities,
|
||||
missingCapabilities,
|
||||
capabilityCoverage,
|
||||
capabilityMismatch,
|
||||
inFlight: {
|
||||
count: onlineNode?.inFlightCount ?? 0,
|
||||
@@ -244,13 +249,16 @@ function availabilitySummary(value: any) {
|
||||
};
|
||||
}
|
||||
|
||||
function deviceStatus(input: any) {
|
||||
if (!input.online) return "offline";
|
||||
if (input.capabilityMismatch) return "capability-mismatch";
|
||||
if (input.busy) return "busy";
|
||||
if (input.availability.ok === false) return "workspace-blocked";
|
||||
if (input.availability.ok === true) return "available";
|
||||
return "unprobed";
|
||||
function buildCapabilityCoverage(declaredCapabilities: string[], actualCapabilities: string[]) {
|
||||
const matched = declaredCapabilities.filter((capability) => actualCapabilities.includes(capability)).length;
|
||||
const extra = actualCapabilities.filter((capability) => !declaredCapabilities.includes(capability)).length;
|
||||
return {
|
||||
matched,
|
||||
declared: declaredCapabilities.length,
|
||||
actual: actualCapabilities.length,
|
||||
missing: declaredCapabilities.length - matched,
|
||||
extra
|
||||
};
|
||||
}
|
||||
|
||||
function deviceBlockers(input: any) {
|
||||
|
||||
Reference in New Issue
Block a user