feat(manager): expose safe VPS edge health
This commit is contained in:
@@ -287,6 +287,9 @@ export function createControlCoreApp({
|
||||
commandTransport: typedCommandRuntime
|
||||
? "typed-service-ping-v1"
|
||||
: "disabled",
|
||||
edgeChannelStatus: edgeChannelStatusProvider
|
||||
? edgeChannelStatusProvider()
|
||||
: null,
|
||||
},
|
||||
);
|
||||
return writeJson(response, 200, { ok: true, workspace });
|
||||
|
||||
@@ -50,7 +50,7 @@ export async function getDeviceProjectWorkspace(
|
||||
client,
|
||||
actor,
|
||||
projectId,
|
||||
{ commandTransport = "disabled" } = {},
|
||||
{ commandTransport = "disabled", edgeChannelStatus = null } = {},
|
||||
) {
|
||||
const project = await findProjectWithCapability(
|
||||
client,
|
||||
@@ -204,7 +204,8 @@ export async function getDeviceProjectWorkspace(
|
||||
);
|
||||
const edges = await client.query(
|
||||
`select de.id, de.edge_key, de.display_name, de.deployment_ref,
|
||||
de.lifecycle_state, de.created_at, de.updated_at
|
||||
de.lifecycle_state, de.channel_lifecycle_state,
|
||||
de.channel_generation_ref, de.created_at, de.updated_at
|
||||
from device_edges de
|
||||
where $2::boolean
|
||||
or exists (
|
||||
@@ -333,7 +334,10 @@ export async function getDeviceProjectWorkspace(
|
||||
adapterPackages: adapterPackages.rows.map(adapterPackageView),
|
||||
adapterVersions: adapterVersions.rows.map(adapterVersionView),
|
||||
modelProfiles: modelProfiles.rows.map(modelProfileView),
|
||||
edges: edges.rows.map(edgeView),
|
||||
edges: edges.rows.map((row) => edgeView(
|
||||
row,
|
||||
edgeChannelRuntime(edgeChannelStatus, `edge:${row.id}`),
|
||||
)),
|
||||
routes: routes.rows.map(routeView),
|
||||
sessions: sessions.rows.map(sessionView),
|
||||
bindings: bindings.rows.map(bindingView),
|
||||
@@ -496,18 +500,41 @@ function modelProfileView(row) {
|
||||
};
|
||||
}
|
||||
|
||||
function edgeView(row) {
|
||||
function edgeView(row, runtime) {
|
||||
const channelLifecycleState = row.channel_lifecycle_state ?? "disabled";
|
||||
return {
|
||||
edgeRef: `edge:${row.id}`,
|
||||
edgeKey: row.edge_key,
|
||||
displayName: row.display_name,
|
||||
deploymentRef: row.deployment_ref ?? null,
|
||||
lifecycleState: row.lifecycle_state,
|
||||
channel: {
|
||||
lifecycleState: channelLifecycleState,
|
||||
generationRef: row.channel_generation_ref ?? null,
|
||||
runtimeState: channelLifecycleState === "active"
|
||||
? runtime?.channel ?? "unobserved"
|
||||
: channelLifecycleState,
|
||||
lastErrorCode: runtime?.lastErrorCode ?? null,
|
||||
},
|
||||
createdAt: toIso(row.created_at),
|
||||
updatedAt: toIso(row.updated_at),
|
||||
};
|
||||
}
|
||||
|
||||
function edgeChannelRuntime(status, edgeRef) {
|
||||
if (!status || !Array.isArray(status.edges)) return null;
|
||||
const value = status.edges.find((item) => item?.edgeRegistrationId === edgeRef);
|
||||
if (!value) return null;
|
||||
return {
|
||||
channel: ["accepted", "connecting", "absent"].includes(value.channel)
|
||||
? value.channel
|
||||
: "unobserved",
|
||||
lastErrorCode: typeof value.lastErrorCode === "string"
|
||||
? value.lastErrorCode.slice(0, 128)
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
function routeView(row) {
|
||||
return {
|
||||
routeRef: `route:${row.id}`,
|
||||
|
||||
@@ -347,14 +347,23 @@ test("project query is service-authenticated and forwards only the trusted actor
|
||||
test("project workspace query accepts only a canonical project path", async () => {
|
||||
const projectId = "11111111-1111-4111-8111-111111111111";
|
||||
let queried;
|
||||
const edgeChannels = {
|
||||
enabled: true,
|
||||
edges: [{
|
||||
edgeRegistrationId: "edge:bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
|
||||
channel: "accepted",
|
||||
lastErrorCode: null,
|
||||
}],
|
||||
};
|
||||
const runtime = await startTestServer({
|
||||
managementApiEnabled: true,
|
||||
managementToken,
|
||||
edgeChannelStatusProvider: () => edgeChannels,
|
||||
repository: {
|
||||
health: async () => "ready",
|
||||
executeManagementCommand: async () => ({ replayed: false, result: {} }),
|
||||
getProjectWorkspace: async (actor, id) => {
|
||||
queried = { actor, id };
|
||||
getProjectWorkspace: async (actor, id, options) => {
|
||||
queried = { actor, id, options };
|
||||
return { project: { projectRef: `project:${id}` }, devices: [] };
|
||||
},
|
||||
},
|
||||
@@ -367,6 +376,7 @@ test("project workspace query accepts only a canonical project path", async () =
|
||||
assert.equal(response.status, 200);
|
||||
assert.equal((await response.json()).workspace.devices.length, 0);
|
||||
assert.equal(queried.id, projectId);
|
||||
assert.equal(queried.options.edgeChannelStatus, edgeChannels);
|
||||
|
||||
const invalid = await fetch(
|
||||
`${runtime.baseUrl}/internal/v1/query/projects/not-a-project/workspace`,
|
||||
|
||||
@@ -53,7 +53,15 @@ test("project list applies direct-grant precedence and returns bounded summaries
|
||||
|
||||
test("project workspace returns only masked identity projections", async () => {
|
||||
const client = workspaceClient();
|
||||
const workspace = await getDeviceProjectWorkspace(client, actor, projectId);
|
||||
const workspace = await getDeviceProjectWorkspace(client, actor, projectId, {
|
||||
edgeChannelStatus: {
|
||||
edges: [{
|
||||
edgeRegistrationId: "edge:bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
|
||||
channel: "accepted",
|
||||
lastErrorCode: null,
|
||||
}],
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(workspace.project.projectRef, `project:${projectId}`);
|
||||
assert.equal(workspace.devices[0].identifier.masked, "***********0001");
|
||||
@@ -66,6 +74,12 @@ test("project workspace returns only masked identity projections", async () => {
|
||||
assert.equal(workspace.adapterPackages[0].packageKey, "generic-tracker");
|
||||
assert.equal(workspace.modelProfiles[0].modelProfileRef, "vendor.model.v1");
|
||||
assert.equal(workspace.routes[0].activeSessionCount, 1);
|
||||
assert.deepEqual(workspace.edges[0].channel, {
|
||||
lifecycleState: "active",
|
||||
generationRef: "channel-generation:1",
|
||||
runtimeState: "accepted",
|
||||
lastErrorCode: null,
|
||||
});
|
||||
assert.equal(workspace.sessions[0].frameCount, 12);
|
||||
assert.equal(workspace.bindings[0].lifecycleState, "pending_external_approval");
|
||||
assert.equal(workspace.configurationRevisions[0].revisionNumber, 1);
|
||||
@@ -103,6 +117,10 @@ test("project read source never selects identifier digests or credential refs",
|
||||
source,
|
||||
/\b(?:identifier_digest|expected_identifier_digest|credential_ref|parameters_digest|parameters_projection|transport_message_ref|external_approval_ref|external_approval_digest)\b/,
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
source,
|
||||
/\b(?:channel_endpoint|channel_servername|channel_trust_bundle_ref|channel_certificate_identities)\b/,
|
||||
);
|
||||
assert.doesNotMatch(source, /\b(?:dae\.payload|dcr\.configuration)\b/);
|
||||
});
|
||||
|
||||
@@ -167,6 +185,8 @@ function workspaceClient({ queries = [] } = {}) {
|
||||
display_name: "Edge one",
|
||||
deployment_ref: "deployment:edge-one",
|
||||
lifecycle_state: "active",
|
||||
channel_lifecycle_state: "active",
|
||||
channel_generation_ref: "channel-generation:1",
|
||||
created_at: timestamp,
|
||||
updated_at: timestamp,
|
||||
}] };
|
||||
|
||||
Reference in New Issue
Block a user