Refine device inventory layout and traffic chart styling
Build and Deploy Gateway / build-and-push (push) Successful in 14s
Build and Deploy Gateway / deploy (push) Successful in 7s

This commit is contained in:
2026-08-07 19:43:56 +03:00
parent 8926a8877d
commit ffd899033e
4 changed files with 51 additions and 25 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
export const HARBOR_VERSIONS = Object.freeze({ export const HARBOR_VERSIONS = Object.freeze({
macClient: '0.14.1', macClient: '0.14.2',
gatewayClient: '0.15.1', gatewayClient: '0.15.2',
gatewayBackend: '0.15.0', gatewayBackend: '0.15.0',
}); });
+1 -1
View File
@@ -479,7 +479,7 @@ export function DevicesPanel({ open, panelRef, closeRef, onClose }) {
dateTime={device.lastSeenAt} dateTime={device.lastSeenAt}
aria-label={`${online ? 'В сети' : 'Не в сети'}. Последний контакт: ${seen.tooltip}`} aria-label={`${online ? 'В сети' : 'Не в сети'}. Последний контакт: ${seen.tooltip}`}
> >
{online ? 'В сети' : <TextMorph from={seen.label} to={seen.relative} />} {online ? 'В сети' : <TextMorph from="Не в сети" to={seen.relative} />}
</time> </time>
</span> </span>
</div> </div>
+39 -16
View File
@@ -936,28 +936,30 @@ p {
grid-column: 2; grid-column: 2;
grid-row: 1; grid-row: 1;
height: 34px; height: 34px;
position: relative;
min-width: 0; min-width: 0;
display: flex; display: flex;
align-items: center; align-items: flex-end;
gap: 8px; gap: 8px;
padding-top: 11px;
} }
.client-device-main > h3 { .client-device-main > h3 {
height: 32px; height: 23px;
flex: 0 1 auto; flex: 0 1 auto;
min-width: 0; min-width: 0;
display: flex; display: flex;
align-items: center; align-items: center;
overflow: hidden; overflow: hidden;
margin: 0; margin: 0;
font-size: 15px; font-size: 14px;
letter-spacing: -0.03em; letter-spacing: -0.03em;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.client-device-name { .client-device-name {
height: 32px; height: 23px;
width: 100%; width: 100%;
min-width: 0; min-width: 0;
display: grid; display: grid;
@@ -967,12 +969,16 @@ p {
border: 0; border: 0;
background: transparent; background: transparent;
color: var(--client-text); color: var(--client-text);
font: 700 15px/1.2 'JetBrains Mono', 'SF Mono', ui-monospace, Menlo, monospace; font: 700 14px/1.2 'JetBrains Mono', 'SF Mono', ui-monospace, Menlo, monospace;
letter-spacing: -0.03em; letter-spacing: -0.03em;
text-align: left; text-align: left;
cursor: copy; cursor: copy;
} }
.client-device-name-ip {
font-size: 12px;
}
.client-device-name > span { .client-device-name > span {
grid-area: 1 / 1; grid-area: 1 / 1;
overflow: hidden; overflow: hidden;
@@ -1051,6 +1057,8 @@ p {
.client-device-edit-wrap { .client-device-edit-wrap {
flex: 0 0 auto; flex: 0 0 auto;
height: 23px;
align-self: flex-end;
opacity: 0.34; opacity: 0.34;
transition: opacity 180ms ease; transition: opacity 180ms ease;
} }
@@ -1076,6 +1084,11 @@ p {
color: var(--client-muted); color: var(--client-muted);
} }
.client-device-edit {
width: 26px;
height: 23px;
}
.client-device-edit svg, .client-device-edit svg,
.client-device-pin svg { .client-device-pin svg {
width: 17px; width: 17px;
@@ -1120,14 +1133,16 @@ p {
} }
.client-device-last-seen { .client-device-last-seen {
height: 32px; position: absolute;
flex: 0 1 auto; top: 0;
left: 0;
height: 10px;
min-width: 0; min-width: 0;
display: flex; display: flex;
align-items: center; align-items: center;
max-width: 116px; max-width: 150px;
overflow: hidden; overflow: hidden;
font-size: 10px; font-size: 8.5px;
text-overflow: ellipsis; text-overflow: ellipsis;
color: var(--client-text); color: var(--client-text);
cursor: default; cursor: default;
@@ -1163,7 +1178,7 @@ p {
.client-text-morph-anchor, .client-text-morph-anchor,
.client-text-morph-value { .client-text-morph-value {
grid-area: 1 / 1; grid-area: 1 / 1;
justify-self: end; justify-self: start;
white-space: nowrap; white-space: nowrap;
} }
@@ -1175,7 +1190,7 @@ p {
will-change: opacity, filter, transform; will-change: opacity, filter, transform;
filter: blur(0); filter: blur(0);
transform: translateY(0) scale(1); transform: translateY(0) scale(1);
transform-origin: right center; transform-origin: left center;
transition: opacity 360ms ease, filter 480ms cubic-bezier(0.16, 1, 0.3, 1), transform 480ms cubic-bezier(0.16, 1, 0.3, 1); transition: opacity 360ms ease, filter 480ms cubic-bezier(0.16, 1, 0.3, 1), transform 480ms cubic-bezier(0.16, 1, 0.3, 1);
} }
@@ -1211,7 +1226,7 @@ p {
width: 112px; width: 112px;
height: 34px; height: 34px;
position: relative; position: relative;
z-index: 3; z-index: 6;
color: var(--client-text); color: var(--client-text);
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
} }
@@ -1331,7 +1346,7 @@ p {
} }
.client-device-traffic-chart { .client-device-traffic-chart {
grid-column: 2 / 3; grid-column: 2 / 4;
grid-row: 2; grid-row: 2;
height: 42px; height: 42px;
position: relative; position: relative;
@@ -1339,6 +1354,10 @@ p {
overflow: visible; overflow: visible;
} }
.client-device-traffic-chart:has(.client-device-traffic-bar:hover) {
z-index: 7;
}
.client-device-traffic-chart::after { .client-device-traffic-chart::after {
position: absolute; position: absolute;
right: 0; right: 0;
@@ -1355,7 +1374,7 @@ p {
display: grid; display: grid;
grid-template-columns: repeat(var(--sample-count), minmax(1px, 1fr)); grid-template-columns: repeat(var(--sample-count), minmax(1px, 1fr));
align-items: flex-end; align-items: flex-end;
gap: 1px; gap: 0;
animation: client-device-traffic-shift 420ms cubic-bezier(0.16, 1, 0.3, 1); animation: client-device-traffic-shift 420ms cubic-bezier(0.16, 1, 0.3, 1);
} }
@@ -1365,6 +1384,10 @@ p {
cursor: help; cursor: help;
} }
.client-device-traffic-bar:hover {
z-index: 10;
}
.client-device-traffic-time { .client-device-traffic-time {
position: absolute; position: absolute;
right: 2px; right: 2px;
@@ -1381,10 +1404,10 @@ p {
.client-device-traffic-bar-fill { .client-device-traffic-bar-fill {
position: absolute; position: absolute;
inset: 0; inset: 0 -2px;
display: flex; display: flex;
overflow: hidden; overflow: hidden;
border-radius: 2px 2px 0 0; border-radius: 3px 3px 0 0;
flex-direction: column-reverse; flex-direction: column-reverse;
} }
+9 -6
View File
@@ -37,7 +37,7 @@ test('Gateway device inventory uses the existing accessible responsive drawer',
assert.match(panel, /COPY_FEEDBACK_MS = 5_000/); assert.match(panel, /COPY_FEEDBACK_MS = 5_000/);
assert.match(panel, /client-device-name-feedback[\s\S]*client-device-edit-wrap[\s\S]*client-device-last-seen/); assert.match(panel, /client-device-name-feedback[\s\S]*client-device-edit-wrap[\s\S]*client-device-last-seen/);
assert.doesNotMatch(panel, /client-device-title/); assert.doesNotMatch(panel, /client-device-title/);
assert.match(panel, /online \? 'В сети' : <TextMorph from=\{seen\.label\} to=\{seen\.relative\} \/>/); assert.match(panel, /online \? 'В сети' : <TextMorph from="Не в сети" to=\{seen\.relative\} \/>/);
assert.match(panel, /aria-label=\{`\$\{online \? 'В сети' : 'Не в сети'\}\. Последний контакт: \$\{seen\.tooltip\}`\}/); assert.match(panel, /aria-label=\{`\$\{online \? 'В сети' : 'Не в сети'\}\. Последний контакт: \$\{seen\.tooltip\}`\}/);
assert.doesNotMatch(panel, /client-device-status|STATUS_LABELS/); assert.doesNotMatch(panel, /client-device-status|STATUS_LABELS/);
assert.doesNotMatch(panel, /client-text-morph-goo/); assert.doesNotMatch(panel, /client-text-morph-goo/);
@@ -82,14 +82,17 @@ test('Gateway device inventory uses the existing accessible responsive drawer',
assert.match(styles, /\.client-device-traffic-total,[\s\S]*\.client-device-traffic-breakdown > span \{[\s\S]*grid-template-columns: 46px minmax\(0, 1fr\)/); assert.match(styles, /\.client-device-traffic-total,[\s\S]*\.client-device-traffic-breakdown > span \{[\s\S]*grid-template-columns: 46px minmax\(0, 1fr\)/);
assert.match(styles, /\.client-device-traffic-breakdown > span \{[\s\S]*translateY\(-12px\)[\s\S]*\.client-device-traffic:hover \.client-device-traffic-breakdown > span,[\s\S]*opacity: 1[\s\S]*translateY\(0\)/); assert.match(styles, /\.client-device-traffic-breakdown > span \{[\s\S]*translateY\(-12px\)[\s\S]*\.client-device-traffic:hover \.client-device-traffic-breakdown > span,[\s\S]*opacity: 1[\s\S]*translateY\(0\)/);
assert.doesNotMatch(styles, /\.client-device-traffic-breakdown \{[^}]*background:|\.client-device-traffic-breakdown \{[^}]*box-shadow:/); assert.doesNotMatch(styles, /\.client-device-traffic-breakdown \{[^}]*background:|\.client-device-traffic-breakdown \{[^}]*box-shadow:/);
assert.match(styles, /\.client-device-traffic-chart \{[\s\S]*grid-column: 2 \/ 3;[\s\S]*grid-row: 2/); assert.match(styles, /\.client-device-traffic-chart \{[\s\S]*grid-column: 2 \/ 4;[\s\S]*grid-row: 2/);
assert.match(styles, /\.client-device-traffic-track \{[\s\S]*client-device-traffic-shift 420ms/); assert.match(styles, /\.client-device-traffic-track \{[\s\S]*client-device-traffic-shift 420ms/);
assert.match(styles, /\.client-device-traffic-track \{[^}]*gap: 0/);
assert.match(styles, /\.client-device-traffic-bar-fill \{[\s\S]*inset: 0 -2px/);
assert.match(styles, /\.client-device-traffic-bar:hover \.client-device-traffic-bar-tooltip \{[\s\S]*opacity: 1[\s\S]*visibility: visible/); assert.match(styles, /\.client-device-traffic-bar:hover \.client-device-traffic-bar-tooltip \{[\s\S]*opacity: 1[\s\S]*visibility: visible/);
assert.match(styles, /\.client-device-traffic-bar\.is-edge-left[\s\S]*\.client-device-traffic-bar\.is-edge-right/); assert.match(styles, /\.client-device-traffic-bar\.is-edge-left[\s\S]*\.client-device-traffic-bar\.is-edge-right/);
assert.match(styles, /@keyframes client-device-traffic-shift[\s\S]*translateX\(8px\)[\s\S]*translateX\(0\)/); assert.match(styles, /@keyframes client-device-traffic-shift[\s\S]*translateX\(8px\)[\s\S]*translateX\(0\)/);
assert.match(styles, /\.client-device-policy \{[\s\S]*width: 34px;[\s\S]*border-radius: 50%/); 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-name \{[\s\S]*font: 700 14px\/1\.2/);
assert.match(styles, /\.client-device-last-seen \{[\s\S]*font-size: 10px/); assert.match(styles, /\.client-device-name-ip \{[\s\S]*font-size: 12px/);
assert.match(styles, /\.client-device-last-seen \{[\s\S]*position: absolute;[\s\S]*top: 0;[\s\S]*left: 0;[\s\S]*font-size: 8\.5px/);
assert.match(styles, /\.client-device-traffic strong \{[\s\S]*font-size: 10px/); assert.match(styles, /\.client-device-traffic strong \{[\s\S]*font-size: 10px/);
assert.match(styles, /\.client-device-name > span \{[^}]*opacity: 0/); 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 > \.client-device-name-primary \{[^}]*opacity: 1/);
@@ -98,8 +101,8 @@ test('Gateway device inventory uses the existing accessible responsive drawer',
assert.match(styles, /\.client-device-name:hover \.client-device-name-ip[\s\S]*opacity: 1[\s\S]*translateY\(0\) scale\(1\)/); assert.match(styles, /\.client-device-name:hover \.client-device-name-ip[\s\S]*opacity: 1[\s\S]*translateY\(0\) scale\(1\)/);
assert.match(styles, /\.client-device-name\.is-copied \.client-device-name-feedback[\s\S]*opacity: 1/); assert.match(styles, /\.client-device-name\.is-copied \.client-device-name-feedback[\s\S]*opacity: 1/);
assert.match(styles, /\.client-device-name-feedback \{[^}]*font-size: 9px/); assert.match(styles, /\.client-device-name-feedback \{[^}]*font-size: 9px/);
assert.match(styles, /\.client-device-main \{[^}]*height: 34px[\s\S]*align-items: center/); assert.match(styles, /\.client-device-main \{[^}]*height: 34px[\s\S]*align-items: flex-end[\s\S]*padding-top: 11px/);
assert.match(styles, /\.client-device-last-seen \{[^}]*height: 32px[\s\S]*align-items: center/); assert.match(styles, /\.client-device-last-seen \{[^}]*height: 10px[\s\S]*align-items: center/);
assert.match(styles, /\.client-device-traffic-value\.has-delta > \.is-total[\s\S]*translateY\(-0\.18em\)/); assert.match(styles, /\.client-device-traffic-value\.has-delta > \.is-total[\s\S]*translateY\(-0\.18em\)/);
assert.match(styles, /\.client-device-last-seen\.is-online \{[\s\S]*color: var\(--client-accent\)/); assert.match(styles, /\.client-device-last-seen\.is-online \{[\s\S]*color: var\(--client-accent\)/);
assert.match(styles, /\.client-text-morph-value \{[\s\S]*transition: opacity 360ms[\s\S]*filter 480ms/); assert.match(styles, /\.client-text-morph-value \{[\s\S]*transition: opacity 360ms[\s\S]*filter 480ms/);