(() => { 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(); } }); })();