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} + );