Polish device traffic charts and bump Harbor client versions
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
||||
formatLastSeen,
|
||||
positiveByteDelta,
|
||||
sortDevicesByTraffic,
|
||||
trafficAxisMid,
|
||||
trafficSampleMetrics,
|
||||
} from '../utils/format.js';
|
||||
|
||||
@@ -46,8 +47,9 @@ function TrafficChart({ samples, scale, capacity, routeLabel }) {
|
||||
const total = byteString(sample.gatewayBytes) + byteString(sample.proxyBytes);
|
||||
return total > largest ? total : largest;
|
||||
}, 0n);
|
||||
const mid = trafficAxisMid(max, scale);
|
||||
const latest = samples.at(-1)?.observedAt || 'empty';
|
||||
return <span className="client-device-traffic-chart" role="img" aria-label={`История трафика, шкала ${scale === 'log' ? 'логарифмическая' : 'линейная'}`}>
|
||||
return <span className="client-device-traffic-chart" role="img" aria-label={`История трафика, шкала ${scale === 'log' ? 'логарифмическая' : 'линейная'}, максимум ${formatByteString(max)}`}>
|
||||
<span className="client-device-traffic-track" key={latest} style={{ '--sample-count': Math.max(1, capacity) }}>
|
||||
{samples.map((sample, index) => {
|
||||
const gateway = byteString(sample.gatewayBytes);
|
||||
@@ -57,8 +59,9 @@ function TrafficChart({ samples, scale, capacity, routeLabel }) {
|
||||
const slot = capacity - samples.length + index;
|
||||
const edge = slot < 28 ? ' is-edge-left' : slot >= capacity - 28 ? ' is-edge-right' : '';
|
||||
const { height, gatewayShare } = trafficSampleMetrics(gateway, proxy, max, scale);
|
||||
const newest = index === samples.length - 1;
|
||||
return <i
|
||||
className={`client-device-traffic-bar${edge}`}
|
||||
className={`client-device-traffic-bar${edge}${newest ? ' is-new' : ''}`}
|
||||
key={`${sample.observedAt}-${index}`}
|
||||
style={{ height: `${height}%`, gridColumnStart: slot + 1 }}
|
||||
>
|
||||
@@ -75,6 +78,11 @@ function TrafficChart({ samples, scale, capacity, routeLabel }) {
|
||||
</i>;
|
||||
})}
|
||||
</span>
|
||||
{max > 0n && <span className="client-device-traffic-axis" aria-hidden="true">
|
||||
<span className="is-max">{formatByteString(max)}</span>
|
||||
<span className="is-mid">{formatByteString(mid)}</span>
|
||||
<span className="is-zero">0</span>
|
||||
</span>}
|
||||
{samples.length > 0 && <span className="client-device-traffic-time" aria-hidden="true">
|
||||
<time dateTime={samples[0].observedAt}>{chartTime(samples[0].observedAt)}</time>
|
||||
<span>15 с</span>
|
||||
@@ -98,6 +106,7 @@ export function DevicesPanel({ open, panelRef, closeRef, onClose }) {
|
||||
const [trafficDeltas, setTrafficDeltas] = useState({});
|
||||
const deviceNodes = useRef(new Map());
|
||||
const previousPositions = useRef(new Map());
|
||||
const movementAnimations = useRef(new Map());
|
||||
const previousTraffic = useRef(new Map());
|
||||
const copyTimer = useRef(null);
|
||||
const trafficDeltaTimer = useRef(null);
|
||||
@@ -172,11 +181,13 @@ export function DevicesPanel({ open, panelRef, closeRef, onClose }) {
|
||||
useLayoutEffect(() => {
|
||||
if (!open) {
|
||||
previousPositions.current.clear();
|
||||
for (const animation of movementAnimations.current.values()) animation.cancel();
|
||||
movementAnimations.current.clear();
|
||||
return;
|
||||
}
|
||||
const positions = new Map();
|
||||
for (const [id, node] of deviceNodes.current) {
|
||||
node.getAnimations().forEach((animation) => animation.cancel());
|
||||
movementAnimations.current.get(id)?.cancel();
|
||||
positions.set(id, node.getBoundingClientRect());
|
||||
}
|
||||
if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
|
||||
@@ -184,10 +195,14 @@ export function DevicesPanel({ open, panelRef, closeRef, onClose }) {
|
||||
const before = previousPositions.current.get(id);
|
||||
const deltaY = before ? before.top - after.top : 0;
|
||||
if (Math.abs(deltaY) < 1) continue;
|
||||
deviceNodes.current.get(id)?.animate([
|
||||
const animation = deviceNodes.current.get(id)?.animate([
|
||||
{ transform: `translateY(${deltaY}px)` },
|
||||
{ transform: 'translateY(0)' },
|
||||
], { duration: DEVICE_MOVE_MS, easing: 'cubic-bezier(0.16, 1, 0.3, 1)' });
|
||||
if (animation) {
|
||||
movementAnimations.current.set(id, animation);
|
||||
animation.onfinish = () => movementAnimations.current.delete(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
previousPositions.current = positions;
|
||||
@@ -408,7 +423,7 @@ export function DevicesPanel({ open, panelRef, closeRef, onClose }) {
|
||||
if (node) deviceNodes.current.set(device.id, node);
|
||||
else deviceNodes.current.delete(device.id);
|
||||
}}
|
||||
className={`client-device is-${device.status}`}
|
||||
className={`client-device is-${device.status}${device.pinned ? ' is-pinned' : ''}`}
|
||||
key={device.id}
|
||||
>
|
||||
<span className="client-device-pin-wrap client-tooltip-anchor">
|
||||
|
||||
Reference in New Issue
Block a user