From d0c5336b7ecca165a3a03f3a10f7bb443d2e6331 Mon Sep 17 00:00:00 2001 From: Dmitriy Petrov Date: Fri, 7 Aug 2026 17:58:52 +0300 Subject: [PATCH] Polish device inventory layout and typography --- src/shared/versions.js | 4 +- src/web/styles.css | 99 ++++++++++++---------- test/web/device-inventory-contract.test.js | 13 ++- 3 files changed, 66 insertions(+), 50 deletions(-) diff --git a/src/shared/versions.js b/src/shared/versions.js index e172f13..558bf59 100644 --- a/src/shared/versions.js +++ b/src/shared/versions.js @@ -1,6 +1,6 @@ export const HARBOR_VERSIONS = Object.freeze({ - macClient: '0.13.2', - gatewayClient: '0.14.2', + macClient: '0.13.4', + gatewayClient: '0.14.4', gatewayBackend: '0.14.0', }); diff --git a/src/web/styles.css b/src/web/styles.css index e55d16c..2647dc3 100644 --- a/src/web/styles.css +++ b/src/web/styles.css @@ -716,13 +716,21 @@ p { } .client-instructions.client-devices { - width: min(560px, 100vw); + width: min(580px, 100vw); } .client-devices-header { margin-bottom: 28px; } +.client-devices-header h2 { + font-size: 24px; +} + +.client-devices-header .client-instructions-intro { + font-size: 12px; +} + .client-devices-kicker { width: max-content; display: flex; @@ -730,9 +738,13 @@ p { gap: 8px; } +.client-devices-kicker > span:first-child { + font-size: 10px; +} + .client-devices-refresh-wrap { - width: 24px; - height: 24px; + width: 28px; + height: 28px; display: grid; place-items: center; } @@ -742,7 +754,7 @@ p { } .client-devices-sort { - min-height: 24px; + min-height: 28px; display: flex; align-items: center; gap: 4px; @@ -750,7 +762,7 @@ p { border: 0; background: transparent; color: var(--client-muted); - font: 700 9px/1 'JetBrains Mono', 'SF Mono', ui-monospace, Menlo, monospace; + font: 700 10px/1 'JetBrains Mono', 'SF Mono', ui-monospace, Menlo, monospace; cursor: pointer; transition: color 220ms ease, filter 300ms ease; } @@ -774,8 +786,8 @@ p { .client-devices-refresh { position: relative; - width: 24px; - height: 24px; + width: 28px; + height: 28px; display: grid; place-items: center; padding: 0; @@ -828,8 +840,8 @@ p { } .client-devices-refresh-icon { - width: 12px; - height: 12px; + width: 14px; + height: 14px; stroke-width: 1.8; } @@ -856,7 +868,7 @@ p { .client-devices-empty { margin: 0 8px 20px; color: var(--client-muted); - font-size: 10px; + font-size: 11px; line-height: 1.6; } @@ -889,10 +901,10 @@ p { .client-device { display: grid; - grid-template-columns: 32px minmax(0, 1fr) 104px 32px; + grid-template-columns: 34px minmax(0, 1fr) 112px 34px; align-items: center; - gap: 6px; - padding: 8px; + gap: 8px; + padding: 10px 8px; border-top: 1px solid color-mix(in oklch, var(--client-border) 72%, transparent); } @@ -900,7 +912,7 @@ p { min-width: 0; display: flex; align-items: center; - gap: 7px; + gap: 8px; } .client-device-main > h3 { @@ -908,7 +920,7 @@ p { min-width: 0; overflow: hidden; margin: 0; - font-size: 14px; + font-size: 15px; letter-spacing: -0.03em; text-overflow: ellipsis; white-space: nowrap; @@ -923,7 +935,7 @@ p { border: 0; background: transparent; color: var(--client-text); - font: 700 14px/1.2 'JetBrains Mono', 'SF Mono', ui-monospace, Menlo, monospace; + font: 700 15px/1.2 'JetBrains Mono', 'SF Mono', ui-monospace, Menlo, monospace; letter-spacing: -0.03em; text-align: left; cursor: copy; @@ -932,14 +944,13 @@ p { .client-device-name > span { grid-area: 1 / 1; overflow: hidden; - opacity: 1; + opacity: 0; text-overflow: ellipsis; white-space: nowrap; } -.client-device-name-ip, -.client-device-name-feedback { - opacity: 0; +.client-device-name > .client-device-name-primary { + opacity: 1; } .client-device-name:hover .client-device-name-primary, @@ -980,8 +991,8 @@ p { .client-device-edit-wrap, .client-device-pin-wrap { - width: 28px; - height: 28px; + width: 32px; + height: 32px; display: grid; place-items: center; } @@ -998,14 +1009,14 @@ p { } .client-device-pin-wrap { - width: 32px; - height: 32px; + width: 34px; + height: 34px; } .client-device-edit, .client-device-pin { - width: 28px; - height: 28px; + width: 32px; + height: 32px; display: grid; place-items: center; color: var(--client-muted); @@ -1013,8 +1024,8 @@ p { .client-device-edit svg, .client-device-pin svg { - width: 15px; - height: 15px; + width: 17px; + height: 17px; fill: none; stroke: currentColor; stroke-width: 1.7; @@ -1059,7 +1070,7 @@ p { min-width: 0; max-width: 116px; overflow: hidden; - font-size: 8px; + font-size: 10px; text-overflow: ellipsis; color: var(--client-text); cursor: default; @@ -1132,7 +1143,7 @@ p { } .client-device-traffic { - width: 104px; + width: 112px; display: grid; align-items: center; color: var(--client-text); @@ -1146,21 +1157,21 @@ p { .client-device-traffic-sources span { display: grid; - grid-template-columns: 41px minmax(0, 1fr); + grid-template-columns: 46px minmax(0, 1fr); align-items: baseline; gap: 4px; } .client-device-traffic-sources b { color: var(--client-muted); - font-size: 7px; + font-size: 8px; letter-spacing: 0.03em; text-transform: uppercase; } .client-device-traffic-sources strong { overflow: hidden; - font-size: 8px; + font-size: 10px; font-weight: 600; text-align: right; text-overflow: ellipsis; @@ -1176,13 +1187,13 @@ p { } .client-device-policy-wrap { - width: 32px; + width: 34px; position: relative; } .client-device-policy { - width: 32px; - height: 32px; + width: 34px; + height: 34px; display: grid; place-items: center; padding: 0; @@ -1195,8 +1206,8 @@ p { } .client-device-policy svg { - width: 18px; - height: 18px; + width: 20px; + height: 20px; fill: none; stroke: currentColor; stroke-width: 1.7; @@ -1259,7 +1270,7 @@ p { flex: 1 1 auto; min-width: 0; display: grid; - grid-template-columns: minmax(0, 1fr) 28px 28px; + grid-template-columns: minmax(0, 1fr) 32px 32px; align-items: center; gap: 3px; } @@ -1272,7 +1283,7 @@ p { outline: 0; background: transparent; color: var(--client-text); - font-size: 12px; + font-size: 14px; } .client-device-alias input:focus { @@ -1280,14 +1291,14 @@ p { } .client-device-alias button { - width: 28px; - height: 28px; - font-size: 14px; + width: 32px; + height: 32px; + font-size: 16px; } .client-devices-error button { justify-self: start; - font-size: 9px; + font-size: 10px; font-weight: 700; } diff --git a/test/web/device-inventory-contract.test.js b/test/web/device-inventory-contract.test.js index 0886918..e257147 100644 --- a/test/web/device-inventory-contract.test.js +++ b/test/web/device-inventory-contract.test.js @@ -62,12 +62,17 @@ test('Gateway device inventory uses the existing accessible responsive drawer', assert.match(panel, /aria-pressed=\{device\.pinned\}/); assert.doesNotMatch(panel, /Закрепите устройство, чтобы изменить маршрут|Сначала верните маршрут через Gateway/); assert.match(panel, /maxLength="64"[\s\S]*autoFocus/); - assert.match(styles, /\.client-devices \{\s*width: min\(560px, 100vw\)/); - assert.match(styles, /\.client-device \{[\s\S]*grid-template-columns: 32px minmax\(0, 1fr\) 104px 32px;[\s\S]*padding: 8px/); + assert.match(styles, /\.client-devices \{\s*width: min\(580px, 100vw\)/); + assert.match(styles, /\.client-device \{[\s\S]*grid-template-columns: 34px minmax\(0, 1fr\) 112px 34px;[\s\S]*padding: 10px 8px/); assert.match(styles, /\.client-device-main \{[\s\S]*display: flex;[\s\S]*align-items: center/); assert.match(styles, /\.client-device-traffic-sources \{[\s\S]*display: grid/); - assert.match(styles, /\.client-device-traffic-sources span \{[\s\S]*grid-template-columns: 41px minmax\(0, 1fr\)/); - assert.match(styles, /\.client-device-policy \{[\s\S]*width: 32px;[\s\S]*border-radius: 50%/); + assert.match(styles, /\.client-device-traffic-sources span \{[\s\S]*grid-template-columns: 46px minmax\(0, 1fr\)/); + assert.match(styles, /\.client-device-policy \{[\s\S]*width: 34px;[\s\S]*border-radius: 50%/); + assert.match(styles, /\.client-device-name \{[\s\S]*font: 700 15px\/1\.2/); + assert.match(styles, /\.client-device-last-seen \{[\s\S]*font-size: 10px/); + assert.match(styles, /\.client-device-traffic-sources strong \{[\s\S]*font-size: 10px/); + assert.match(styles, /\.client-device-name > span \{[^}]*opacity: 0/); + assert.match(styles, /\.client-device-name > \.client-device-name-primary \{[^}]*opacity: 1/); assert.match(styles, /\.client-device-name:hover \.client-device-name-ip[\s\S]*opacity: 1/); assert.match(styles, /\.client-device-name\.is-copied \.client-device-name-feedback[\s\S]*opacity: 1/); assert.doesNotMatch(styles, /\.client-device-name > span \{[^}]*transition:/);