diff --git a/frontend/src/NetworkMapUnifi.jsx b/frontend/src/NetworkMapUnifi.jsx
index 353febf..e72e5e8 100644
--- a/frontend/src/NetworkMapUnifi.jsx
+++ b/frontend/src/NetworkMapUnifi.jsx
@@ -4,13 +4,17 @@ import { IconRefresh, IconMaximize, IconMinimize, IconClock } from '@tabler/icon
const FLAG_CDN = 'https://flagcdn.com';
const UNIFI_BG = '#0f172a';
+const UNIFI_BG_DEEP = '#0b1220';
const UNIFI_CARD_BG = '#1e293b';
const UNIFI_CARD_BORDER = '#334155';
const UNIFI_GREEN = '#22c55e';
+const UNIFI_GREEN_SOFT = '#34d399';
+const UNIFI_BLUE_SOFT = '#38bdf8';
+const UNIFI_WARNING = '#f59e0b';
const UNIFI_TEXT = '#f1f5f9';
const UNIFI_TEXT_MUTED = '#94a3b8';
-const NODE_WIDTH = 88;
-const NODE_HEIGHT = 52;
+const NODE_WIDTH = 124;
+const NODE_HEIGHT = 64;
const STORAGE_KEY = 'network-map-unifi-positions';
function computeInitialLayout(servers, size) {
@@ -86,6 +90,21 @@ function speedKey(key1, key2) {
return [String(key1), String(key2)].sort().join(':');
}
+function getNodeTypeMeta(type) {
+ const t = String(type || '').toLowerCase();
+ if (t === 'home') return { label: 'HOME', color: '#60a5fa' };
+ if (t === 'jumphost') return { label: 'JUMP', color: '#34d399' };
+ if (t === 'exit') return { label: 'EXIT', color: '#f59e0b' };
+ return { label: 'NODE', color: '#94a3b8' };
+}
+
+function getTunnelColor(tunnelType) {
+ const t = String(tunnelType || '').toLowerCase();
+ if (t.includes('wireguard') || t === 'wg') return UNIFI_BLUE_SOFT;
+ if (t.includes('ipsec')) return UNIFI_GREEN;
+ return UNIFI_GREEN_SOFT;
+}
+
export default function NetworkMapUnifi({
servers = [],
connections = [],
@@ -231,6 +250,8 @@ export default function NetworkMapUnifi({
speed && (speed.tcpDownloadBps != null || speed.tcpUploadBps != null)
? formatSpeedShort(speed.tcpDownloadBps, speed.tcpUploadBps)
: null;
+ const edgeLabel =
+ pingLabel && speedLabel ? `${pingLabel} • ${speedLabel}` : (pingLabel || speedLabel);
return {
edgeKey: `${from}-${to}-${c.tunnelType || ''}`,
from,
@@ -239,8 +260,10 @@ export default function NetworkMapUnifi({
p2,
pingLabel,
speedLabel,
+ edgeLabel,
pingStale,
speedStale,
+ stale: pingStale || speedStale,
speed,
tunnelType: c.tunnelType,
raw: c,
@@ -334,17 +357,34 @@ export default function NetworkMapUnifi({
preserveAspectRatio="xMidYMid meet"
style={{ display: 'block' }}
>
- {/* Рёбра: зелёные кривые (стиль UNIFI — проводные соединения) */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Рёбра: сглаженные линии с цветовой дифференциацией туннеля */}
{(() => {
const used = [];
- const LABEL_W = 100;
- const LABEL_H = 18;
- const LABEL_GAP = 6;
+ const LABEL_W = 118;
+ const LABEL_H = 22;
return edgesWithPing.map((e, idx) => {
const dimFactor =
hoveredEdgeKey && hoveredEdgeKey !== e.edgeKey ? 0.25 : 1;
- const strokeWidth = hoveredEdgeKey === e.edgeKey ? 3.2 : 2.5;
+ const strokeWidth = hoveredEdgeKey === e.edgeKey ? 3 : 2;
+ const edgeColor = getTunnelColor(e.tunnelType);
return (
{(() => {
- const labels = [
- e.pingLabel ? { text: e.pingLabel, stale: e.pingStale } : null,
- e.speedLabel ? { text: e.speedLabel, stale: e.speedStale } : null,
- ].filter(Boolean);
- if (labels.length === 0) return null;
- const totalH = labels.length * LABEL_H + (labels.length - 1) * LABEL_GAP;
+ if (!e.edgeLabel) return null;
const minDist = 70;
let level = 0;
let x; let y;
@@ -385,54 +421,47 @@ export default function NetworkMapUnifi({
}
used.push({ x, y });
- const pad = 8;
- const staleColor = '#f59e0b';
+ const pad = 10;
+ const isStale = e.stale === true;
+ const staleColor = UNIFI_WARNING;
+ const textWidth = e.edgeLabel.length * 6.2;
+ const iconWidth = isStale ? 14 : 0;
+ const w = Math.max(LABEL_W, Math.min(textWidth + pad * 2 + iconWidth, 210));
+ const h = LABEL_H;
+ const textX = x - (iconWidth / 2);
return (
- {labels.map((label, i) => {
- const yy = y + i * (LABEL_H + LABEL_GAP) + LABEL_H / 2;
- const isStale = label.stale === true;
- const textWidth = label.text.length * 6;
- const iconWidth = isStale ? 14 : 0;
- const w = Math.max(LABEL_W, Math.min(textWidth + pad * 2 + iconWidth, 180));
- const h = LABEL_H + 4;
- const textX = x - (iconWidth / 2);
- return (
-
-
- {isStale && (
-
- )}
-
- {label.text}
-
-
- );
- })}
+
+ {isStale && (
+
+ )}
+
+ {e.edgeLabel}
+
);
})()}
@@ -447,6 +476,7 @@ export default function NetworkMapUnifi({
const pos = positions[String(s.ip)];
if (!pos) return null;
const label = s.dns?.split('.')[0] || s.ip || '?';
+ const typeMeta = getNodeTypeMeta(s.type);
const isDragging = drag?.nodeId === s.ip;
return (
{/* Флаг страны в левом верхнем углу карточки (картинка, т.к. эмодзи в SVG часто отображаются как буквы) */}
@@ -488,29 +519,50 @@ export default function NetworkMapUnifi({
})()}
{/* Иконка сервера в стиле UNIFI: прямоугольник с «портами» */}
-
-
-
+
+
+
- {label.length > 12 ? label.slice(0, 11) + '…' : label}
+ {label.length > 14 ? `${label.slice(0, 13)}…` : label}
{s.ip}
+
+
+ {typeMeta.label}
+
);