Enforce topology rules and fix device deletion flow

This commit is contained in:
2026-02-19 09:13:03 +01:00
parent 9121a2ddfd
commit 9ece132df5
7 changed files with 80 additions and 12 deletions

View File

@@ -1,14 +1,10 @@
# NEXT_STEPS # NEXT_STEPS
## Aktive Aufgaben (priorisiert) ## Aktive Aufgaben (priorisiert)
- [ ] [#11] Encoding- und Umlautfehler bereinigen (inkl. Anzeige in UI-Dateien und Markdown-Dokumenten)
## Verifikation (Status unklar, nicht als erledigt markieren ohne Reproduktion + Commit) ## Verifikation (Status unklar, nicht als erledigt markieren ohne Reproduktion + Commit)
- [ ] [#15] Neue Verbindung: Netzwerkdose auswählbar (Regressionstest in UI durchführen) - [ ] [#15] Neue Verbindung: Netzwerkdose auswählbar (Regressionstest in UI durchführen)
## gefundene bugs ## gefundene bugs
- [ ] device löschen geht nicht
- [ ] TODO Design vereinheitlichen - [ ] TODO Design vereinheitlichen
- [ ] Validierungsregeln fuer Topologie fest verdrahten (Patchpanel-Port nur mit Patchpanel-Port oder Netzwerkbuchsen-Port).

View File

@@ -77,6 +77,19 @@ function endpointExists($sql, string $type, int $id): bool
return false; 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 loadConnections($sql): void function loadConnections($sql): void
{ {
$contextType = strtolower(trim((string)($_GET['context_type'] ?? 'all'))); $contextType = strtolower(trim((string)($_GET['context_type'] ?? 'all')));
@@ -189,6 +202,9 @@ function saveConnection($sql): void
if ($portAId <= 0 || $portBId <= 0) { if ($portAId <= 0 || $portBId <= 0) {
jsonError('port_a_id und port_b_id sind erforderlich', 400); 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) { if ($portAType === $portBType && $portAId === $portBId) {
jsonError('Port A und Port B duerfen nicht identisch sein', 400); jsonError('Port A und Port B duerfen nicht identisch sein', 400);

View File

@@ -51,8 +51,49 @@
}); });
} }
function enforceTopologyTypeRules(typeA, typeB) {
const allowWithPatchpanel = { patchpanel: true, outlet: true };
const selectedA = typeA.value;
const selectedB = typeB.value;
const applyRules = (sourceType, targetSelect) => {
for (const option of targetSelect.options) {
const value = option.value;
if (!value) {
option.disabled = false;
continue;
}
if (sourceType === 'patchpanel') {
option.disabled = !allowWithPatchpanel[value];
} else {
option.disabled = false;
}
}
if (targetSelect.selectedOptions.length > 0 && targetSelect.selectedOptions[0].disabled) {
targetSelect.value = '';
}
};
applyRules(selectedA, typeB);
applyRules(selectedB, typeA);
}
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
bindPair('port_a_type', 'port_a_id'); bindPair('port_a_type', 'port_a_id');
bindPair('port_b_type', 'port_b_id'); bindPair('port_b_type', 'port_b_id');
const typeA = document.getElementById('port_a_type');
const typeB = document.getElementById('port_b_type');
if (!typeA || !typeB) {
return;
}
const syncRules = () => {
enforceTopologyTypeRules(typeA, typeB);
};
syncRules();
typeA.addEventListener('change', syncRules);
typeB.addEventListener('change', syncRules);
}); });
})(); })();

View File

