Refine device traffic reset control
Build and Deploy Gateway / build-and-push (push) Successful in 20s
Build and Deploy Gateway / deploy (push) Successful in 7s

This commit is contained in:
2026-08-13 00:05:04 +03:00
parent b86812d02b
commit 0290784526
6 changed files with 62 additions and 39 deletions
+2 -2
View File
@@ -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',
});
+14 -11
View File
@@ -339,17 +339,20 @@ export function DevicesPanel({ feature }: { feature: DevicesFeature }) {
<button type="button" aria-pressed={trafficScale === 'linear'} onClick={() => setTrafficScale('linear')}>Лин</button>
<button type="button" aria-pressed={trafficScale === 'log'} onClick={() => setTrafficScale('log')}>Лог</button>
</span>
<button
className="client-devices-reset"
type="button"
disabled={!snapshot || resetting}
onClick={requestTrafficReset}
>
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M4 12a8 8 0 1 0 2.3-5.7M4 5v7h7" />
</svg>
<span>Сбросить данные</span>
</button>
<span className="client-devices-reset-wrap client-tooltip-anchor">
<button
className="client-devices-reset"
type="button"
aria-label="Сбросить вход и выход всех устройств"
disabled={!snapshot || resetting}
onClick={requestTrafficReset}
>
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M4 12a8 8 0 1 0 2.3-5.7M4 5v7h7" />
</svg>
</button>
<Tooltip>Сбросить вход и выход всех устройств</Tooltip>
</span>
</div>
<h2 id="client-devices-title">Устройства</h2>
<div className="client-instructions-intro">
+27 -10
View File
@@ -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;
+4 -1
View File
@@ -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>Сбросить вход и выход всех устройств<\/Tooltip>/);
assert.doesNotMatch(panel, /className="client-devices-reset"[\s\S]{0,320}<span>/);
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:/);
+14 -14
View File
@@ -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');
});
+1 -1
View File
@@ -32,7 +32,7 @@ test('all repeated client primitive consumers use the shared owners', () => {
.map(({ source }) => source)
.join('\n');
assert.equal((production.match(/<Tooltip\b/g) || []).length, 9);
assert.equal((production.match(/<Tooltip\b/g) || []).length, 10);
assert.equal((production.match(/<CopyButton\b/g) || []).length, 2);
assert.equal((production.match(/<RailAction\b/g) || []).length, 5);
assert.equal((production.match(/<Drawer\b/g) || []).length, 5);