Gerät-zu-Gerät-Verbindungs-SVG im Dashboard ergänzt

closes #29
This commit is contained in:
bigserver
2026-04-09 08:31:52 +02:00
parent 65d46d925d
commit 29fc683675

View File

@@ -245,6 +245,106 @@ foreach ($rackLinksByKey as $entry) {
'sample_connection_id' => (int)($entry['sample_connection_id'] ?? 0),
];
}
$deviceIdByDevicePort = [];
foreach ($sql->get(
"SELECT id, device_id
FROM device_ports",
"",
[]
) as $row) {
$portId = (int)($row['id'] ?? 0);
$deviceId = (int)($row['device_id'] ?? 0);
if ($portId <= 0 || $deviceId <= 0) {
continue;
}
$deviceIdByDevicePort[$portId] = $deviceId;
}
$deviceIdByModulePort = [];
foreach ($sql->get(
"SELECT mp.id AS port_id, MIN(dp.device_id) AS device_id
FROM module_ports mp
JOIN modules m ON m.id = mp.module_id
JOIN device_port_modules dpm ON dpm.module_id = m.id
JOIN device_ports dp ON dp.id = dpm.device_port_id
GROUP BY mp.id",
"",
[]
) as $row) {
$portId = (int)($row['port_id'] ?? 0);
$deviceId = (int)($row['device_id'] ?? 0);
if ($portId <= 0 || $deviceId <= 0) {
continue;
}
$deviceIdByModulePort[$portId] = $deviceId;
}
$resolveDeviceId = static function (string $endpointType, int $endpointId) use ($deviceIdByDevicePort, $deviceIdByModulePort): int {
if ($endpointId <= 0) {
return 0;
}
$type = strtolower(trim($endpointType));
if ($type === 'device' || $type === 'device_ports') {
return (int)($deviceIdByDevicePort[$endpointId] ?? 0);
}
if ($type === 'module' || $type === 'module_ports') {
return (int)($deviceIdByModulePort[$endpointId] ?? 0);
}
return 0;
};
$deviceLinksByKey = [];
foreach ($sql->get(
"SELECT id, port_a_type, port_a_id, port_b_type, port_b_id
FROM connections",
"",
[]
) as $row) {
$deviceA = $resolveDeviceId((string)($row['port_a_type'] ?? ''), (int)($row['port_a_id'] ?? 0));
$deviceB = $resolveDeviceId((string)($row['port_b_type'] ?? ''), (int)($row['port_b_id'] ?? 0));
if ($deviceA <= 0 || $deviceB <= 0 || $deviceA === $deviceB) {
continue;
}
$from = min($deviceA, $deviceB);
$to = max($deviceA, $deviceB);
$key = $from . ':' . $to;
if (!isset($deviceLinksByKey[$key])) {
$deviceLinksByKey[$key] = [
'from_device_id' => $from,
'to_device_id' => $to,
'count' => 0,
'sample_connection_id' => (int)($row['id'] ?? 0)
];
}
$deviceLinksByKey[$key]['count']++;
}
$deviceNameById = [];
foreach ($topologyPayload as $entry) {
$deviceId = (int)($entry['device_id'] ?? 0);
if ($deviceId <= 0 || isset($deviceNameById[$deviceId])) {
continue;
}
$deviceNameById[$deviceId] = (string)($entry['device_name'] ?? ('Gerät #' . $deviceId));
}
$deviceLinkPayload = [];
foreach ($deviceLinksByKey as $entry) {
$fromId = (int)($entry['from_device_id'] ?? 0);
$toId = (int)($entry['to_device_id'] ?? 0);
$deviceLinkPayload[] = [
'from_device_id' => $fromId,
'to_device_id' => $toId,
'count' => (int)($entry['count'] ?? 0),
'from_device_name' => (string)($deviceNameById[$fromId] ?? ('Gerät #' . $fromId)),
'to_device_name' => (string)($deviceNameById[$toId] ?? ('Gerät #' . $toId)),
'sample_connection_id' => (int)($entry['sample_connection_id'] ?? 0),
];
}
?>
<div class="dashboard">
@@ -343,6 +443,7 @@ foreach ($rackLinksByKey as $entry) {
<script id="dashboard-topology-data" type="application/json"><?php echo json_encode($topologyPayload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?></script>
<script id="dashboard-topology-links" type="application/json"><?php echo json_encode($rackLinkPayload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?></script>
<script id="dashboard-topology-device-links" type="application/json"><?php echo json_encode($deviceLinkPayload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?></script>
<script>
(function () {
const root = document.getElementById('dashboard-topology-wall');
@@ -366,8 +467,10 @@ foreach ($rackLinksByKey as $entry) {
const dataTag = document.getElementById('dashboard-topology-data');
const linkTag = document.getElementById('dashboard-topology-links');
const deviceLinkTag = document.getElementById('dashboard-topology-device-links');
let nodes = [];
let rackLinks = [];
let deviceLinks = [];
try {
nodes = JSON.parse((dataTag && dataTag.textContent) || '[]');
} catch (error) {
@@ -378,6 +481,11 @@ foreach ($rackLinksByKey as $entry) {
} catch (error) {
rackLinks = [];
}
try {
deviceLinks = JSON.parse((deviceLinkTag && deviceLinkTag.textContent) || '[]');
} catch (error) {
deviceLinks = [];
}
const scene = { width: 2400, height: 1400 };
let view = { x: 0, y: 0, width: scene.width, height: scene.height };
@@ -513,6 +621,7 @@ foreach ($rackLinksByKey as $entry) {
const positioned = [];
const rackCenters = new Map();
const deviceCenters = new Map();
let maxY = 1400;
let locationIndex = 0;
@@ -607,6 +716,9 @@ foreach ($rackLinksByKey as $entry) {
port_count: Number(device.port_count || 0),
port_preview: Array.isArray(device.port_preview) ? device.port_preview : []
});
if (Number(device.device_id || 0) > 0) {
deviceCenters.set(Number(device.device_id || 0), { x, y });
}
});
rackCursorY += rackHeight + 16;
@@ -651,7 +763,7 @@ foreach ($rackLinksByKey as $entry) {
const width = Math.max(2400, 220 + locationIndex * 760);
const height = Math.max(1400, Math.ceil(maxY));
return { entries: positioned, rackCenters, width, height };
return { entries: positioned, rackCenters, deviceCenters, width, height };
}
function showOverlay(item) {
@@ -898,6 +1010,30 @@ foreach ($rackLinksByKey as $entry) {
});
});
deviceLinks.forEach((link) => {
const fromDeviceId = Number(link.from_device_id || 0);
const toDeviceId = Number(link.to_device_id || 0);
const count = Math.max(1, Number(link.count || 1));
const fromPoint = layout.deviceCenters.get(fromDeviceId);
const toPoint = layout.deviceCenters.get(toDeviceId);
if (!fromPoint || !toPoint) {
return;
}
const line = svgElement('line');
line.setAttribute('x1', String(fromPoint.x));
line.setAttribute('y1', String(fromPoint.y));
line.setAttribute('x2', String(toPoint.x));
line.setAttribute('y2', String(toPoint.y));
line.setAttribute('class', 'topology-device-link-line');
line.setAttribute('stroke-width', String(Math.min(5, 1 + (count * 0.4))));
const title = svgElement('title');
title.textContent = `${link.from_device_name || `Gerät ${fromDeviceId}`} <-> ${link.to_device_name || `Gerät ${toDeviceId}`}: ${count} Verbindungen`;
line.appendChild(title);
connectionLayer.appendChild(line);
});
rackLinks.forEach((link) => {
const fromRackId = Number(link.from_rack_id || 0);
const toRackId = Number(link.to_rack_id || 0);
@@ -1262,6 +1398,13 @@ foreach ($rackLinksByKey as $entry) {
cursor: pointer;
}
.topology-device-link-line {
stroke: #56a17f;
stroke-opacity: 0.5;
stroke-linecap: round;
pointer-events: none;
}
.topology-connection-line:hover,
.topology-connection-line:focus,
.topology-connection-line.active {