Track per-device traffic totals and recover inventory state
Build and Deploy Gateway / build-and-push (push) Successful in 15s
Build and Deploy Gateway / deploy (push) Successful in 13s

This commit is contained in:
2026-08-07 15:24:54 +03:00
parent e774486b99
commit 307ad02cd7
13 changed files with 842 additions and 96 deletions
+28 -1
View File
@@ -3,7 +3,11 @@ import fs from 'node:fs';
import path from 'node:path';
import test from 'node:test';
import { fileURLToPath } from 'node:url';
import { formatLastSeen } from '../../src/web/utils/format.js';
import {
formatByteString,
formatLastSeen,
sortDevicesByTraffic,
} from '../../src/web/utils/format.js';
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
const overview = fs.readFileSync(path.join(root, 'src/web/components/ClientOverviewPage.jsx'), 'utf8');
@@ -17,6 +21,7 @@ test('Gateway device inventory uses the existing accessible responsive drawer',
assert.match(panel, /api\.devices\.list\(\)/);
assert.match(panel, /api\.devices\.refresh\(\)/);
assert.match(panel, /api\.devices\.update\(device\.id, patch, snapshot\.revision\)/);
assert.match(panel, /requestError\.code !== 'STATE_CONFLICT'[\s\S]*api\.devices\.list\(\)[\s\S]*Object\.keys\(patch\)\.some\(\(key\) => latestDevice\[key\] !== device\[key\]\)[\s\S]*api\.devices\.update\(device\.id, patch, latest\.revision\)/);
assert.match(panel, /deviceNodes\.current\.get\(id\)\?\.animate/);
assert.match(panel, /prefers-reduced-motion: reduce/);
assert.match(api, /refresh: \(\) => request\('\/api\/devices\/refresh', \{ method: 'POST' \}\)/);
@@ -27,6 +32,11 @@ test('Gateway device inventory uses the existing accessible responsive drawer',
assert.match(panel, /client-device-addresses/);
assert.doesNotMatch(panel, /device\.interface/);
assert.match(panel, /device\.confidence === 'ambiguous'/);
assert.match(panel, /sortDevicesByTraffic\(snapshot\?\.devices, sortDirection\)/);
assert.match(panel, /Трафик временно не обновляется/);
assert.match(panel, /Получено \$\{download\}, отдано \$\{upload\}/);
assert.match(panel, /client-device-traffic/);
assert.match(panel, /client-drawer client-instructions client-devices/);
assert.doesNotMatch(panel, /точная MAC|частная MAC|<dt>Источник<\/dt>/);
assert.match(panel, /aria-pressed=\{device\.pinned\}/);
assert.match(panel, /maxLength="64"[\s\S]*autoFocus/);
@@ -36,6 +46,8 @@ test('Gateway device inventory uses the existing accessible responsive drawer',
assert.match(styles, /\.client-device-pin-wrap\.client-tooltip-anchor:hover > \.client-tooltip[\s\S]*translate\(0, 0\)/);
assert.match(styles, /\.client-devices-refresh-ring circle[\s\S]*client-devices-refresh-progress 15s/);
assert.match(styles, /@media \(prefers-reduced-motion: reduce\)[\s\S]*\.client-text-morph-value/);
assert.match(styles, /\.client-drawer \{[\s\S]*z-index: 50;[\s\S]*box-shadow:/);
assert.match(styles, /\.harbor-versions \{[\s\S]*z-index: 40/);
});
test('device last-seen copy is compact with precise accessible and relative forms', () => {
@@ -50,3 +62,18 @@ test('device last-seen copy is compact with precise accessible and relative form
},
);
});
test('device traffic formatting and sorting preserve uint64 precision and canonical ties', () => {
assert.equal(formatByteString('9007199254740993'), '8,0 ПБ');
assert.equal(formatByteString('1536'), '1,5 КБ');
assert.equal(formatByteString('invalid'), '0 Б');
const devices = [
{ id: 'a', uploadBytes: '9007199254740993', downloadBytes: '0' },
{ id: 'b', uploadBytes: '9007199254740992', downloadBytes: '2' },
{ id: 'c', uploadBytes: '10', downloadBytes: '10' },
{ id: 'd', uploadBytes: '15', downloadBytes: '5' },
];
assert.deepEqual(sortDevicesByTraffic(devices, 'desc').map(({ id }) => id), ['b', 'a', 'c', 'd']);
assert.deepEqual(sortDevicesByTraffic(devices, 'asc').map(({ id }) => id), ['c', 'd', 'a', 'b']);
});