refactor(NetworkMapUnifi): improve layout computation by grouping server types and enhancing curve point calculations for better visualization
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 2m5s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 2m5s
This commit is contained in:
@@ -9,23 +9,47 @@ const UNIFI_TEXT = '#f1f5f9';
|
|||||||
const UNIFI_TEXT_MUTED = '#94a3b8';
|
const UNIFI_TEXT_MUTED = '#94a3b8';
|
||||||
const NODE_WIDTH = 88;
|
const NODE_WIDTH = 88;
|
||||||
const NODE_HEIGHT = 52;
|
const NODE_HEIGHT = 52;
|
||||||
const CIRCLE_RADIUS = 240;
|
|
||||||
const STORAGE_KEY = 'network-map-unifi-positions';
|
const STORAGE_KEY = 'network-map-unifi-positions';
|
||||||
|
|
||||||
function computeCircleLayout(servers, size) {
|
function computeInitialLayout(servers, size) {
|
||||||
const n = servers.length;
|
|
||||||
if (n === 0) return {};
|
|
||||||
const cx = size.w / 2;
|
|
||||||
const cy = size.h / 2;
|
|
||||||
const r = Math.min(CIRCLE_RADIUS, Math.min(size.w, size.h) * 0.4);
|
|
||||||
const positions = {};
|
const positions = {};
|
||||||
servers.forEach((s, i) => {
|
if (!servers.length) return positions;
|
||||||
const angle = (2 * Math.PI * i) / n - Math.PI / 2;
|
|
||||||
positions[String(s.ip)] = {
|
// Группируем: сначала home, затем jumphost, затем exit, затем остальные
|
||||||
x: cx + r * Math.cos(angle),
|
const homes = servers.filter((s) => String(s.type || '').toLowerCase() === 'home');
|
||||||
y: cy + r * Math.sin(angle),
|
const jumphosts = servers.filter((s) => String(s.type || '').toLowerCase() === 'jumphost');
|
||||||
};
|
const exits = servers.filter((s) => String(s.type || '').toLowerCase() === 'exit');
|
||||||
|
const others = servers.filter((s) => {
|
||||||
|
const t = String(s.type || '').toLowerCase();
|
||||||
|
return t !== 'home' && t !== 'jumphost' && t !== 'exit';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const columns = [homes, jumphosts, exits, others].filter((col) => col.length > 0);
|
||||||
|
const colCount = columns.length || 1;
|
||||||
|
const paddingX = 80;
|
||||||
|
const usableWidth = Math.max(200, size.w - paddingX * 2);
|
||||||
|
const colStep = usableWidth / Math.max(1, colCount - 1);
|
||||||
|
const baseX = paddingX;
|
||||||
|
|
||||||
|
columns.forEach((col, colIdx) => {
|
||||||
|
// В пределах колонки сортируем по dns/ip для стабильности
|
||||||
|
const sorted = [...col].sort((a, b) => {
|
||||||
|
const la = (a.dns || a.ip || '').toString();
|
||||||
|
const lb = (b.dns || b.ip || '').toString();
|
||||||
|
return la.localeCompare(lb);
|
||||||
|
});
|
||||||
|
const n = sorted.length;
|
||||||
|
const x = baseX + colStep * colIdx;
|
||||||
|
const topPadding = 60;
|
||||||
|
const bottomPadding = 60;
|
||||||
|
const usableHeight = Math.max(120, size.h - topPadding - bottomPadding);
|
||||||
|
sorted.forEach((s, i) => {
|
||||||
|
const t = n > 1 ? i / (n - 1) : 0.5;
|
||||||
|
const y = topPadding + usableHeight * t;
|
||||||
|
positions[String(s.ip)] = { x, y };
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return positions;
|
return positions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,14 +86,14 @@ export default function NetworkMapUnifi({ servers = [], connections = [], pingMa
|
|||||||
const raw = window.localStorage.getItem(STORAGE_KEY);
|
const raw = window.localStorage.getItem(STORAGE_KEY);
|
||||||
if (raw) saved = JSON.parse(raw) || {};
|
if (raw) saved = JSON.parse(raw) || {};
|
||||||
} catch {}
|
} catch {}
|
||||||
const circle = computeCircleLayout(servers, size);
|
const initial = computeInitialLayout(servers, size);
|
||||||
const merged = {};
|
const merged = {};
|
||||||
servers.forEach((s, i) => {
|
servers.forEach((s, i) => {
|
||||||
const ip = String(s.ip);
|
const ip = String(s.ip);
|
||||||
if (saved[ip] && typeof saved[ip].x === 'number' && typeof saved[ip].y === 'number') {
|
if (saved[ip] && typeof saved[ip].x === 'number' && typeof saved[ip].y === 'number') {
|
||||||
merged[ip] = { x: saved[ip].x, y: saved[ip].y };
|
merged[ip] = { x: saved[ip].x, y: saved[ip].y };
|
||||||
} else {
|
} else {
|
||||||
merged[ip] = circle[ip] ?? { x: size.w / 2, y: size.h / 2 };
|
merged[ip] = initial[ip] ?? { x: size.w / 2, y: size.h / 2 };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
setPositions(merged);
|
setPositions(merged);
|
||||||
@@ -117,10 +141,10 @@ export default function NetworkMapUnifi({ servers = [], connections = [], pingMa
|
|||||||
}, [drag, getSvgCoords]);
|
}, [drag, getSvgCoords]);
|
||||||
|
|
||||||
const resetLayout = useCallback(() => {
|
const resetLayout = useCallback(() => {
|
||||||
const circle = computeCircleLayout(servers, size);
|
const initial = computeInitialLayout(servers, size);
|
||||||
setPositions(circle);
|
setPositions(initial);
|
||||||
try {
|
try {
|
||||||
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(circle));
|
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(initial));
|
||||||
} catch {}
|
} catch {}
|
||||||
}, [servers, size]);
|
}, [servers, size]);
|
||||||
|
|
||||||
@@ -158,36 +182,60 @@ export default function NetworkMapUnifi({ servers = [], connections = [], pingMa
|
|||||||
}).filter((e) => e.p1 && e.p2);
|
}).filter((e) => e.p1 && e.p2);
|
||||||
}, [connections, positions, pingMap]);
|
}, [connections, positions, pingMap]);
|
||||||
|
|
||||||
const pathD = useCallback((e) => {
|
// Вычисляем старт/финиш и контрольную точку так, чтобы рёбра выходили
|
||||||
|
// с «боков» карточек и красиво изгибались (как в UniFi).
|
||||||
|
const computeCurvePoints = useCallback((e) => {
|
||||||
const { p1, p2 } = e;
|
const { p1, p2 } = e;
|
||||||
const mx = (p1.x + p2.x) / 2;
|
|
||||||
const my = (p1.y + p2.y) / 2;
|
|
||||||
const dx = p2.x - p1.x;
|
const dx = p2.x - p1.x;
|
||||||
const dy = p2.y - p1.y;
|
const dy = p2.y - p1.y;
|
||||||
const len = Math.hypot(dx, dy) || 1;
|
const adx = Math.abs(dx);
|
||||||
const perpX = (-dy / len) * 35;
|
const ady = Math.abs(dy);
|
||||||
const perpY = (dx / len) * 35;
|
|
||||||
|
let sx = p1.x;
|
||||||
|
let sy = p1.y;
|
||||||
|
let ex = p2.x;
|
||||||
|
let ey = p2.y;
|
||||||
|
|
||||||
|
if (adx >= ady) {
|
||||||
|
const sign = dx >= 0 ? 1 : -1;
|
||||||
|
sx = p1.x + sign * (NODE_WIDTH / 2);
|
||||||
|
sy = p1.y;
|
||||||
|
ex = p2.x - sign * (NODE_WIDTH / 2);
|
||||||
|
ey = p2.y;
|
||||||
|
} else {
|
||||||
|
const sign = dy >= 0 ? 1 : -1;
|
||||||
|
sx = p1.x;
|
||||||
|
sy = p1.y + sign * (NODE_HEIGHT / 2);
|
||||||
|
ex = p2.x;
|
||||||
|
ey = p2.y - sign * (NODE_HEIGHT / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mx = (sx + ex) / 2;
|
||||||
|
const my = (sy + ey) / 2;
|
||||||
|
const ddx = ex - sx;
|
||||||
|
const ddy = ey - sy;
|
||||||
|
const len = Math.hypot(ddx, ddy) || 1;
|
||||||
|
const baseOffset = 40;
|
||||||
|
const perpX = (-ddy / len) * baseOffset;
|
||||||
|
const perpY = (ddx / len) * baseOffset;
|
||||||
const cpx = mx + perpX;
|
const cpx = mx + perpX;
|
||||||
const cpy = my + perpY;
|
const cpy = my + perpY;
|
||||||
return `M ${p1.x} ${p1.y} Q ${cpx} ${cpy} ${p2.x} ${p2.y}`;
|
|
||||||
|
return { sx, sy, ex, ey, cpx, cpy };
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const pathD = useCallback((e) => {
|
||||||
|
const { sx, sy, ex, ey, cpx, cpy } = computeCurvePoints(e);
|
||||||
|
return `M ${sx} ${sy} Q ${cpx} ${cpy} ${ex} ${ey}`;
|
||||||
|
}, [computeCurvePoints]);
|
||||||
|
|
||||||
const labelPoint = useCallback((e) => {
|
const labelPoint = useCallback((e) => {
|
||||||
const { p1, p2 } = e;
|
const { sx, sy, ex, ey, cpx, cpy } = computeCurvePoints(e);
|
||||||
const mx = (p1.x + p2.x) / 2;
|
|
||||||
const my = (p1.y + p2.y) / 2;
|
|
||||||
const dx = p2.x - p1.x;
|
|
||||||
const dy = p2.y - p1.y;
|
|
||||||
const len = Math.hypot(dx, dy) || 1;
|
|
||||||
const perpX = (-dy / len) * 35;
|
|
||||||
const perpY = (dx / len) * 35;
|
|
||||||
const cpx = mx + perpX;
|
|
||||||
const cpy = my + perpY;
|
|
||||||
const t = 0.5;
|
const t = 0.5;
|
||||||
const lx = (1 - t) * (1 - t) * p1.x + 2 * (1 - t) * t * cpx + t * t * p2.x;
|
const lx = (1 - t) * (1 - t) * sx + 2 * (1 - t) * t * cpx + t * t * ex;
|
||||||
const ly = (1 - t) * (1 - t) * p1.y + 2 * (1 - t) * t * cpy + t * t * p2.y;
|
const ly = (1 - t) * (1 - t) * sy + 2 * (1 - t) * t * cpy + t * t * ey;
|
||||||
return { x: lx, y: ly };
|
return { x: lx, y: ly };
|
||||||
}, []);
|
}, [computeCurvePoints]);
|
||||||
|
|
||||||
if (servers.length === 0) return null;
|
if (servers.length === 0) return null;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user