feat(device-manager): add full device profile workspace

This commit is contained in:
Codex
2026-08-13 11:38:26 +03:00
parent bbb50e06b4
commit 19f0d97e23
8 changed files with 1416 additions and 15 deletions
@@ -80,7 +80,7 @@ export function createDeviceCoreClient({ baseUrl, token, fetchImpl = fetch } = {
};
}
export function createLocalPreviewDeviceCore() {
export function createLocalPreviewDeviceCore({ fixture = null } = {}) {
const ownerScopes = new Map();
const projects = new Map();
const collections = new Map();
@@ -90,6 +90,8 @@ export function createLocalPreviewDeviceCore() {
const edges = new Map();
const routes = new Map();
const enrollments = new Map();
const devices = new Map();
const sessions = new Map();
const bindings = new Map();
const grants = new Map();
const configurationRevisions = new Map();
@@ -121,7 +123,11 @@ export function createLocalPreviewDeviceCore() {
.filter((collection) => collection.projectRef === project.projectRef);
return {
...project,
counts: { devices: 0, collections: projectCollections.length, discoveries: 0 },
counts: {
devices: projectValues(devices, project.projectRef).length,
collections: projectCollections.length,
discoveries: 0,
},
};
}
@@ -130,7 +136,8 @@ export function createLocalPreviewDeviceCore() {
if (!project) throw serviceError("device_project_not_found", 404);
return {
project: projectSummary(project),
devices: [],
devices: projectValues(devices, projectRef)
.map(({ projectRef: _projectRef, ...device }) => device),
discoveries: [],
enrollments: projectValues(enrollments, projectRef),
collections: projectValues(collections, projectRef)
@@ -140,7 +147,8 @@ export function createLocalPreviewDeviceCore() {
modelProfiles: [...modelProfiles.values()],
edges: [...edges.values()],
routes: projectValues(routes, projectRef),
sessions: [],
sessions: projectValues(sessions, projectRef)
.map(({ projectRef: _projectRef, ...session }) => session),
bindings: projectValues(bindings, projectRef),
configurationRevisions: projectValues(configurationRevisions, projectRef),
configurationStates: projectValues(configurationStates, projectRef),
@@ -156,6 +164,20 @@ export function createLocalPreviewDeviceCore() {
};
}
if (fixture === "arusnavi-b2") seedArusnaviB2Preview({
ownerScopes,
projects,
devices,
modelProfiles,
edges,
routes,
sessions,
configurationStates,
});
else if (fixture != null && fixture !== "") {
throw serviceError("device_manager_preview_fixture_invalid", 400);
}
return {
configured: true,
async listProjects() {
@@ -552,6 +574,8 @@ export function createLocalPreviewDeviceCore() {
edges,
routes,
enrollments,
devices,
sessions,
bindings,
grants,
configurationRevisions,
@@ -572,6 +596,160 @@ function requirePlatformOwner(actor) {
}
}
function seedArusnaviB2Preview({
ownerScopes,
projects,
devices,
modelProfiles,
edges,
routes,
sessions,
configurationStates,
}) {
const timestamp = new Date().toISOString();
const ownerScopeRef = "owner-scope:78da71d5-f48f-4de0-8e47-729f6d644151";
const projectRef = "project:ad7b357c-c7ac-4bf8-a638-c7f956e9aa71";
const deviceRef = "device:b6a55921-7888-44b5-a93e-241aa2fdd3d7";
const edgeRef = "edge:73da0c42-a641-4559-b8f7-23509b60bfe9";
const routeRef = "route:fef9b7a0-a462-4d68-9991-af026203368b";
const sessionRef = "session:57ead610-47de-45f7-a42d-fbe4fa0aba38";
const scope = {
ownerScopeRef,
scopeKind: "personal",
ownerRef: "user:local-device-admin",
displayName: "Local Device Admin",
lifecycleState: "active",
};
ownerScopes.set("personal:user:local-device-admin", scope);
projects.set(projectRef, {
projectRef,
projectKey: "arusnavi-b2-preview",
name: "ARUSNAVI B2 preview",
description: "Локальная визуальная фикстура без реальных идентификаторов и команд",
lifecycleState: "active",
ownerScope: scope,
access: { projectRole: "owner", capabilities: ownerCapabilities },
counts: { devices: 1, collections: 0, discoveries: 0 },
createdAt: timestamp,
updatedAt: timestamp,
});
modelProfiles.set("arusnavi.b2.internal.v1", {
modelProfileRef: "arusnavi.b2.internal.v1",
adapterVersionRef: null,
schemaVersion: "1.0.0",
vendor: "ARUSNAVI",
model: "B2",
deviceType: "tracker",
protocol: "INTERNAL",
schemaArtifactRef: "schema:arusnavi.b2.internal.v1",
profileDigest: null,
capabilities: ["telemetry", "configuration", "commands"],
lifecycleState: "active",
createdAt: timestamp,
updatedAt: timestamp,
});
edges.set(edgeRef, {
edgeRef,
edgeKey: "preview-edge",
displayName: "Preview VPS edge",
deploymentRef: "deployment:preview",
lifecycleState: "active",
createdAt: timestamp,
updatedAt: timestamp,
});
routes.set(routeRef, {
routeRef,
projectRef,
routeKey: "preview-b2-route",
displayName: "B2 direct preview",
edgeRef,
edgeName: "Preview VPS edge",
modelProfileRef: "arusnavi.b2.internal.v1",
profileName: "ARUSNAVI B2",
listenerRef: "listener:preview",
protocol: "INTERNAL",
direction: "bidirectional",
lifecycleState: "active",
sessionCount: 1,
activeSessionCount: 1,
createdAt: timestamp,
updatedAt: timestamp,
});
devices.set(deviceRef, {
projectRef,
deviceRef,
deviceKey: "pilot-b2-preview",
displayName: "Пилотный B2",
modelProfileRef: "arusnavi.b2.internal.v1",
lifecycleState: "active",
identifier: { kind: "imei", masked: "***********0001" },
session: { state: "online", lastSeenAt: timestamp },
reported: {
observedAt: timestamp,
identity: { iccid1: "****************1111", iccid2: "****************2222" },
firmware: { currentVersion: "0.02", appliedAt: timestamp, availableVersion: "0.05" },
configuration: {
monitoring: {
servers: [
{ host: "legacy.example.invalid", port: 20623, protocol: "INTERNAL", identity: "0" },
{ host: "direct.example.invalid", port: 9921, protocol: "INTERNAL", identity: "0" },
],
},
transmission: { navigation: { position: true, motion: true, hdop: false } },
trajectory: {
normal: { courseDeltaDegrees: 15, speedDeltaKph: 10, distanceMeters: 15, parkingIntervalSeconds: 15 },
roaming: { courseDeltaDegrees: 20, speedDeltaKph: 50, distanceMeters: 1000, parkingIntervalSeconds: 300 },
},
navigation: {
sources: { satellite: true, wifi: false, lbs: false, tag: false },
constellations: { gps: true, glonass: true, galileo: false, beidou: false },
filter: { minimumSatellites: 4, maximumHdopTimesTen: 30 },
},
},
telemetry: {
navigation: {
latitude: "55.7500",
longitude: "37.6200",
speedKph: 18,
altitudeMeters: 156,
satellites: 12,
courseDegrees: 84,
hdop: 1.2,
},
gsm: { signal: 79, operator: "preview", lac: "masked", cid: "masked" },
system: { externalVoltageMv: 13240, internalVoltageMv: 4120, status: "Норма" },
},
},
createdAt: timestamp,
updatedAt: timestamp,
});
sessions.set(sessionRef, {
sessionRef,
projectRef,
routeRef,
routeName: "B2 direct preview",
deviceRef,
deviceName: "Пилотный B2",
protocol: "INTERNAL",
lifecycleState: "online",
connectedAt: timestamp,
lastSeenAt: timestamp,
disconnectedAt: null,
closeReasonCode: null,
frameCount: 1842,
byteCount: 734208,
});
configurationStates.set(deviceRef, {
projectRef,
deviceRef,
deviceName: "Пилотный B2",
desiredConfigurationRevisionRef: null,
appliedConfigurationRevisionRef: null,
appliedAt: null,
updatedAt: timestamp,
});
}
const previewTransitions = Object.freeze({
adapterPackage: Object.freeze({
active: Object.freeze(["active", "retired"]),
@@ -220,6 +220,24 @@ test("local preview is empty and creates resources only through canonical comman
assert.deepEqual(workspace.devices, []);
});
test("explicit B2 preview fixture is isolated from the empty canonical preview", async () => {
const client = createLocalPreviewDeviceCore({ fixture: "arusnavi-b2" });
const projects = await client.listProjects(actor);
assert.equal(projects.length, 1);
assert.equal(projects[0].counts.devices, 1);
const workspace = await client.getWorkspace(actor, projects[0].projectRef);
assert.equal(workspace.devices[0].modelProfileRef, "arusnavi.b2.internal.v1");
assert.equal(workspace.devices[0].identifier.masked, "***********0001");
assert.equal(workspace.sessions[0].lifecycleState, "online");
assert.equal(workspace.policies.commandTransport, "disabled");
assert.equal(JSON.stringify(workspace).includes("123456789012345"), false);
assert.throws(
() => createLocalPreviewDeviceCore({ fixture: "unknown" }),
/device_manager_preview_fixture_invalid/,
);
});
function jsonResponse(status, body) {
return new Response(JSON.stringify(body), {
status,
@@ -140,7 +140,9 @@ export async function createConfiguredDeviceManagerServer({ env = process.env }
if (String(env.NODE_ENV || "").toLowerCase() === "production") {
throw new Error("device_manager_local_preview_forbidden");
}
coreClient = createLocalPreviewDeviceCore();
coreClient = createLocalPreviewDeviceCore({
fixture: String(env.NODEDC_DEVICE_MANAGER_PREVIEW_FIXTURE || "").trim() || null,
});
} else {
const tokenFile = String(env.NODEDC_DEVICE_CORE_TOKEN_FILE || "").trim();
if (!tokenFile) throw new Error("device_core_token_file_required");