diff --git a/app/index.php b/app/index.php index 75c76f8..ca2fa1e 100644 --- a/app/index.php +++ b/app/index.php @@ -8,7 +8,7 @@ $module = $_GET['module'] ?? 'dashboard'; $action = $_GET['action'] ?? 'list'; $validModules = ['dashboard', 'locations', 'buildings', 'rooms', 'device_types', 'devices', 'racks', 'floors', 'floor_infrastructure', 'connections', 'port_types']; -$validActions = ['list', 'edit', 'save', 'ports', 'delete']; +$validActions = ['list', 'edit', 'save', 'ports', 'delete', 'swap']; if (!in_array($module, $validModules, true)) { renderClientError(400, 'Ungueltiges Modul'); @@ -20,7 +20,7 @@ if (!in_array($action, $validActions, true)) { exit; } -if (!in_array($action, ['save', 'delete'], true)) { +if (!in_array($action, ['save', 'delete', 'swap'], true)) { require_once __DIR__ . '/templates/header.php'; } @@ -32,6 +32,6 @@ if (file_exists($modulePath)) { renderClientError(404, 'Die angeforderte Seite existiert nicht.'); } -if (!in_array($action, ['save', 'delete'], true)) { +if (!in_array($action, ['save', 'delete', 'swap'], true)) { require_once __DIR__ . '/templates/footer.php'; } diff --git a/app/modules/connections/edit.php b/app/modules/connections/edit.php index 0a0d75a..331eb86 100644 --- a/app/modules/connections/edit.php +++ b/app/modules/connections/edit.php @@ -46,6 +46,68 @@ $endpointOptions = [ 'patchpanel' => [], ]; +$occupiedByType = [ + 'device' => [], + 'module' => [], + 'outlet' => [], + 'patchpanel' => [], +]; +$occupiedRows = $sql->get( + "SELECT id, port_a_type, port_a_id, port_b_type, port_b_id + FROM connections + WHERE id <> ?", + "i", + [$connectionId] +); +foreach ((array)$occupiedRows as $row) { + $typeA = $normalizePortType((string)($row['port_a_type'] ?? '')); + $idA = (int)($row['port_a_id'] ?? 0); + if ($idA > 0 && isset($occupiedByType[$typeA])) { + $occupiedByType[$typeA][$idA] = true; + } + + $typeB = $normalizePortType((string)($row['port_b_type'] ?? '')); + $idB = (int)($row['port_b_id'] ?? 0); + if ($idB > 0 && isset($occupiedByType[$typeB])) { + $occupiedByType[$typeB][$idB] = true; + } +} + +$isEndpointAllowed = static function (string $type, int $id) use ($occupiedByType, $portAType, $portAId, $portBType, $portBId): bool { + if ($id <= 0) { + return false; + } + if ($type === $portAType && $id === $portAId) { + return true; + } + if ($type === $portBType && $id === $portBId) { + return true; + } + return empty($occupiedByType[$type][$id]); +}; + +// Auto-heal: ensure each outlet has at least one selectable port. +$outletsWithoutPorts = $sql->get( + "SELECT o.id + FROM network_outlets o + LEFT JOIN network_outlet_ports nop ON nop.outlet_id = o.id + GROUP BY o.id + HAVING COUNT(nop.id) = 0", + "", + [] +); +foreach ((array)$outletsWithoutPorts as $outletRow) { + $outletId = (int)($outletRow['id'] ?? 0); + if ($outletId <= 0) { + continue; + } + $sql->set( + "INSERT INTO network_outlet_ports (outlet_id, name) VALUES (?, 'Port 1')", + "i", + [$outletId] + ); +} + $devicePorts = $sql->get( "SELECT dp.id, dp.name, d.name AS owner_name FROM device_ports dp @@ -55,8 +117,12 @@ $devicePorts = $sql->get( [] ); foreach ($devicePorts as $row) { + $id = (int)$row['id']; + if (!$isEndpointAllowed('device', $id)) { + continue; + } $endpointOptions['device'][] = [ - 'id' => (int)$row['id'], + 'id' => $id, 'label' => $row['owner_name'] . ' / ' . $row['name'], ]; } @@ -78,27 +144,35 @@ $modulePorts = $sql->get( [] ); foreach ($modulePorts as $row) { + $id = (int)$row['id']; + if (!$isEndpointAllowed('module', $id)) { + continue; + } $deviceName = trim((string)($row['device_name'] ?? '')) ?: 'Unzugeordnet'; $endpointOptions['module'][] = [ - 'id' => (int)$row['id'], + 'id' => $id, 'label' => $deviceName . ' / ' . $row['module_name'] . ' / ' . $row['name'], ]; } $outletPorts = $sql->get( - "SELECT nop.id, nop.name, no.name AS outlet_name, r.name AS room_name, f.name AS floor_name + "SELECT nop.id, nop.name, o.name AS outlet_name, r.name AS room_name, f.name AS floor_name FROM network_outlet_ports nop - JOIN network_outlets no ON no.id = nop.outlet_id - LEFT JOIN rooms r ON r.id = no.room_id + JOIN network_outlets o ON o.id = nop.outlet_id + LEFT JOIN rooms r ON r.id = o.room_id LEFT JOIN floors f ON f.id = r.floor_id ORDER BY floor_name, room_name, outlet_name, nop.name", "", [] ); foreach ($outletPorts as $row) { + $id = (int)$row['id']; + if (!$isEndpointAllowed('outlet', $id)) { + continue; + } $parts = array_filter([(string)($row['floor_name'] ?? ''), (string)($row['room_name'] ?? ''), (string)$row['outlet_name'], (string)$row['name']]); $endpointOptions['outlet'][] = [ - 'id' => (int)$row['id'], + 'id' => $id, 'label' => implode(' / ', $parts), ]; } @@ -113,9 +187,13 @@ $patchpanelPorts = $sql->get( [] ); foreach ($patchpanelPorts as $row) { + $id = (int)$row['id']; + if (!$isEndpointAllowed('patchpanel', $id)) { + continue; + } $parts = array_filter([(string)($row['floor_name'] ?? ''), (string)$row['patchpanel_name'], (string)$row['name']]); $endpointOptions['patchpanel'][] = [ - 'id' => (int)$row['id'], + 'id' => $id, 'label' => implode(' / ', $parts), ]; } diff --git a/app/modules/connections/list.php b/app/modules/connections/list.php index 58b0d0e..192bc8f 100644 --- a/app/modules/connections/list.php +++ b/app/modules/connections/list.php @@ -42,11 +42,11 @@ $endpointUnionSql = " 'outlet' AS endpoint_type, nop.id AS endpoint_id, nop.name AS port_name, - CONCAT(no.name, ' / ', IFNULL(r.name, ''), ' / ', IFNULL(f.name, '')) AS owner_name, + CONCAT(o.name, ' / ', IFNULL(r.name, ''), ' / ', IFNULL(f.name, '')) AS owner_name, NULL AS owner_device_id FROM network_outlet_ports nop - JOIN network_outlets no ON no.id = nop.outlet_id - LEFT JOIN rooms r ON r.id = no.room_id + JOIN network_outlets o ON o.id = nop.outlet_id + LEFT JOIN rooms r ON r.id = o.room_id LEFT JOIN floors f ON f.id = r.floor_id UNION ALL SELECT @@ -328,6 +328,7 @@ if ($deviceId > 0) { Bearbeiten + Von/Nach tauschen get( + "SELECT id, port_a_type, port_a_id, port_b_type, port_b_id + FROM connections + WHERE id <> ?", + "i", + [$connId] +); + +$isEndpointUsed = static function (string $endpointType, int $endpointId) use ($otherConnections, $normalizePortType): bool { + if ($endpointId <= 0) { + return false; + } + foreach ((array)$otherConnections as $row) { + $typeA = $normalizePortType((string)($row['port_a_type'] ?? '')); + $idA = (int)($row['port_a_id'] ?? 0); + if ($typeA === $endpointType && $idA === $endpointId) { + return true; + } + + $typeB = $normalizePortType((string)($row['port_b_type'] ?? '')); + $idB = (int)($row['port_b_id'] ?? 0); + if ($typeB === $endpointType && $idB === $endpointId) { + return true; + } + } + return false; +}; + +if ($isEndpointUsed($portAType, $portAId)) { + $errors[] = "Port an Endpunkt A ist bereits in Verwendung"; +} +if ($isEndpointUsed($portBType, $portBId)) { + $errors[] = "Port an Endpunkt B ist bereits in Verwendung"; +} + if (!empty($errors)) { $_SESSION['error'] = implode(', ', $errors); $redirectUrl = $connId ? "?module=connections&action=edit&id=$connId" : "?module=connections&action=edit"; @@ -67,15 +102,36 @@ if ($connId > 0) { // UPDATE $sql->set( "UPDATE connections SET port_a_type = ?, port_a_id = ?, port_b_type = ?, port_b_id = ?, vlan_config = ?, comment = ? WHERE id = ?", - "siisisi", + "sisissi", [$portAType, $portAId, $portBType, $portBId, $vlanJson, $comment, $connId] ); } else { + $connectionTypeId = (int)($sql->single( + "SELECT id FROM connection_types ORDER BY id LIMIT 1", + "", + [] + )['id'] ?? 0); + + if ($connectionTypeId <= 0) { + $connectionTypeId = (int)$sql->set( + "INSERT INTO connection_types (name, medium, duplex, line_style, comment) VALUES (?, ?, ?, ?, ?)", + "sssss", + ['Default', 'copper', 'custom', 'solid', 'Auto-created by connections/save'], + true + ); + } + + if ($connectionTypeId <= 0) { + $_SESSION['error'] = "Kein Verbindungstyp verfuegbar"; + header("Location: ?module=connections&action=edit"); + exit; + } + // INSERT $sql->set( - "INSERT INTO connections (port_a_type, port_a_id, port_b_type, port_b_id, vlan_config, comment) VALUES (?, ?, ?, ?, ?, ?)", - "siisis", - [$portAType, $portAId, $portBType, $portBId, $vlanJson, $comment] + "INSERT INTO connections (connection_type_id, port_a_type, port_a_id, port_b_type, port_b_id, vlan_config, comment) VALUES (?, ?, ?, ?, ?, ?, ?)", + "isisiss", + [$connectionTypeId, $portAType, $portAId, $portBType, $portBId, $vlanJson, $comment] ); } diff --git a/app/modules/connections/swap.php b/app/modules/connections/swap.php new file mode 100644 index 0000000..9480077 --- /dev/null +++ b/app/modules/connections/swap.php @@ -0,0 +1,46 @@ +single( + "SELECT id, port_a_type, port_a_id, port_b_type, port_b_id + FROM connections + WHERE id = ?", + "i", + [$connectionId] +); + +if (!$connection) { + $_SESSION['error'] = 'Verbindung nicht gefunden'; + header('Location: ?module=connections&action=list'); + exit; +} + +$sql->set( + "UPDATE connections + SET port_a_type = ?, port_a_id = ?, port_b_type = ?, port_b_id = ? + WHERE id = ?", + "sisii", + [ + (string)$connection['port_b_type'], + (int)$connection['port_b_id'], + (string)$connection['port_a_type'], + (int)$connection['port_a_id'], + $connectionId + ] +); + +$_SESSION['success'] = 'Endpunkte wurden vertauscht'; +header('Location: ?module=connections&action=list'); +exit; diff --git a/app/modules/floor_infrastructure/save.php b/app/modules/floor_infrastructure/save.php index bd44496..c416bee 100644 --- a/app/modules/floor_infrastructure/save.php +++ b/app/modules/floor_infrastructure/save.php @@ -21,6 +21,8 @@ if ($type === 'patchpanel') { $portCount = (int)($_POST['port_count'] ?? 0); $comment = trim($_POST['comment'] ?? ''); + $panelId = $id; + if ($id > 0) { $sql->set( "UPDATE floor_patchpanels SET name = ?, floor_id = ?, pos_x = ?, pos_y = ?, width = ?, height = ?, port_count = ?, comment = ? WHERE id = ?", @@ -28,18 +30,38 @@ if ($type === 'patchpanel') { [$name, $floorId, $posX, $posY, $width, $height, $portCount, $comment, $id] ); } else { - $sql->set( + $panelId = (int)$sql->set( "INSERT INTO floor_patchpanels (name, floor_id, pos_x, pos_y, width, height, port_count, comment) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", "siiiiiss", - [$name, $floorId, $posX, $posY, $width, $height, $portCount, $comment] + [$name, $floorId, $posX, $posY, $width, $height, $portCount, $comment], + true ); } + + if ($panelId > 0 && $portCount > 0) { + $existingCount = (int)($sql->single( + "SELECT COUNT(*) AS cnt FROM floor_patchpanel_ports WHERE patchpanel_id = ?", + "i", + [$panelId] + )['cnt'] ?? 0); + + if ($existingCount < $portCount) { + for ($i = $existingCount + 1; $i <= $portCount; $i++) { + $sql->set( + "INSERT INTO floor_patchpanel_ports (patchpanel_id, name) VALUES (?, ?)", + "is", + [$panelId, 'Port ' . $i] + ); + } + } + } } elseif ($type === 'outlet') { $name = trim($_POST['name'] ?? ''); $roomId = (int)($_POST['room_id'] ?? 0); $x = (int)($_POST['x'] ?? 0); $y = (int)($_POST['y'] ?? 0); $comment = trim($_POST['comment'] ?? ''); + $outletId = $id; if ($id > 0) { $sql->set( @@ -48,12 +70,29 @@ if ($type === 'patchpanel') { [$name, $roomId, $x, $y, $comment, $id] ); } else { - $sql->set( + $outletId = (int)$sql->set( "INSERT INTO network_outlets (name, room_id, x, y, comment) VALUES (?, ?, ?, ?, ?)", "siiis", - [$name, $roomId, $x, $y, $comment] + [$name, $roomId, $x, $y, $comment], + true ); } + + if ($outletId > 0) { + $existingPortCount = (int)($sql->single( + "SELECT COUNT(*) AS cnt FROM network_outlet_ports WHERE outlet_id = ?", + "i", + [$outletId] + )['cnt'] ?? 0); + + if ($existingPortCount === 0) { + $sql->set( + "INSERT INTO network_outlet_ports (outlet_id, name) VALUES (?, 'Port 1')", + "i", + [$outletId] + ); + } + } } header('Location: ?module=floor_infrastructure&action=list'); diff --git a/init.sql b/init.sql index a78af85..4b0252b 100644 --- a/init.sql +++ b/init.sql @@ -44,9 +44,9 @@ INSERT INTO `buildings` (`id`, `location_id`, `name`, `comment`) VALUES CREATE TABLE `connections` ( `id` int(11) NOT NULL, `connection_type_id` int(11) NOT NULL, - `port_a_type` enum('device','module','outlet') NOT NULL, + `port_a_type` enum('device','module','outlet','patchpanel') NOT NULL, `port_a_id` int(11) NOT NULL, - `port_b_type` enum('device','module','outlet') NOT NULL, + `port_b_type` enum('device','module','outlet','patchpanel') NOT NULL, `port_b_id` int(11) NOT NULL, `vlan_config` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`vlan_config`)), `mode` varchar(50) DEFAULT NULL, @@ -70,6 +70,13 @@ CREATE TABLE `connection_types` ( `comment` text DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +-- +-- Daten fuer Tabelle `connection_types` +-- + +INSERT INTO `connection_types` (`id`, `name`, `medium`, `duplex`, `max_speed`, `color`, `line_style`, `comment`) VALUES +(1, 'Default', 'copper', 'custom', NULL, NULL, 'solid', 'Auto-created default type'); + -- -------------------------------------------------------- --