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');
+
-- --------------------------------------------------------
--
|