feat(web): rebuild the console around the fused control plane

Render the Hub feature set in the existing Animal Island styling: fleet
messages, notification centre, log centre, organisation panel, per-instance
device module panels and the settings backup, connection and maintenance
surfaces.
This commit is contained in:
chick
2026-09-05 18:53:04 +08:00
parent f9186bd851
commit a75badeb58
77 changed files with 20578 additions and 435 deletions
+215 -69
View File
@@ -3,39 +3,46 @@ import { describe, expect, it, vi } from 'vitest';
import { createFleetApiDataSource } from './fleet-api-data-source.js';
describe('Fleet API data source', () => {
it('loads instances and their allowlisted resource summaries', async () => {
it('loads the aggregate fleet overview and its resource summaries', async () => {
const fetcher = vi.fn(
async (input: RequestInfo | URL) =>
async () =>
new Response(
JSON.stringify(
String(input).endsWith('/resources')
? {
JSON.stringify({
items: [
{
id: 'alpha',
name: 'Alpha',
origin: 'https://alpha.example/admin',
tags: ['lab'],
revision: 4,
capabilityStatus: 'unknown',
freshness: 'unknown',
credentialConfigured: false,
resources: {
cpuPercent: 23.4,
memoryPercent: 67.8,
maxTemperatureCelsius: 52.6,
phoneNumbers: ['13800000000'],
hardwareOnline: true,
controlOnline: true,
simPresent: true,
carrier: 'China Mobile',
cellularRegistration: 'registered_home',
accessTechnology: 'LTE',
cellularOnline: true,
signalPercent: 72,
uptimeSeconds: 93784.9,
version: '1.9.4',
}
: {
items: [
{
id: 'alpha',
name: 'Alpha',
origin: 'https://alpha.example/admin',
tags: ['lab'],
revision: 4,
capabilityStatus: 'unknown',
freshness: 'unknown',
credentialConfigured: false,
},
],
page: { page: 1, pageSize: 20, total: 1 },
},
),
},
],
}),
{ status: 200, headers: { 'content-type': 'application/json' } },
),
);
const snapshot = await createFleetApiDataSource(fetcher as typeof fetch).load();
expect(snapshot.instances).toEqual([
{
id: 'alpha',
@@ -50,63 +57,57 @@ describe('Fleet API data source', () => {
memoryPercent: 67.8,
maxTemperatureCelsius: 52.6,
phoneNumbers: ['13800000000'],
hardwareOnline: true,
controlOnline: true,
simPresent: true,
carrier: 'China Mobile',
cellularRegistration: 'registered_home',
accessTechnology: 'LTE',
cellularOnline: true,
signalPercent: 72,
uptimeSeconds: 93784,
});
expect(snapshot.statuses.get('alpha')?.summary?.version).toBe('1.9.4');
expect(fetcher).toHaveBeenCalledTimes(1);
expect(fetcher).toHaveBeenCalledWith(
'/api/v1/instances',
expect.objectContaining({ credentials: 'same-origin' }),
);
expect(fetcher).toHaveBeenCalledWith(
'/api/v1/instances/alpha/resources',
'/api/v1/fleet/overview',
expect.objectContaining({ credentials: 'same-origin' }),
);
});
it('emits a partial fleet snapshot before resource enrichment completes', async () => {
let resolveResources!: (value: Response) => void;
const resourcesPromise = new Promise<Response>((resolve) => {
resolveResources = resolve;
});
const fetcher = vi.fn(async (input: RequestInfo | URL) => {
if (String(input).endsWith('/resources')) return resourcesPromise;
return new Response(
JSON.stringify({
items: [
{
id: 'alpha',
name: 'Alpha',
origin: 'https://alpha.example/admin',
tags: [],
revision: 1,
capabilityStatus: 'unknown',
freshness: 'unknown',
credentialConfigured: false,
},
],
page: { page: 1, pageSize: 20, total: 1 },
}),
{ status: 200, headers: { 'content-type': 'application/json' } },
);
});
it('emits skeleton and complete snapshots from the aggregate overview', async () => {
const fetcher = vi.fn(
async () =>
new Response(
JSON.stringify({
items: [
{
id: 'alpha',
name: 'Alpha',
origin: 'https://alpha.example/admin',
tags: [],
revision: 1,
capabilityStatus: 'unknown',
freshness: 'unknown',
credentialConfigured: false,
resources: { cpuPercent: 11, memoryPercent: 22 },
},
],
}),
{ status: 200, headers: { 'content-type': 'application/json' } },
),
);
const partials: unknown[] = [];
const pending = createFleetApiDataSource(fetcher as typeof fetch).load(
undefined,
(snapshot) => {
partials.push({
resources: snapshot.statuses.get('alpha')?.summary?.resources,
freshness: snapshot.statuses.get('alpha')?.summary?.freshness,
});
},
);
await vi.waitFor(() => expect(partials.length).toBe(1));
await createFleetApiDataSource(fetcher as typeof fetch).load(undefined, (snapshot) => {
partials.push({
resources: snapshot.statuses.get('alpha')?.summary?.resources,
freshness: snapshot.statuses.get('alpha')?.summary?.freshness,
});
});
expect(partials.length).toBe(2);
expect(partials[0]).toEqual({ resources: undefined, freshness: 'unknown' });
resolveResources(
new Response(JSON.stringify({ cpuPercent: 11, memoryPercent: 22 }), {
status: 200,
headers: { 'content-type': 'application/json' },
}),
);
await pending;
expect(partials.at(-1)).toEqual({
resources: { cpuPercent: 11, memoryPercent: 22 },
freshness: 'fresh',
@@ -120,3 +121,148 @@ describe('Fleet API data source', () => {
);
});
});
function overviewWith(connection: unknown) {
return vi.fn(
async () =>
new Response(
JSON.stringify({
items: [
{
id: 'alpha',
name: 'Alpha',
origin: 'https://alpha.example/admin',
tags: [],
revision: 1,
capabilityStatus: 'unknown',
freshness: 'unknown',
credentialConfigured: false,
connection,
resources: {},
},
],
}),
{ status: 200, headers: { 'content-type': 'application/json' } },
),
);
}
describe('Fleet resource bounds', () => {
const overviewWithResources = (resources: Record<string, unknown>) =>
vi.fn(
async () =>
new Response(
JSON.stringify({
items: [
{
id: 'alpha',
name: 'Alpha',
origin: 'https://alpha.example/admin',
tags: [],
revision: 1,
capabilityStatus: 'unknown',
freshness: 'unknown',
credentialConfigured: false,
resources,
},
],
}),
{ status: 200, headers: { 'content-type': 'application/json' } },
),
) as unknown as typeof fetch;
it('drops a negative or non-numeric uptime instead of rendering a bogus clock', async () => {
const negative = await createFleetApiDataSource(
overviewWithResources({ uptimeSeconds: -1 }),
).load();
const text = await createFleetApiDataSource(
overviewWithResources({ uptimeSeconds: '93784' }),
).load();
expect(negative.statuses.get('alpha')?.summary?.resources?.uptimeSeconds).toBeUndefined();
expect(text.statuses.get('alpha')?.summary?.resources?.uptimeSeconds).toBeUndefined();
});
it('keeps a zero uptime, which means the device just booted', async () => {
const snapshot = await createFleetApiDataSource(
overviewWithResources({ uptimeSeconds: 0 }),
).load();
expect(snapshot.statuses.get('alpha')?.summary?.resources?.uptimeSeconds).toBe(0);
});
});
describe('Fleet heartbeat connection state', () => {
it('carries a live heartbeat through to the row status', async () => {
const snapshot = await createFleetApiDataSource(
overviewWith({
probed: true,
reachable: true,
authenticated: true,
checkedAt: '2026-09-05T00:00:00.000Z',
}) as typeof fetch,
).load();
expect(snapshot.statuses.get('alpha')).toMatchObject({
probed: true,
reachable: true,
authenticated: true,
checkedAt: '2026-09-05T00:00:00.000Z',
});
});
it('marks a device the heartbeat lost as offline but still checked', async () => {
const snapshot = await createFleetApiDataSource(
overviewWith({
probed: true,
reachable: false,
authenticated: false,
checkedAt: '2026-09-05T00:00:00.000Z',
}) as typeof fetch,
).load();
expect(snapshot.statuses.get('alpha')).toMatchObject({
probed: true,
reachable: false,
authenticated: false,
checkedAt: '2026-09-05T00:00:00.000Z',
});
});
it('reports a device that has never been probed as unknown', async () => {
const snapshot = await createFleetApiDataSource(
overviewWith({
probed: false,
reachable: false,
authenticated: false,
checkedAt: null,
}) as typeof fetch,
).load();
expect(snapshot.statuses.get('alpha')).toMatchObject({
probed: false,
checkedAt: null,
});
});
it('keeps the optimistic shape for a server without the heartbeat journal', async () => {
const snapshot = await createFleetApiDataSource(overviewWith(undefined) as typeof fetch).load();
expect(snapshot.statuses.get('alpha')).toMatchObject({
reachable: true,
authenticated: true,
});
expect(snapshot.statuses.get('alpha')?.probed).toBeUndefined();
expect(snapshot.statuses.get('alpha')?.checkedAt).toBeUndefined();
});
it('applies the heartbeat state to the skeleton snapshot too', async () => {
const partials: unknown[] = [];
await createFleetApiDataSource(
overviewWith({
probed: true,
reachable: false,
authenticated: false,
checkedAt: null,
}) as typeof fetch,
).load(undefined, (snapshot) => {
partials.push(snapshot.statuses.get('alpha'));
});
expect(partials[0]).toMatchObject({ probed: true, reachable: false });
expect(partials.at(-1)).toMatchObject({ probed: true, reachable: false });
});
});