feat(web): rebuild fleet page with Hub-style identity guard and context actions

- Surface device identity state, carrier details and upstream link health.

- Add fleet-level identity monitoring and restart/baseband context actions.

- Apply consistent notification scoping across fleet views.
This commit is contained in:
chick
2026-09-07 00:45:28 +08:00
parent 635527dd26
commit 864e1bd90c
16 changed files with 1596 additions and 90 deletions
@@ -277,4 +277,74 @@ describe('fleet table view model', () => {
}).emptyReason,
).toBe('filter');
});
it('marks and searches the nodes the identity guard is holding', () => {
const instances: FleetInstance[] = [
{ id: 'held', name: 'Held', url: 'http://a', tags: [] },
{ id: 'clear', name: 'Clear', url: 'http://b', tags: [] },
{ id: 'blind', name: 'Blind', url: 'http://c', tags: [] },
];
const statuses = new Map<string, FleetStatus>([
[
'held',
{
reachable: true,
identity: { tracked: true, status: 'pending', reasons: ['imei_claimed'] },
},
],
['clear', { reachable: true, identity: { tracked: true, status: 'confirmed', reasons: [] } }],
[
'blind',
{ reachable: true, identity: { tracked: false, status: 'confirmed', reasons: [] } },
],
]);
const rows = buildFleetTableViewModel(instances, statuses).rows;
expect(rows.map((row) => [row.id, row.identityPending])).toEqual([
['blind', false],
['clear', false],
['held', true],
]);
expect(
buildFleetTableViewModel(instances, statuses, { query: '身份待确认' }).rows.map((r) => r.id),
).toEqual(['held']);
});
it('filters and searches by derived access method and reported IMEI', () => {
const scoped: readonly FleetInstance[] = [
{ id: 'local', name: 'Local', url: 'http://127.0.0.1:3000', tags: [] },
{ id: 'lan', name: 'Lan', url: 'http://192.168.1.3', tags: [] },
{ id: 'wan', name: 'Wan', url: 'https://wan.example', tags: [] },
];
const model = buildFleetTableViewModel(scoped, new Map(), { access: 'local' });
expect(model.rows.map((row) => row.id)).toEqual(['local']);
expect(buildFleetTableViewModel(instances, statuses, { access: 'lan' }).emptyReason).toBe(
'filter',
);
expect(
buildFleetTableViewModel(scoped, new Map(), { query: 'lan' }).rows.map((r) => r.id),
).toEqual(['lan']);
const identified = new Map<string, FleetStatus>([
[
'alpha',
{
reachable: true,
identity: {
tracked: true,
status: 'confirmed',
reasons: [],
imei: '860000000000001',
},
},
],
]);
expect(
buildFleetTableViewModel(
[{ id: 'alpha', name: 'Alpha', url: 'http://alpha.example', tags: [] }],
identified,
{ query: '860000000000001' },
).rows.map((row) => row.id),
).toEqual(['alpha']);
});
});