Default new devices to Direct routing
Build and Deploy Gateway / build-and-push (push) Successful in 24s
Build and Deploy Gateway / deploy (push) Successful in 13s

This commit is contained in:
2026-08-31 01:07:59 +03:00
parent f977874da6
commit 7e15cc199f
11 changed files with 276 additions and 22 deletions
@@ -6,6 +6,7 @@ import { fileURLToPath } from 'node:url';
import {
formatByteString,
formatLastSeen,
isNewDevice,
positiveByteDelta,
sortDevicesByTraffic,
stabilizeDevicesByTraffic,
@@ -54,6 +55,9 @@ test('Gateway device inventory uses the existing accessible responsive drawer',
assert.match(panel, /copyText\(value\)/);
assert.match(panel, /const hasName = Boolean\(device\.alias \|\| device\.hostname\)/);
assert.match(panel, /const title = device\.alias \|\| device\.hostname \|\| device\.ip \|\| 'Неизвестное устройство'/);
assert.match(panel, /const newDevice = isNewDevice\(device\.firstSeenAt\)/);
assert.match(panel, /client-device-new-badge[\s\S]*aria-hidden="true"[\s\S]*>NEW<[\s\S]*client-device-new-a11y[\s\S]*Новое устройство/);
assert.match(panel, /displayPolicy === 'direct'[\s\S]*newDevice[\s\S]*Новое устройство идёт напрямую\. Нажмите, чтобы разрешить VPN через правила Gateway/);
assert.match(panel, /client-device-fallback-name[\s\S]*client-device-identity-details[\s\S]*<b>IP<\/b>[\s\S]*<b>MAC<\/b>[\s\S]*<b>Host<\/b>/);
assert.match(panel, /onClick=\{\(\) => startEditing\(device\)\}/);
assert.match(panel, /\{!hasName && !editing && <span className="client-device-edit-wrap client-tooltip-anchor">/);
@@ -175,6 +179,9 @@ test('Gateway device inventory uses the existing accessible responsive drawer',
assert.match(styles, /\.client-device-policy \{[\s\S]*width: 34px;[\s\S]*border-radius: 50%/);
assert.match(styles, /\.client-device-alias-trigger \{[\s\S]*font: var\(--type-item-title\)/);
assert.match(styles, /\.client-device-alias-trigger\.is-custom-name \{[\s\S]*font: var\(--type-section-title\)/);
assert.match(styles, /\.client-device-new-badge \{[\s\S]*height: 14px;[\s\S]*flex: 0 0 auto;[\s\S]*padding: 0 4px;[\s\S]*border: 1px solid color-mix\(in oklch, var\(--client-accent\)[\s\S]*border-radius: 999px;[\s\S]*background: var\(--client-accent-soft\);[\s\S]*font: var\(--type-micro\)/);
assert.match(styles, /\.client-device-new-a11y \{[\s\S]*position: absolute;[\s\S]*width: 1px;[\s\S]*clip-path: inset\(50%\)/);
assert.doesNotMatch(styles, /client-device-new-badge[^{]*\{[^}]*(?:transition|animation):/);
assert.match(styles, /\.client-device-alias-input \{[\s\S]*width: var\(--alias-width\)[\s\S]*caret-color: var\(--client-accent\)[\s\S]*font: var\(--type-section-title\)[\s\S]*client-device-alias-edit-in 360ms/);
assert.match(styles, /@keyframes client-device-alias-edit-in[\s\S]*color: var\(--client-accent\)[\s\S]*filter: blur\(2px\)/);
assert.doesNotMatch(styles, /\.client-device-alias \{/);
@@ -270,6 +277,14 @@ test('device last-seen copy is compact with precise accessible and relative form
);
});
test('new-device marker uses an exact seven-day window', () => {
const now = Date.parse('2026-08-31T12:00:00.000Z');
assert.equal(isNewDevice(new Date(now - (7 * 24 * 60 * 60 * 1000 - 1_000)).toISOString(), now), true);
assert.equal(isNewDevice(new Date(now - 7 * 24 * 60 * 60 * 1000).toISOString(), now), false);
assert.equal(isNewDevice(new Date(now + 1_000).toISOString(), now), false);
assert.equal(isNewDevice('not-a-date', now), false);
});
test('device traffic formatting and sorting preserve uint64 precision and canonical ties', () => {
assert.equal(formatByteString('9007199254740993'), '8,0 ПБ');
assert.equal(formatByteString('1536'), '1,5 КБ');