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:
@@ -48,6 +48,16 @@ describe('fleet table view model', () => {
|
||||
expect(fleetStatusKind({ reachable: true, authenticated: true })).toBe('online');
|
||||
});
|
||||
|
||||
it('treats a device the heartbeat has never reached as unknown, not offline', () => {
|
||||
expect(fleetStatusKind({ probed: false, reachable: false, authenticated: false })).toBe(
|
||||
'unknown',
|
||||
);
|
||||
expect(fleetStatusKind({ probed: true, reachable: false, authenticated: false })).toBe(
|
||||
'offline',
|
||||
);
|
||||
expect(fleetStatusKind({ probed: true, reachable: true, authenticated: true })).toBe('online');
|
||||
});
|
||||
|
||||
it('builds stable rows and defaults to ascending display-name order', () => {
|
||||
const model = buildFleetTableViewModel(instances, statuses);
|
||||
|
||||
@@ -197,6 +207,74 @@ describe('fleet table view model', () => {
|
||||
capabilities: ['sms', 'ussd'],
|
||||
versions: ['2.4.1'],
|
||||
tags: ['east', 'west'],
|
||||
groups: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('filters, sorts, and searches by device group', () => {
|
||||
const grouped: readonly FleetInstance[] = [
|
||||
{ id: 'one', name: 'One', url: 'https://one.example', groupId: 'group-field' },
|
||||
{ id: 'two', name: 'Two', url: 'https://two.example', groupId: 'group-office' },
|
||||
{ id: 'three', name: 'Three', url: 'https://three.example', groupId: null },
|
||||
{ id: 'four', name: 'Four', url: 'https://four.example' },
|
||||
];
|
||||
const groupNames = new Map([
|
||||
['group-field', 'Field Ops'],
|
||||
['group-office', 'Office'],
|
||||
]);
|
||||
|
||||
const model = buildFleetTableViewModel(grouped, new Map(), { groupNames });
|
||||
expect(model.facets.groups).toEqual(['group-field', 'group-office']);
|
||||
expect(model.rows.map((row) => [row.id, row.groupId])).toEqual([
|
||||
['four', null],
|
||||
['one', 'group-field'],
|
||||
['three', null],
|
||||
['two', 'group-office'],
|
||||
]);
|
||||
|
||||
expect(
|
||||
buildFleetTableViewModel(grouped, new Map(), {
|
||||
group: 'group-field',
|
||||
groupNames,
|
||||
}).rows.map((row) => row.id),
|
||||
).toEqual(['one']);
|
||||
|
||||
// Group names are searchable, not just the raw identifier.
|
||||
expect(
|
||||
buildFleetTableViewModel(grouped, new Map(), { query: 'office', groupNames }).rows.map(
|
||||
(row) => row.id,
|
||||
),
|
||||
).toEqual(['two']);
|
||||
expect(
|
||||
buildFleetTableViewModel(grouped, new Map(), {
|
||||
query: '外场',
|
||||
groupNames: new Map([['group-field', '外场']]),
|
||||
}).rows.map((row) => row.id),
|
||||
).toEqual(['one']);
|
||||
|
||||
expect(
|
||||
buildFleetTableViewModel(grouped, new Map(), {
|
||||
sort: { column: 'group', direction: 'asc' },
|
||||
groupNames,
|
||||
}).rows.map((row) => row.id),
|
||||
).toEqual(['one', 'two', 'four', 'three']);
|
||||
|
||||
// Sorting follows the visible group name, never the opaque identifier.
|
||||
expect(
|
||||
buildFleetTableViewModel(grouped, new Map(), {
|
||||
sort: { column: 'group', direction: 'asc' },
|
||||
groupNames: new Map([
|
||||
['group-field', 'Zeta'],
|
||||
['group-office', 'Alpha'],
|
||||
]),
|
||||
}).rows.map((row) => row.id),
|
||||
).toEqual(['two', 'one', 'four', 'three']);
|
||||
|
||||
expect(
|
||||
buildFleetTableViewModel(grouped, new Map(), {
|
||||
group: 'group-missing',
|
||||
groupNames,
|
||||
}).emptyReason,
|
||||
).toBe('filter');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user