$message]); exit; } function normalizeEndpointType(string $type): ?string { $map = [ 'device' => 'device', 'device_ports' => 'device', 'module' => 'module', 'module_ports' => 'module', 'outlet' => 'outlet', 'network_outlet_ports' => 'outlet', 'patchpanel' => 'patchpanel', 'floor_patchpanel_ports' => 'patchpanel', ]; $key = strtolower(trim($type)); return $map[$key] ?? null; } function endpointExists($sql, string $type, int $id): bool { if ($id <= 0) { return false; } if ($type === 'device') { $row = $sql->single('SELECT id FROM device_ports WHERE id = ?', 'i', [$id]); return !empty($row); } if ($type === 'module') { $row = $sql->single('SELECT id FROM module_ports WHERE id = ?', 'i', [$id]); return !empty($row); } if ($type === 'outlet') { $row = $sql->single('SELECT id FROM network_outlet_ports WHERE id = ?', 'i', [$id]); return !empty($row); } if ($type === 'patchpanel') { $row = $sql->single('SELECT id FROM floor_patchpanel_ports WHERE id = ?', 'i', [$id]); return !empty($row); } return false; } function isTopologyPairAllowed(string $typeA, string $typeB): bool { $allowed = ['device' => true, 'module' => true, 'outlet' => true, 'patchpanel' => true]; if (!isset($allowed[$typeA]) || !isset($allowed[$typeB])) { return false; } if ($typeA === 'patchpanel' || $typeB === 'patchpanel') { return ($typeA === 'patchpanel' && in_array($typeB, ['patchpanel', 'outlet'], true)) || ($typeB === 'patchpanel' && in_array($typeA, ['patchpanel', 'outlet'], true)); } return true; } function buildEndpointUsageMap($sql, int $excludeConnectionId = 0): array { $usage = [ 'device' => [], 'module' => [], 'outlet' => [], 'patchpanel' => [], ]; $rows = $sql->get( "SELECT id, port_a_type, port_a_id, port_b_type, port_b_id FROM connections WHERE id <> ?", 'i', [$excludeConnectionId] ); $track = static function (string $endpointType, int $endpointId, string $otherType) use (&$usage): void { if ($endpointId <= 0 || !isset($usage[$endpointType])) { return; } if (!isset($usage[$endpointType][$endpointId])) { $usage[$endpointType][$endpointId] = [ 'total' => 0, 'fixed' => 0, 'patch' => 0, ]; } $usage[$endpointType][$endpointId]['total']++; if (in_array($endpointType, ['outlet', 'patchpanel'], true)) { if (in_array($otherType, ['outlet', 'patchpanel'], true)) { $usage[$endpointType][$endpointId]['fixed']++; } elseif (in_array($otherType, ['device', 'module'], true)) { $usage[$endpointType][$endpointId]['patch']++; } } }; foreach ((array)$rows as $row) { $typeA = normalizeEndpointType((string)($row['port_a_type'] ?? '')); $typeB = normalizeEndpointType((string)($row['port_b_type'] ?? '')); $idA = (int)($row['port_a_id'] ?? 0); $idB = (int)($row['port_b_id'] ?? 0); if ($typeA === null || $typeB === null) { continue; } $track($typeA, $idA, $typeB); $track($typeB, $idB, $typeA); } return $usage; } function validateEndpointCapacity(array $usage, string $endpointType, int $endpointId, string $otherType, string $label): ?string { if ($endpointId <= 0) { return null; } $stats = $usage[$endpointType][$endpointId] ?? ['total' => 0, 'fixed' => 0, 'patch' => 0]; if ((int)$stats['total'] <= 0) { return null; } if (in_array($endpointType, ['outlet', 'patchpanel'], true)) { if ((int)$stats['total'] >= 2) { return $label . ' hat bereits die maximale Anzahl von 2 Verbindungen'; } if (in_array($otherType, ['outlet', 'patchpanel'], true) && (int)$stats['fixed'] >= 1) { return $label . ' hat bereits eine feste Verdrahtung'; } if (in_array($otherType, ['device', 'module'], true) && (int)$stats['patch'] >= 1) { return $label . ' hat bereits ein Patchkabel'; } return null; } return $label . ' ist bereits in Verwendung'; } function loadConnections($sql): void { $contextType = strtolower(trim((string)($_GET['context_type'] ?? 'all'))); $contextId = isset($_GET['context_id']) ? (int)$_GET['context_id'] : 0; $where = ''; $bindType = ''; $bindValues = []; if ($contextType !== 'all') { if ($contextId <= 0) { jsonError('context_id fehlt oder ist ungueltig', 400); } if ($contextType === 'location') { $where = ' WHERE b.location_id = ?'; $bindType = 'i'; $bindValues = [$contextId]; } elseif ($contextType === 'building') { $where = ' WHERE f.building_id = ?'; $bindType = 'i'; $bindValues = [$contextId]; } elseif ($contextType === 'floor') { $where = ' WHERE r.floor_id = ?'; $bindType = 'i'; $bindValues = [$contextId]; } elseif ($contextType === 'rack') { $where = ' WHERE d.rack_id = ?'; $bindType = 'i'; $bindValues = [$contextId]; } else { jsonError('Ungueltiger Kontext. Erlaubt: all, location, building, floor, rack', 400); } } $devices = $sql->get( "SELECT d.id, d.name, d.device_type_id, d.rack_id, 0 AS pos_x, 0 AS pos_y FROM devices d LEFT JOIN racks r ON r.id = d.rack_id LEFT JOIN floors f ON f.id = r.floor_id LEFT JOIN buildings b ON b.id = f.building_id" . $where, $bindType, $bindValues ); $ports = $sql->get( "SELECT dp.id, dp.device_id, dp.name, dp.port_type_id FROM device_ports dp JOIN devices d ON d.id = dp.device_id LEFT JOIN racks r ON r.id = d.rack_id LEFT JOIN floors f ON f.id = r.floor_id LEFT JOIN buildings b ON b.id = f.building_id" . $where, $bindType, $bindValues ); $connections = $sql->get( "SELECT id, connection_type_id, port_a_type, port_a_id, port_b_type, port_b_id, vlan_config, mode, comment FROM connections", '', [] ); echo json_encode([ 'devices' => $devices ?: [], 'ports' => $ports ?: [], 'connections' => $connections ?: [] ]); } function resolveConnectionTypeId($sql, array $data): int { if (!empty($data['connection_type_id'])) { $requestedId = (int)$data['connection_type_id']; $exists = $sql->single('SELECT id FROM connection_types WHERE id = ?', 'i', [$requestedId]); if (!$exists) { jsonError('connection_type_id existiert nicht', 400); } return $requestedId; } $defaultType = $sql->single('SELECT id FROM connection_types ORDER BY id ASC LIMIT 1'); if (!$defaultType) { jsonError('Kein Verbindungstyp vorhanden', 400); } return (int)$defaultType['id']; } function saveConnection($sql): void { if ($_SERVER['REQUEST_METHOD'] !== 'POST') { jsonError('Methode nicht erlaubt', 405); } $data = json_decode(file_get_contents('php://input'), true); if (!is_array($data)) { jsonError('Ungueltige JSON-Daten', 400); } $portAType = normalizeEndpointType((string)($data['port_a_type'] ?? '')); $portBType = normalizeEndpointType((string)($data['port_b_type'] ?? '')); $portAId = (int)($data['port_a_id'] ?? 0); $portBId = (int)($data['port_b_id'] ?? 0); if ($portAType === null || $portBType === null) { jsonError('port_a_type/port_b_type ungueltig', 400); } if ($portAId <= 0 || $portBId <= 0) { jsonError('port_a_id und port_b_id sind erforderlich', 400); } if (!isTopologyPairAllowed($portAType, $portBType)) { jsonError('Patchpanel-Ports duerfen nur mit Patchpanel-Ports oder Netzwerkdosen-Ports verbunden werden', 400); } if ($portAType === $portBType && $portAId === $portBId) { jsonError('Port A und Port B duerfen nicht identisch sein', 400); } if (!endpointExists($sql, $portAType, $portAId) || !endpointExists($sql, $portBType, $portBId)) { jsonError('Mindestens ein Endpunkt existiert nicht', 400); } $connectionTypeId = resolveConnectionTypeId($sql, $data); $vlanConfig = $data['vlan_config'] ?? null; if (is_array($vlanConfig)) { $vlanConfig = json_encode($vlanConfig); } elseif (!is_string($vlanConfig) && $vlanConfig !== null) { jsonError('vlan_config muss String, Array oder null sein', 400); } $mode = isset($data['mode']) ? (string)$data['mode'] : null; $comment = isset($data['comment']) ? (string)$data['comment'] : null; $connectionId = !empty($data['id']) ? (int)$data['id'] : 0; $usage = buildEndpointUsageMap($sql, $connectionId); $capacityErrorA = validateEndpointCapacity($usage, $portAType, $portAId, $portBType, 'Port an Endpunkt A'); if ($capacityErrorA !== null) { jsonError($capacityErrorA, 409); } $capacityErrorB = validateEndpointCapacity($usage, $portBType, $portBId, $portAType, 'Port an Endpunkt B'); if ($capacityErrorB !== null) { jsonError($capacityErrorB, 409); } if ($connectionId > 0) { $id = $connectionId; $existing = $sql->single('SELECT id FROM connections WHERE id = ?', 'i', [$connectionId]); if (!$existing) { jsonError('Verbindung existiert nicht', 404); } $rows = $sql->set( 'UPDATE connections SET connection_type_id = ?, port_a_type = ?, port_a_id = ?, port_b_type = ?, port_b_id = ?, vlan_config = ?, mode = ?, comment = ? WHERE id = ?', 'isisisssi', [$connectionTypeId, $portAType, $portAId, $portBType, $portBId, $vlanConfig, $mode, $comment, $id] ); if ($rows === false) { jsonError('Update fehlgeschlagen', 500); } echo json_encode(['status' => 'updated', 'rows' => $rows]); return; } $id = $sql->set( 'INSERT INTO connections (connection_type_id, port_a_type, port_a_id, port_b_type, port_b_id, vlan_config, mode, comment) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', 'isisisss', [$connectionTypeId, $portAType, $portAId, $portBType, $portBId, $vlanConfig, $mode, $comment], true ); if ($id === false) { jsonError('Insert fehlgeschlagen', 500); } echo json_encode(['status' => 'created', 'id' => $id]); } function deleteConnection($sql): void { if ($_SERVER['REQUEST_METHOD'] !== 'POST' && $_SERVER['REQUEST_METHOD'] !== 'DELETE') { jsonError('Methode nicht erlaubt', 405); } $id = isset($_GET['id']) ? (int)$_GET['id'] : 0; if ($id <= 0) { jsonError('ID fehlt', 400); } $existing = $sql->single('SELECT id FROM connections WHERE id = ?', 'i', [$id]); if (!$existing) { jsonError('Verbindung existiert nicht', 404); } $rows = $sql->set('DELETE FROM connections WHERE id = ?', 'i', [$id]); if ($rows === false) { jsonError('Loeschen fehlgeschlagen', 500); } echo json_encode(['status' => 'deleted', 'rows' => $rows]); }