Add outbound traffic breakdown to device charts
This commit is contained in:
@@ -504,6 +504,82 @@ test('device traffic history stays in bounded service memory and survives client
|
||||
assert.deepEqual(createService().snapshot().traffic.history, []);
|
||||
});
|
||||
|
||||
test('device outbound history keeps sing-box routes and kernel Direct on separate reset-safe series', async (t) => {
|
||||
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'harbor-device-outbound-history-'));
|
||||
t.after(() => fs.rmSync(directory, { recursive: true, force: true }));
|
||||
const filePath = path.join(directory, 'devices.json');
|
||||
const store = createJsonStore({ filePath, defaultValue: {}, migrate: migrateDeviceInventoryState });
|
||||
const mac = '00:11:22:33:44:55';
|
||||
const id = deviceId(mac);
|
||||
let observedAt = '2026-08-12T12:00:00.000Z';
|
||||
let epoch = 'epoch-a';
|
||||
let vpn = ['100', '200'];
|
||||
let trackedDirect = ['10', '20'];
|
||||
let unknown = ['1', '2'];
|
||||
let directIpv4 = ['40', '60'];
|
||||
const service = createDeviceInventoryService({
|
||||
store,
|
||||
observe: () => ({
|
||||
observedAt,
|
||||
error: null,
|
||||
observations: [{ ip: '192.168.50.7', mac, interface: 'eth0', observedAt, active: true }],
|
||||
}),
|
||||
observeTraffic: () => ({
|
||||
epoch,
|
||||
generation: 'rules-a',
|
||||
observedAt,
|
||||
source: { error: null },
|
||||
direct: { uploadBytes: directIpv4[0], downloadBytes: directIpv4[1] },
|
||||
devices: [{
|
||||
ip: '192.168.50.7', mac, interface: 'eth0', uploadBytes: '100', downloadBytes: '200',
|
||||
directUploadBytes: directIpv4[0], directDownloadBytes: directIpv4[1],
|
||||
}],
|
||||
}),
|
||||
observeDomainTraffic: () => ({
|
||||
epoch,
|
||||
observedAt,
|
||||
source: { error: null },
|
||||
routes: [
|
||||
{ deviceId: id, source: 'gateway', outbound: 'vpn', uploadBytes: vpn[0], downloadBytes: vpn[1] },
|
||||
{ deviceId: id, source: 'proxy', outbound: 'direct', uploadBytes: trackedDirect[0], downloadBytes: trackedDirect[1] },
|
||||
{ deviceId: id, source: 'gateway', outbound: 'unknown', uploadBytes: unknown[0], downloadBytes: unknown[1] },
|
||||
],
|
||||
series: [],
|
||||
}),
|
||||
});
|
||||
|
||||
assert.deepEqual((await service.refresh()).devices[0].outboundTrafficHistory, []);
|
||||
observedAt = '2026-08-12T12:00:15.000Z';
|
||||
vpn = ['150', '250'];
|
||||
trackedDirect = ['30', '50'];
|
||||
unknown = ['4', '8'];
|
||||
directIpv4 = ['70', '90'];
|
||||
let snapshot = await service.refresh();
|
||||
assert.deepEqual(snapshot.devices[0].outboundTrafficHistory, [{
|
||||
observedAt,
|
||||
vpnBytes: '100',
|
||||
directTrackedBytes: '50',
|
||||
directIpv4Bytes: '60',
|
||||
unknownBytes: '9',
|
||||
}]);
|
||||
|
||||
observedAt = '2026-08-12T12:00:30.000Z';
|
||||
epoch = 'epoch-b';
|
||||
vpn = ['3', '4'];
|
||||
trackedDirect = ['1', '2'];
|
||||
unknown = ['0', '1'];
|
||||
directIpv4 = ['5', '6'];
|
||||
snapshot = await service.refresh();
|
||||
assert.deepEqual(snapshot.devices[0].outboundTrafficHistory.at(-1), {
|
||||
observedAt,
|
||||
vpnBytes: '0',
|
||||
directTrackedBytes: '0',
|
||||
directIpv4Bytes: '0',
|
||||
unknownBytes: '0',
|
||||
});
|
||||
assert.doesNotMatch(fs.readFileSync(filePath, 'utf8'), /outboundTrafficHistory/);
|
||||
});
|
||||
|
||||
test('global traffic stays monotonic when a device expires and returns in the same epoch', async (t) => {
|
||||
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'harbor-global-traffic-retention-'));
|
||||
t.after(() => fs.rmSync(directory, { recursive: true, force: true }));
|
||||
|
||||
Reference in New Issue
Block a user