Files
netwatch/app/assets/js/network-view.js
2026-02-16 13:56:01 +01:00

263 lines
7.9 KiB
JavaScript

(() => {
const svgElement = document.querySelector('#network-svg');
if (!svgElement) {
return;
}
const CONTEXT_TYPE = svgElement.dataset.contextType || 'all';
const CONTEXT_ID = Number(svgElement.dataset.contextId || 0);
const API_LOAD_NETWORK = '/api/connections.php?action=load';
const API_SAVE_POSITIONS = '/api/network_view.php?action=save_positions';
let devices = [];
let ports = [];
let connections = [];
let selectedDeviceId = null;
let isDragging = false;
let dragOffset = { x: 0, y: 0 };
bindSvgEvents();
loadNetwork();
function bindSvgEvents() {
svgElement.addEventListener('mousemove', onMouseMove);
svgElement.addEventListener('mouseup', onMouseUp);
svgElement.addEventListener('click', onSvgClick);
}
function buildLoadUrl() {
const params = new URLSearchParams();
params.set('action', 'load');
params.set('context_type', CONTEXT_TYPE);
if (CONTEXT_TYPE !== 'all') {
params.set('context_id', String(CONTEXT_ID));
}
return '/api/connections.php?' + params.toString();
}
function loadNetwork() {
fetch(buildLoadUrl())
.then((res) => res.json())
.then((data) => {
if (!data || !Array.isArray(data.devices) || !Array.isArray(data.connections)) {
throw new Error('Antwortformat ungueltig');
}
devices = data.devices.map((device, index) => ({
...device,
x: Number(device.pos_x ?? device.x ?? 50 + (index % 6) * 150),
y: Number(device.pos_y ?? device.y ?? 60 + Math.floor(index / 6) * 120)
}));
ports = Array.isArray(data.ports) ? data.ports : [];
connections = data.connections;
renderAll();
})
.catch((err) => {
console.error('Fehler beim Laden der Netzwerkansicht', err);
});
}
function renderAll() {
clearSvg();
renderConnections();
renderDevices();
}
function clearSvg() {
while (svgElement.firstChild) {
svgElement.removeChild(svgElement.firstChild);
}
}
function renderDevices() {
devices.forEach((device) => renderDevice(device));
}
function renderDevice(device) {
const group = document.createElementNS('http://www.w3.org/2000/svg', 'g');
group.classList.add('device-node');
group.dataset.id = device.id;
group.setAttribute('transform', `translate(${device.x || 0}, ${device.y || 0})`);
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
rect.setAttribute('width', 120);
rect.setAttribute('height', 60);
rect.setAttribute('rx', 6);
rect.classList.add('device-node-rect');
rect.addEventListener('mousedown', (e) => {
startDrag(e, device.id);
e.stopPropagation();
});
const text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
text.setAttribute('x', 60);
text.setAttribute('y', 35);
text.setAttribute('text-anchor', 'middle');
text.textContent = device.name || 'Device';
group.appendChild(rect);
group.appendChild(text);
const devicePorts = ports.filter((port) => Number(port.device_id) === Number(device.id));
const spacing = 120 / (Math.max(1, devicePorts.length) + 1);
devicePorts.forEach((port, index) => {
const dot = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
dot.setAttribute('cx', String(Math.round((index + 1) * spacing)));
dot.setAttribute('cy', '62');
dot.setAttribute('r', '3');
dot.classList.add('device-port-dot');
dot.dataset.portId = String(port.id);
dot.dataset.deviceId = String(device.id);
dot.addEventListener('click', (event) => {
event.stopPropagation();
console.info('Port ausgewaehlt', port.id);
});
group.appendChild(dot);
});
svgElement.appendChild(group);
}
function renderConnections() {
connections.forEach((connection) => renderConnection(connection));
}
function renderConnection(connection) {
const sourcePort = ports.find((port) => Number(port.id) === Number(connection.port_a_id));
const targetPort = ports.find((port) => Number(port.id) === Number(connection.port_b_id));
if (!sourcePort || !targetPort) {
return;
}
const sourceDevice = devices.find((device) => Number(device.id) === Number(sourcePort.device_id));
const targetDevice = devices.find((device) => Number(device.id) === Number(targetPort.device_id));
if (!sourceDevice || !targetDevice) {
return;
}
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
line.setAttribute('x1', String(sourceDevice.x + 60));
line.setAttribute('y1', String(sourceDevice.y + 60));
line.setAttribute('x2', String(targetDevice.x + 60));
line.setAttribute('y2', String(targetDevice.y + 60));
const isFiber = String(connection.mode || '').toLowerCase().includes('fiber');
line.classList.add('connection-line');
line.setAttribute('stroke', isFiber ? '#2f6fef' : '#1f8b4c');
line.setAttribute('stroke-width', isFiber ? '2.5' : '2');
line.setAttribute('stroke-dasharray', isFiber ? '6 4' : '');
svgElement.appendChild(line);
}
function onSvgClick(event) {
if (event.target === svgElement) {
selectedDeviceId = null;
updateSelection();
}
}
function startDrag(event, deviceId) {
const device = getDeviceById(deviceId);
if (!device) return;
isDragging = true;
selectedDeviceId = deviceId;
const point = getSvgCoordinates(event);
dragOffset.x = (device.x || 0) - point.x;
dragOffset.y = (device.y || 0) - point.y;
updateSelection();
}
function onMouseMove(event) {
if (!isDragging || !selectedDeviceId) return;
const device = getDeviceById(selectedDeviceId);
if (!device) return;
const point = getSvgCoordinates(event);
device.x = point.x + dragOffset.x;
device.y = point.y + dragOffset.y;
renderAll();
}
function onMouseUp() {
if (!isDragging) {
return;
}
isDragging = false;
}
function updateSelection() {
svgElement.querySelectorAll('.device-node').forEach((el) => {
el.classList.toggle('selected', el.dataset.id === String(selectedDeviceId));
});
const sidebar = document.querySelector('[data-network-selected-device]');
if (!sidebar) {
return;
}
const device = getDeviceById(selectedDeviceId);
sidebar.textContent = device
? `${device.name} (ID ${device.id})`
: 'Kein Geraet ausgewaehlt';
}
function savePositions() {
fetch(API_SAVE_POSITIONS, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
context_id: CONTEXT_ID,
devices: devices.map((device) => ({ id: device.id, x: device.x, y: device.y }))
})
})
.then((res) => res.json())
.then((data) => {
if (data?.error) {
throw new Error(data.error);
}
alert('Positionen gespeichert');
})
.catch((err) => {
alert('Positionen konnten nicht gespeichert werden: ' + err.message);
});
}
function getSvgCoordinates(event) {
const pt = svgElement.createSVGPoint();
pt.x = event.clientX;
pt.y = event.clientY;
const transformed = pt.matrixTransform(svgElement.getScreenCTM().inverse());
return { x: transformed.x, y: transformed.y };
}
function getDeviceById(id) {
return devices.find((device) => Number(device.id) === Number(id));
}
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape') {
selectedDeviceId = null;
updateSelection();
return;
}
if (event.key === 'Delete' && selectedDeviceId) {
console.warn('Delete von Geraeten ist in der Netzwerkansicht noch nicht implementiert.');
}
if (event.key.toLowerCase() === 's' && (event.ctrlKey || event.metaKey)) {
event.preventDefault();
savePositions();
}
});
})();