@@ -42,6 +42,18 @@ $normalizePortType = static function (string $value): string {
$portAType = $normalizePortType((string)$portAType); $portAType = $normalizePortType((string)$portAType);
$portBType = $normalizePortType((string)$portBType); $portBType = $normalizePortType((string)$portBType);
$isTopologyPairAllowed = static function (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;
};
// ========================= // =========================
// Validierung (einfach) // Validierung (einfach)
// ========================= // =========================
@@ -50,6 +62,9 @@ $errors = [];
if ($portAId <= 0 || $portBId <= 0) { if ($portAId <= 0 || $portBId <= 0) {
$errors[] = "Beide Ports sind erforderlich"; $errors[] = "Beide Ports sind erforderlich";
} }
if (!$isTopologyPairAllowed($portAType, $portBType)) {
$errors[] = "Patchpanel-Ports duerfen nur mit Patchpanel-Ports oder Netzwerkdosen-Ports verbunden werden";
}
$otherConnections = $sql->get( $otherConnections = $sql->get(
"SELECT id, port_a_type, port_a_id, port_b_type, port_b_id "SELECT id, port_a_type, port_a_id, port_b_type, port_b_id

View File

@@ -56,10 +56,10 @@ $dependencies = $sql->single(
( (
SELECT COUNT(*) SELECT COUNT(*)
FROM connections c FROM connections c
WHERE (c.port_a_type = 'device' AND c.port_a_id IN ( WHERE ((c.port_a_type = 'device' OR c.port_a_type = 'device_ports') AND c.port_a_id IN (
SELECT dp3.id FROM device_ports dp3 WHERE dp3.device_id = ? SELECT dp3.id FROM device_ports dp3 WHERE dp3.device_id = ?
)) ))
OR (c.port_b_type = 'device' AND c.port_b_id IN ( OR ((c.port_b_type = 'device' OR c.port_b_type = 'device_ports') AND c.port_b_id IN (
SELECT dp4.id FROM device_ports dp4 WHERE dp4.device_id = ? SELECT dp4.id FROM device_ports dp4 WHERE dp4.device_id = ?
)) ))
) AS connection_count", ) AS connection_count",
@@ -108,8 +108,8 @@ if ($hasDependencies && !$forceDelete) {
// Connections referenzieren device_ports nur logisch, daher manuell entfernen. // Connections referenzieren device_ports nur logisch, daher manuell entfernen.
$sql->set( $sql->set(
"DELETE FROM connections "DELETE FROM connections
WHERE (port_a_type = 'device' AND port_a_id IN (SELECT id FROM device_ports WHERE device_id = ?)) WHERE ((port_a_type = 'device' OR port_a_type = 'device_ports') AND port_a_id IN (SELECT id FROM device_ports WHERE device_id = ?))
OR (port_b_type = 'device' AND port_b_id IN (SELECT id FROM device_ports WHERE device_id = ?))", OR ((port_b_type = 'device' OR port_b_type = 'device_ports') AND port_b_id IN (SELECT id FROM device_ports WHERE device_id = ?))",
"ii", "ii",
[$deviceId, $deviceId] [$deviceId, $deviceId]
); );

View File

@@ -47,10 +47,10 @@ if ($isEdit) {
( (
SELECT COUNT(*) SELECT COUNT(*)
FROM connections c FROM connections c
WHERE (c.port_a_type = 'device' AND c.port_a_id IN ( WHERE ((c.port_a_type = 'device' OR c.port_a_type = 'device_ports') AND c.port_a_id IN (
SELECT dp3.id FROM device_ports dp3 WHERE dp3.device_id = ? SELECT dp3.id FROM device_ports dp3 WHERE dp3.device_id = ?
)) ))
OR (c.port_b_type = 'device' AND c.port_b_id IN ( OR ((c.port_b_type = 'device' OR c.port_b_type = 'device_ports') AND c.port_b_id IN (
SELECT dp4.id FROM device_ports dp4 WHERE dp4.device_id = ? SELECT dp4.id FROM device_ports dp4 WHERE dp4.device_id = ?
)) ))
) AS connection_count", ) AS connection_count",

View File

@@ -97,10 +97,10 @@ $devices = $sql->get(
( (
SELECT COUNT(*) SELECT COUNT(*)
FROM connections c FROM connections c
WHERE (c.port_a_type = 'device' AND c.port_a_id IN ( WHERE ((c.port_a_type = 'device' OR c.port_a_type = 'device_ports') AND c.port_a_id IN (
SELECT dp3.id FROM device_ports dp3 WHERE dp3.device_id = d.id SELECT dp3.id FROM device_ports dp3 WHERE dp3.device_id = d.id
)) ))
OR (c.port_b_type = 'device' AND c.port_b_id IN ( OR ((c.port_b_type = 'device' OR c.port_b_type = 'device_ports') AND c.port_b_id IN (
SELECT dp4.id FROM device_ports dp4 WHERE dp4.device_id = d.id SELECT dp4.id FROM device_ports dp4 WHERE dp4.device_id = d.id
)) ))
) AS connection_count ) AS connection_count