From 02907845263e5e661ad6cce5c4f2b83ad640e758 Mon Sep 17 00:00:00 2001 From: Dmitriy Petrov Date: Thu, 13 Aug 2026 00:05:04 +0300 Subject: [PATCH] Refine device traffic reset control --- src/shared/versions.ts | 4 +-- src/web/features/devices/DevicesPanel.tsx | 25 ++++++++------- src/web/styles/features/devices.css | 37 ++++++++++++++++------ test/web/device-inventory-contract.test.js | 5 ++- test/web/style-boundaries.test.js | 28 ++++++++-------- test/web/ui-foundations-contract.test.js | 2 +- 6 files changed, 62 insertions(+), 39 deletions(-) diff --git a/src/shared/versions.ts b/src/shared/versions.ts index 32f8718..8930a02 100644 --- a/src/shared/versions.ts +++ b/src/shared/versions.ts @@ -1,6 +1,6 @@ export const HARBOR_VERSIONS = Object.freeze({ - macClient: '0.25.6', - gatewayClient: '0.26.5', + macClient: '0.25.7', + gatewayClient: '0.26.6', gatewayBackend: '0.26.5', }); diff --git a/src/web/features/devices/DevicesPanel.tsx b/src/web/features/devices/DevicesPanel.tsx index e917503..c0c3081 100644 --- a/src/web/features/devices/DevicesPanel.tsx +++ b/src/web/features/devices/DevicesPanel.tsx @@ -339,17 +339,20 @@ export function DevicesPanel({ feature }: { feature: DevicesFeature }) { - + + + Сбросить вход и выход всех устройств +

Устройства

diff --git a/src/web/styles/features/devices.css b/src/web/styles/features/devices.css index 4dee98a..e3731b8 100644 --- a/src/web/styles/features/devices.css +++ b/src/web/styles/features/devices.css @@ -21,7 +21,7 @@ .client-devices-kicker { width: 100%; display: flex; - flex-wrap: wrap; + flex-wrap: nowrap; align-items: center; gap: 8px; } @@ -67,20 +67,26 @@ color: var(--client-accent); } +.client-devices-reset-wrap { + width: 28px; + height: 28px; + flex: 0 0 28px; + display: grid; + place-items: center; + margin-left: auto; +} + .client-devices-reset { - min-height: 28px; - display: flex; - align-items: center; - gap: 5px; - padding: 0 6px; + width: 28px; + height: 28px; + display: grid; + place-items: center; + padding: 0; border: 0; + border-radius: 50%; background: transparent; color: oklch(0.68 0.15 28); - font: var(--type-label); - letter-spacing: var(--type-label-tracking); - text-transform: var(--type-label-transform); cursor: pointer; - white-space: nowrap; transition: color 220ms ease, filter 300ms ease; } @@ -115,6 +121,17 @@ opacity: 0.45; } +.client-devices-reset-wrap > .client-tooltip { + right: 0; + left: auto; + transform: translate(0, 2px); +} + +.client-devices-reset-wrap:hover > .client-tooltip, +.client-devices-reset-wrap:has(> :focus-visible) > .client-tooltip { + transform: translate(0, 0); +} + .client-devices-sort { min-height: 28px; display: flex; diff --git a/test/web/device-inventory-contract.test.js b/test/web/device-inventory-contract.test.js index 4a452b7..d40c20f 100644 --- a/test/web/device-inventory-contract.test.js +++ b/test/web/device-inventory-contract.test.js @@ -200,7 +200,10 @@ test('Gateway device inventory uses the existing accessible responsive drawer', assert.match(styles, /\.client-device-traffic-plot svg \{[\s\S]*height: 100%;[\s\S]*overflow: hidden/); assert.match(styles, /\.client-devices-sort:hover \.client-devices-sort-icon[\s\S]*rotate\(360deg\)/); assert.match(styles, /\.client-devices-refresh-ring circle[\s\S]*client-devices-refresh-progress 15s/); - assert.match(panel, /className="client-devices-reset"[\s\S]*Сбросить данные/); + assert.match(panel, /client-devices-reset-wrap client-tooltip-anchor[\s\S]*aria-label="Сбросить вход и выход всех устройств"[\s\S]*Сбросить вход и выход всех устройств<\/Tooltip>/); + assert.doesNotMatch(panel, /className="client-devices-reset"[\s\S]{0,320}/); + assert.match(styles, /\.client-devices-kicker \{[^}]*flex-wrap: nowrap/); + assert.match(styles, /\.client-devices-reset-wrap \{[^}]*margin-left: auto/); assert.match(styles, /\.client-devices-reset \{[\s\S]*oklch\(0\.68 0\.15 28\)[\s\S]*\.client-devices-reset:hover:not\(:disabled\) svg[\s\S]*rotate\(-360deg\)/); assert.match(styles, /@media \(prefers-reduced-motion: reduce\)[\s\S]*\.client-device-policy svg[\s\S]*\.client-device-alias-trigger[\s\S]*\.client-device-identity-details[\s\S]*\.client-device-identity-copy[\s\S]*\.client-device-traffic-value > span[\s\S]*\.client-device-traffic-breakdown[\s\S]*\.client-device-traffic-lines[\s\S]*\.client-text-morph-value[\s\S]*transition: none;[\s\S]*animation: none/); assert.match(styles, /\.client-drawer \{[\s\S]*z-index: 50;[\s\S]*box-shadow:/); diff --git a/test/web/style-boundaries.test.js b/test/web/style-boundaries.test.js index 390c69b..89087ed 100644 --- a/test/web/style-boundaries.test.js +++ b/test/web/style-boundaries.test.js @@ -37,26 +37,26 @@ const expectedImports = [ const sha256 = (value) => crypto.createHash('sha256').update(value).digest('hex'); const acceptedLedger = { counts: { - cascadeEdges: 815, + cascadeEdges: 823, customProperties: 103, - declarations: 3299, + declarations: 3306, important: 0, keyframes: 56, media: 13, - rules: 947, - variableReferences: 815, + rules: 950, + variableReferences: 812, }, hashes: { - cascadeEdges: '8ed02f069be17d132a23eed9e9480b31fa583f91771dffbe4d44b485042035e0', + cascadeEdges: '52014b4f05da82ed3e1404ac7e81e2e7dd0960431828eb22fcdcd99ea4009683', customProperties: 'ef70fb1d9b61b177f1939f811babe1116bee631de26f5eac0aa0d0009ca5a1fb', - declarations: '0f20800db4323aac387f740f9b55ef8d7b3b2e78bac8765941dbda4f1e832499', + declarations: '568631ecf7ea61652c5fbb5f6d947e8a08fff1a179a72fb2dd6439e0e9cac950', duplicateKeyframes: '4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945', duplicateSelectors: '257eff4727dab9ce25f4e1f9320e89bf2270eb29feae921b96601f103ad7f036', keyframes: 'd4378751f36eccc87923c12540315462055032a5d34978a0f45ecada0a5cc1ce', - ruleDeclarationSequences: 'bc194eaaf4351ea21cefa140fe084e8a086720dc0fc1b0159dad623e76a0d4e6', - selectors: 'ddb869866dc84197f7a6bef2e8d07310ec2e9fcc6822b18952de800f4c5d0d59', - variableReferences: '2ccbb06fe9f4d0e00dc40aa117add2bed00321b92beee7c581436ecc8c9da4ca', - witnesses: 'd8f04b8250b68423a593cb471e535712cb5b9f7d85d4ebd8d34c44f2855432da', + ruleDeclarationSequences: 'a53ac1a62e6c88fd923971802e9ec5238c8d5d285b6fbecce1d54bbd38d91e2c', + selectors: '190b90a75c4dfedc9ea66e37046fbceba70b0adbbdf89872db8f83ce93119114', + variableReferences: 'd3a740a583156df0b42ce0f52687617a5b5507b394251070c63d355560921f03', + witnesses: 'ae24f63221c956797fcd18706d3f318f15d75ffded0297fdbeb81487e6c7875e', }, }; @@ -209,7 +209,7 @@ test('client typography uses the shared semantic scale outside the token owner', test('accepted stylesheet has pinned declaration, selector, keyframe, variable, and cascade ledgers', () => { const witnesses = readStyleWitnesses(root); - assert.equal(witnesses.length, 807); + assert.equal(witnesses.length, 808); assert.equal(witnesses.filter((witness) => witness.unknown || witness.ancestorUnknown).length, 0); const ledger = createStyleLedger(readStyleSource(root), { witnesses }); assert.deepEqual(ledger.counts, acceptedLedger.counts); @@ -405,8 +405,8 @@ test('main owns one public stylesheet and the regrouped production CSS is determ assert.equal((main.match(/import ['"][^'"]+\.css['"]/g) || []).length, 1); const assets = fs.readdirSync(path.join(root, 'dist/assets')).filter((file) => file.endsWith('.css')); - assert.deepEqual(assets, ['index-Cen_yNNI.css']); + assert.deepEqual(assets, ['index-B8MBAMBM.css']); const built = fs.readFileSync(path.join(root, 'dist/assets', assets[0])); - assert.equal(built.byteLength, 127660); - assert.equal(sha256(built), 'f0f09ebbe31597130121828157e9e68fc77141db5975d32f9e3062b70e5f894b'); + assert.equal(built.byteLength, 127884); + assert.equal(sha256(built), 'ec9b242f2a01e75970ec6dee43a037764835c9032a1ea6cfa97df3c7735f76ba'); }); diff --git a/test/web/ui-foundations-contract.test.js b/test/web/ui-foundations-contract.test.js index 4a388d5..a7a461b 100644 --- a/test/web/ui-foundations-contract.test.js +++ b/test/web/ui-foundations-contract.test.js @@ -32,7 +32,7 @@ test('all repeated client primitive consumers use the shared owners', () => { .map(({ source }) => source) .join('\n'); - assert.equal((production.match(/