feat(NetworkMapUnifi): add short speed formatting and country flag display to network edges
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m56s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m56s
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { useMemo, useCallback, useState, useRef, useEffect } from 'react';
|
||||
import { IconRefresh, IconMaximize, IconMinimize } from '@tabler/icons-react';
|
||||
import { countryToFlag } from './utils/serverUtils.js';
|
||||
|
||||
const UNIFI_BG = '#0f172a';
|
||||
const UNIFI_CARD_BG = '#1e293b';
|
||||
@@ -64,6 +65,22 @@ function formatMbps(bps) {
|
||||
return `${mbps.toFixed(1)} Мбит/с`;
|
||||
}
|
||||
|
||||
/** Короткий формат скорости для подписи на ребре: "↓287 ↑170 Мбит/с" */
|
||||
function formatSpeedShort(downloadBps, uploadBps) {
|
||||
const fmt = (v) => {
|
||||
if (v == null || Number.isNaN(v)) return '—';
|
||||
const mbps = v / 1_000_000;
|
||||
if (!Number.isFinite(mbps)) return '—';
|
||||
return mbps >= 10 ? Math.round(mbps) : mbps.toFixed(1);
|
||||
};
|
||||
const down = fmt(downloadBps);
|
||||
const up = fmt(uploadBps);
|
||||
if (down === '—' && up === '—') return null;
|
||||
if (down === '—') return `↑${up} Мбит/с`;
|
||||
if (up === '—') return `↓${down} Мбит/с`;
|
||||
return `↓${down} ↑${up} Мбит/с`;
|
||||
}
|
||||
|
||||
function speedKey(key1, key2) {
|
||||
return [String(key1), String(key2)].sort().join(':');
|
||||
}
|
||||
@@ -197,7 +214,7 @@ export default function NetworkMapUnifi({ servers = [], connections = [], pingMa
|
||||
const speed = speedMap[sk];
|
||||
const speedLabel =
|
||||
speed && (speed.tcpDownloadBps != null || speed.tcpUploadBps != null)
|
||||
? `↓ ${formatMbps(speed.tcpDownloadBps)} ↑ ${formatMbps(speed.tcpUploadBps)}`
|
||||
? formatSpeedShort(speed.tcpDownloadBps, speed.tcpUploadBps)
|
||||
: null;
|
||||
return {
|
||||
edgeKey: `${from}-${to}-${c.tunnelType || ''}`,
|
||||
@@ -299,9 +316,9 @@ export default function NetworkMapUnifi({ servers = [], connections = [], pingMa
|
||||
<g>
|
||||
{(() => {
|
||||
const used = [];
|
||||
const LABEL_W = 64;
|
||||
const LABEL_H = 20;
|
||||
const LABEL_GAP = 4;
|
||||
const LABEL_W = 100;
|
||||
const LABEL_H = 18;
|
||||
const LABEL_GAP = 6;
|
||||
return edgesWithPing.map((e, idx) => {
|
||||
const dimFactor =
|
||||
hoveredEdgeKey && hoveredEdgeKey !== e.edgeKey ? 0.25 : 1;
|
||||
@@ -331,39 +348,40 @@ export default function NetworkMapUnifi({ servers = [], connections = [], pingMa
|
||||
const labels = [e.pingLabel, e.speedLabel].filter(Boolean);
|
||||
if (labels.length === 0) return null;
|
||||
let level = 0;
|
||||
const totalH = labels.length * LABEL_H + (labels.length - 1) * LABEL_GAP;
|
||||
while (
|
||||
level < 10 &&
|
||||
used.some(
|
||||
(p) =>
|
||||
Math.abs(p.x - x) < LABEL_W &&
|
||||
Math.abs(p.y - (y + level * (LABEL_H + LABEL_GAP))) < (labels.length * (LABEL_H + LABEL_GAP))
|
||||
Math.abs(p.y - (y + level * (totalH + 8))) < totalH + 16
|
||||
)
|
||||
) {
|
||||
level += 1;
|
||||
}
|
||||
y = y + level * (LABEL_H + LABEL_GAP);
|
||||
y = y + level * (totalH + 8);
|
||||
used.push({ x, y });
|
||||
|
||||
const pad = 6;
|
||||
const pad = 8;
|
||||
return (
|
||||
<g>
|
||||
{labels.map((text, i) => {
|
||||
const yy = y + i * (LABEL_H + LABEL_GAP);
|
||||
const w = Math.min(text.length * 5.5 + pad * 2, 140);
|
||||
const h = 18;
|
||||
const yy = y + i * (LABEL_H + LABEL_GAP) + LABEL_H / 2;
|
||||
const w = Math.max(LABEL_W, Math.min(text.length * 6 + pad * 2, 160));
|
||||
const h = LABEL_H + 4;
|
||||
return (
|
||||
<g key={i}>
|
||||
<rect x={x - w / 2} y={yy - h / 2} width={w} height={h} rx={4} fill="rgba(15,23,42,0.9)" stroke={UNIFI_GREEN} strokeWidth={1} />
|
||||
<rect x={x - w / 2} y={yy - h / 2} width={w} height={h} rx={4} fill="rgba(15,23,42,0.92)" stroke={UNIFI_GREEN} strokeWidth={1} />
|
||||
<text
|
||||
x={x}
|
||||
y={yy}
|
||||
textAnchor="middle"
|
||||
dominantBaseline="middle"
|
||||
fill={UNIFI_GREEN}
|
||||
fontSize={i === 0 ? 11 : 10}
|
||||
fontSize={i === 0 ? 12 : 11}
|
||||
fontWeight={600}
|
||||
>
|
||||
{text.length > 24 ? text.slice(0, 22) + '…' : text}
|
||||
{text}
|
||||
</text>
|
||||
</g>
|
||||
);
|
||||
@@ -400,6 +418,20 @@ export default function NetworkMapUnifi({ servers = [], connections = [], pingMa
|
||||
strokeWidth={1}
|
||||
/>
|
||||
<g transform={`translate(${NODE_WIDTH / 2}, ${NODE_HEIGHT / 2})`}>
|
||||
{/* Флаг страны в левом верхнем углу карточки */}
|
||||
{s.country && (
|
||||
<text
|
||||
x={-NODE_WIDTH / 2 + 14}
|
||||
y={-NODE_HEIGHT / 2 + 14}
|
||||
textAnchor="middle"
|
||||
dominantBaseline="middle"
|
||||
fontSize={14}
|
||||
style={{ pointerEvents: 'none' }}
|
||||
title={s.country}
|
||||
>
|
||||
{countryToFlag(s.country)}
|
||||
</text>
|
||||
)}
|
||||
{/* Иконка сервера в стиле UNIFI: прямоугольник с «портами» */}
|
||||
<g transform="translate(-10, -16)">
|
||||
<rect x={2} y={0} width={16} height={12} rx={2} fill="none" stroke={UNIFI_GREEN} strokeWidth={1.5} />
|
||||
|
||||
Reference in New Issue
Block a user