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({ export const HARBOR_VERSIONS = Object.freeze({
macClient: '0.25.6', macClient: '0.25.7',
gatewayClient: '0.26.5', gatewayClient: '0.26.6',
gatewayBackend: '0.26.5', gatewayBackend: '0.26.5',
}); });
+4 -1
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 === 'linear'} onClick={() => setTrafficScale('linear')}>Лин</button>
<button type="button" aria-pressed={trafficScale === 'log'} onClick={() => setTrafficScale('log')}>Лог</button> <button type="button" aria-pressed={trafficScale === 'log'} onClick={() => setTrafficScale('log')}>Лог</button>
</span> </span>
<span className="client-devices-reset-wrap client-tooltip-anchor">
<button <button
className="client-devices-reset" className="client-devices-reset"
type="button" type="button"
aria-label="Сбросить вход и выход всех устройств"
disabled={!snapshot || resetting} disabled={!snapshot || resetting}
onClick={requestTrafficReset} onClick={requestTrafficReset}
> >
<svg viewBox="0 0 24 24" aria-hidden="true"> <svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M4 12a8 8 0 1 0 2.3-5.7M4 5v7h7" /> <path d="M4 12a8 8 0 1 0 2.3-5.7M4 5v7h7" />
</svg> </svg>
<span>Сбросить данные</span>
</button> </button>
<Tooltip>Сбросить вход и выход всех устройств</Tooltip>
</span>
</div> </div>
<h2 id="client-devices-title">Устройства</h2> <h2 id="client-devices-title">Устройства</h2>
<div className="client-instructions-intro"> <div className="client-instructions-intro">
+27 -10
View File
@@ -21,7 +21,7 @@
.client-devices-kicker { .client-devices-kicker {
width: 100%; width: 100%;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: nowrap;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
} }
@@ -67,20 +67,26 @@
color: var(--client-accent); 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 { .client-devices-reset {
min-height: 28px; width: 28px;
display: flex; height: 28px;
align-items: center; display: grid;
gap: 5px; place-items: center;
padding: 0 6px; padding: 0;
border: 0; border: 0;
border-radius: 50%;
background: transparent; background: transparent;
color: oklch(0.68 0.15 28); 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; cursor: pointer;
white-space: nowrap;
transition: color 220ms ease, filter 300ms ease; transition: color 220ms ease, filter 300ms ease;
} }
@@ -115,6 +121,17 @@
opacity: 0.45; 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 { .client-devices-sort {
min-height: 28px; min-height: 28px;
display: flex; 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-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-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(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, /\.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, /@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:/); 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 sha256 = (value) => crypto.createHash('sha256').update(value).digest('hex');
const acceptedLedger = { const acceptedLedger = {
counts: { counts: {
cascadeEdges: 815, cascadeEdges: 823,
customProperties: 103, customProperties: 103,
declarations: 3299, declarations: 3306,
important: 0, important: 0,
keyframes: 56, keyframes: 56,
media: 13, media: 13,
rules: 947, rules: 950,
variableReferences: 815, variableReferences: 812,
}, },
hashes: { hashes: {
cascadeEdges: '8ed02f069be17d132a23eed9e9480b31fa583f91771dffbe4d44b485042035e0', cascadeEdges: '52014b4f05da82ed3e1404ac7e81e2e7dd0960431828eb22fcdcd99ea4009683',
customProperties: 'ef70fb1d9b61b177f1939f811babe1116bee631de26f5eac0aa0d0009ca5a1fb', customProperties: 'ef70fb1d9b61b177f1939f811babe1116bee631de26f5eac0aa0d0009ca5a1fb',
declarations: '0f20800db4323aac387f740f9b55ef8d7b3b2e78bac8765941dbda4f1e832499', declarations: '568631ecf7ea61652c5fbb5f6d947e8a08fff1a179a72fb2dd6439e0e9cac950',
duplicateKeyframes: '4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945', duplicateKeyframes: '4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945',
duplicateSelectors: '257eff4727dab9ce25f4e1f9320e89bf2270eb29feae921b96601f103ad7f036', duplicateSelectors: '257eff4727dab9ce25f4e1f9320e89bf2270eb29feae921b96601f103ad7f036',
keyframes: 'd4378751f36eccc87923c12540315462055032a5d34978a0f45ecada0a5cc1ce', keyframes: 'd4378751f36eccc87923c12540315462055032a5d34978a0f45ecada0a5cc1ce',
ruleDeclarationSequences: 'bc194eaaf4351ea21cefa140fe084e8a086720dc0fc1b0159dad623e76a0d4e6', ruleDeclarationSequences: 'a53ac1a62e6c88fd923971802e9ec5238c8d5d285b6fbecce1d54bbd38d91e2c',
selectors: 'ddb869866dc84197f7a6bef2e8d07310ec2e9fcc6822b18952de800f4c5d0d59', selectors: '190b90a75c4dfedc9ea66e37046fbceba70b0adbbdf89872db8f83ce93119114',
variableReferences: '2ccbb06fe9f4d0e00dc40aa117add2bed00321b92beee7c581436ecc8c9da4ca', variableReferences: 'd3a740a583156df0b42ce0f52687617a5b5507b394251070c63d355560921f03',
witnesses: 'd8f04b8250b68423a593cb471e535712cb5b9f7d85d4ebd8d34c44f2855432da', 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', () => { test('accepted stylesheet has pinned declaration, selector, keyframe, variable, and cascade ledgers', () => {
const witnesses = readStyleWitnesses(root); 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); assert.equal(witnesses.filter((witness) => witness.unknown || witness.ancestorUnknown).length, 0);
const ledger = createStyleLedger(readStyleSource(root), { witnesses }); const ledger = createStyleLedger(readStyleSource(root), { witnesses });
assert.deepEqual(ledger.counts, acceptedLedger.counts); 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); assert.equal((main.match(/import ['"][^'"]+\.css['"]/g) || []).length, 1);
const assets = fs.readdirSync(path.join(root, 'dist/assets')).filter((file) => file.endsWith('.css')); 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])); const built = fs.readFileSync(path.join(root, 'dist/assets', assets[0]));
assert.equal(built.byteLength, 127660); assert.equal(built.byteLength, 127884);
assert.equal(sha256(built), 'f0f09ebbe31597130121828157e9e68fc77141db5975d32f9e3062b70e5f894b'); 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) .map(({ source }) => source)
.join('\n'); .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(/<CopyButton\b/g) || []).length, 2);
assert.equal((production.match(/<RailAction\b/g) || []).length, 5); assert.equal((production.match(/<RailAction\b/g) || []).length, 5);
assert.equal((production.match(/<Drawer\b/g) || []).length, 5); assert.equal((production.match(/<Drawer\b/g) || []).length, 5);