From c8fb5b140cfe8fe1718f07b5d93f538e16ce88d9 Mon Sep 17 00:00:00 2001 From: fixclean Date: Wed, 18 Feb 2026 09:23:11 +0100 Subject: [PATCH] feat: improve dashboard and connection workflows - add connection delete endpoint and update connection list handling - expand dashboard visualization behavior - update helpers/header and project TODO tracking --- NEXT_STEPS.md | 76 ++-- TODO.md | 11 +- app/lib/helpers.php | 85 +++- app/modules/connections/delete.php | 41 ++ app/modules/connections/list.php | 420 ++++++++++++-------- app/modules/dashboard/list.php | 600 +++++++++++++++++++++++++++-- app/templates/header.php | 1 + 7 files changed, 998 insertions(+), 236 deletions(-) create mode 100644 app/modules/connections/delete.php diff --git a/NEXT_STEPS.md b/NEXT_STEPS.md index 6648520..d4830a5 100644 --- a/NEXT_STEPS.md +++ b/NEXT_STEPS.md @@ -1,6 +1,6 @@ -# 📋 NÄCHSTE ARBEITSPAKETE +# 📋 NÄCHSTE ARBEITSPAKETE -## 🎯 Für die nächsten Sessions +## 🎯 Für die nächsten Sessions ### Package 1: Fehlerbehandlung & Sessions (1-2h) - [ ] Session-Handling in `bootstrap.php` implementieren @@ -9,9 +9,9 @@ - [ ] Validierungsfehler anzeigen ### Package 2: Delete-Funktionen (1h) -- [ ] DELETE-Endpoints für alle Module -- [ ] AJAX-Bestätigung vor Löschen -- [ ] Kaskadierendes Löschen prüfen (z.B. Floor → Racks) +- [ ] DELETE-Endpoints für alle Module +- [ ] AJAX-Bestätigung vor Löschen +- [ ] Kaskadierendes Löschen prüfen (z.B. Floor → Racks) ### Package 3: Port-Management (2-3h) - [ ] Ports zu Device-Types verwalten @@ -19,23 +19,23 @@ - [ ] Port-Status (aktiv/inaktiv) - [ ] VLAN-Zuordnung zu Ports -### Package 4: SVG-Editor für Floorplans (4-5h) -- [ ] Interaktiver SVG-Editor für Rooms +### Package 4: SVG-Editor für Floorplans (4-5h) +- [ ] Interaktiver SVG-Editor für Rooms - [ ] Netzwerkdosen platzieren - [ ] Dosen nummerieren - [ ] Speicher-Integration ### Package 5: Navigation & UI (1-2h) -- [ ] Breadcrumbs hinzufügen -- [ ] Mobile-Menü verbessern -- [ ] CSS polieren (Farben, Abstände) +- [ ] Breadcrumbs hinzufügen +- [ ] Mobile-Menü verbessern +- [ ] CSS polieren (Farben, Abstände) - [ ] Dark-Mode (optional) --- -## 📚 Code-Referenzen +## 📚 Code-Referenzen -### Template für neue CRUD-Module: +### Template für neue CRUD-Module: ```php // list.php: Filter + Tabelle // edit.php: Formular @@ -71,7 +71,7 @@ $whereSql = $where ? "WHERE " . implode(" AND ", $where) : ""; --- -## 🔧 Bekannte TODOs im Code +## 🔧 Bekannte TODOs im Code Alle noch offenen Punkte sind mit `// TODO:` gekennzeichnet: @@ -83,48 +83,48 @@ grep -r "TODO:" app/modules/ --include="*.php" Wichtigste TODOs: - `index.php:19` - Session starten - `*/save.php` - Fehlerbehandlung -- `connections/` - Port-Verknüpfung +- `connections/` - Port-Verknüpfung - `lib/auth.php` - Auth-Logik --- -## 💾 Datenbank-Setup +## 💾 Datenbank-Setup Die Datenbank wird automatisch durch `init.sql` initialisiert. Wichtige Tabellen: - `locations` - Standorte -- `buildings` - Gebäude +- `buildings` - Gebäude - `floors` - Stockwerke -- `rooms` - Räume +- `rooms` - Räume - `network_outlets` - Netzwerkdosen -- `device_types` - Gerätetypen +- `device_types` - Gerätetypen - `device_type_ports` - Port-Templates -- `devices` - konkrete Geräte -- `device_ports` - Gerätports +- `devices` - konkrete Geräte +- `device_ports` - Gerätports - `racks` - Racks - `connections` - Verbindungen zwischen Ports --- -## 🧪 Testing-Checklist +## 🧪 Testing-Checklist -Bei jeder Änderung checken: +Bei jeder Änderung checken: - [ ] Formular sendet Daten korrekt - [ ] Daten werden in DB gespeichert - [ ] Liste zeigt neue Daten -- [ ] Edit lädt existierende Daten vor +- [ ] Edit lädt existierende Daten vor - [ ] Filter funktioniert - [ ] Validierungsfehler werden angezeigt --- -## 🎨 Design-Richtlinien +## 🎨 Design-Richtlinien ### Farben: - Primary (Buttons): `#007bff` (Blau) -- Success (Speichern): `#28a745` (Grün) -- Danger (Löschen): `#dc3545` (Rot) +- Success (Speichern): `#28a745` (Grün) +- Danger (Löschen): `#dc3545` (Rot) - Background: `#f9f9f9` (Hell) - Border: `#ddd` (Hell-Grau) @@ -135,8 +135,28 @@ Bei jeder Änderung checken: ### Schriftarten: - Erben von HTML (derzeit: System) -- Monospace für Code/IDs: `font-family: monospace` +- Monospace für Code/IDs: `font-family: monospace` --- -**Happy Coding! 🚀** +**Happy Coding! 🚀** + +## Aktuell offene TODOs (Stand: 18. Februar 2026) + +- [ ] #15 Neue Verbindung: Es kann keine Netzwerkdose ausgewahlt werden. +- [ ] #14 Hilfslinien der Stockwerkskarten nur im Edit-Mode anzeigen; im Anzeige-Mode ausblenden. +- [ ] #11 Encoding- und Umlautfehler beheben (inkl. ae/oe/ue-Themen). +- [ ] #10 Dashboard-Grafik erzeugen: + - Oberste Ebene: Locations, ggf. mit Unterordnung. + - Darunter: Gebaudekomplexe mit Rack-Verbindungen. + - Darunter: Stockwerke. + - Darunter: Etagenweise Verbindungen. +- [ ] #8 Gerate löschen fehlt: Es erfolgt Weiterleitung, aber keine echte Fehlermeldung. +- [ ] #7 Letzten Punkt im Floor-Editor löschen: + - URL: `http://localhost/?module=floors&action=edit&id=1` +- [ ] #5 Dashboard als zoombare und verschiebbare SVG-Flache: + - Gerate anordnen. + - Gerate, Ports und Verbindungen anklickbar. + - Sprechblase mit Infos und Buttons zu Bereichen (editieren, entfernen, ...). +- [ ] #4 `device_types/edit`: Option "Ports automatisch erstellen" nur beim Erstellen anzeigen, nicht beim Editieren. + diff --git a/TODO.md b/TODO.md index 8023301..6cf7f5b 100644 --- a/TODO.md +++ b/TODO.md @@ -1,4 +1,4 @@ -# TODO +# TODO Bereinigte und aktuelle TODO-Liste (Stand: 18. Februar 2026). Quelle: vorhandene `TODO`-Marker im Repository plus offene Architekturpunkte. @@ -14,12 +14,9 @@ Quelle: vorhandene `TODO`-Marker im Repository plus offene Architekturpunkte. ## Offen (direkt im Code markiert) -- [ ] `app/modules/dashboard/list.php:11`: - Grosse zoombare/verschiebbare SVG-Wand mit Punkten + Overlay-Drilldown (z. B. Rack-Ansicht). -- [ ] `app/modules/connections/list.php:387`: - Verbindung im UI bearbeiten/loeschen. -- [ ] `app/lib/helpers.php:300`: - Generischer Platzhalter fuer weitere Helper (nur bei konkretem Bedarf ergaenzen). +- [x] `app/modules/dashboard/list.php`: zoombare/verschiebbare SVG-Wand mit klickbaren Punkten und Overlay-Drilldown umgesetzt. +- [x] `app/modules/connections/list.php`: Detailbereich fuer ausgewaehlte Verbindung inkl. Bearbeiten/Loeschen im UI umgesetzt. +- [x] `app/lib/helpers.php`: konkrete allgemeine Helper ergaenzt (`formatDateTime`, `formatBytes`, `generateUuidV4`, `normalizeSvgCoordinate`). ## Offen (Bugs / Doku / Statusdateien) diff --git a/app/lib/helpers.php b/app/lib/helpers.php index 9746082..cd3d98c 100644 --- a/app/lib/helpers.php +++ b/app/lib/helpers.php @@ -297,8 +297,83 @@ HTML, [ * Sonstiges * ========================= */ -// TODO: Weitere Helfer nach Bedarf -// - Datum formatieren -// - Bytes → MB -// - UUID erzeugen -// - SVG-Koordinaten normalisieren +/** + * Formatiert Datum/Uhrzeit robust oder gibt Fallback zurueck. + * + * @param string|null $value + * @param string $format + * @param string $fallback + * @return string + */ +function formatDateTime(?string $value, string $format = 'd.m.Y H:i', string $fallback = '-'): string +{ + if ($value === null || trim($value) === '') { + return $fallback; + } + + $timestamp = strtotime($value); + if ($timestamp === false) { + return $fallback; + } + + return date($format, $timestamp); +} + +/** + * Formatiert Byte-Werte in menschenlesbare Einheit. + * + * @param int|float $bytes + * @param int $precision + * @return string + */ +function formatBytes($bytes, int $precision = 2): string +{ + $value = (float)$bytes; + if ($value <= 0) { + return '0 B'; + } + + $units = ['B', 'KB', 'MB', 'GB', 'TB']; + $power = min((int)floor(log($value, 1024)), count($units) - 1); + $scaled = $value / (1024 ** $power); + + return number_format($scaled, $precision, '.', '') . ' ' . $units[$power]; +} + +/** + * Erzeugt eine UUID v4. + * + * @return string + * @throws Exception + */ +function generateUuidV4(): string +{ + $bytes = random_bytes(16); + $bytes[6] = chr((ord($bytes[6]) & 0x0f) | 0x40); + $bytes[8] = chr((ord($bytes[8]) & 0x3f) | 0x80); + $hex = bin2hex($bytes); + + return sprintf( + '%s-%s-%s-%s-%s', + substr($hex, 0, 8), + substr($hex, 8, 4), + substr($hex, 12, 4), + substr($hex, 16, 4), + substr($hex, 20, 12) + ); +} + +/** + * Klemmt eine SVG-Koordinate auf gueltigen Bereich. + * + * @param float $value + * @param float $min + * @param float $max + * @param int $precision + * @return float + */ +function normalizeSvgCoordinate(float $value, float $min, float $max, int $precision = 2): float +{ + $normalized = max($min, min($max, $value)); + return round($normalized, $precision); +} diff --git a/app/modules/connections/delete.php b/app/modules/connections/delete.php new file mode 100644 index 0000000..fb0797a --- /dev/null +++ b/app/modules/connections/delete.php @@ -0,0 +1,41 @@ +single( + "SELECT id FROM connections WHERE id = ?", + "i", + [$connectionId] +); + +if (!$connection) { + $_SESSION['error'] = 'Verbindung nicht gefunden'; + header('Location: ?module=connections&action=list'); + exit; +} + +$rows = $sql->set( + "DELETE FROM connections WHERE id = ?", + "i", + [$connectionId] +); + +if ($rows > 0) { + $_SESSION['success'] = 'Verbindung geloescht'; +} else { + $_SESSION['error'] = 'Verbindung konnte nicht geloescht werden'; +} + +header('Location: ?module=connections&action=list'); +exit; diff --git a/app/modules/connections/list.php b/app/modules/connections/list.php index 192bc8f..9cb4ff3 100644 --- a/app/modules/connections/list.php +++ b/app/modules/connections/list.php @@ -2,19 +2,13 @@ /** * app/modules/connections/list.php * - * Übersicht der Netzwerkverbindungen - * - Tabellarische Liste aller Verbindungen - * - Filter nach Geräten, VLANs, Status - * - Später: Visuelle Netzwerk-Topologie + * Uebersicht der Netzwerkverbindungen */ -// ========================= -// Filter einlesen -// ========================= $search = trim($_GET['search'] ?? ''); $deviceId = (int)($_GET['device_id'] ?? 0); +$selectedConnectionId = (int)($_GET['connection_id'] ?? 0); -// Einheitliche Endpunkt-Aufloesung fuer polymorphe Port-Typen. $endpointUnionSql = " SELECT 'device' AS endpoint_type, @@ -60,16 +54,13 @@ $endpointUnionSql = " LEFT JOIN floors f ON f.id = fp.floor_id "; -// ========================= -// WHERE-Clause bauen -// ========================= $where = []; $types = ''; $params = []; if ($search !== '') { $where[] = "(e1.owner_name LIKE ? OR e2.owner_name LIKE ? OR e1.port_name LIKE ? OR e2.port_name LIKE ?)"; - $types .= "ssss"; + $types .= 'ssss'; $params[] = "%$search%"; $params[] = "%$search%"; $params[] = "%$search%"; @@ -78,16 +69,13 @@ if ($search !== '') { if ($deviceId > 0) { $where[] = "(e1.owner_device_id = ? OR e2.owner_device_id = ?)"; - $types .= "ii"; + $types .= 'ii'; $params[] = $deviceId; $params[] = $deviceId; } -$whereSql = $where ? "WHERE " . implode(" AND ", $where) : ""; +$whereSql = $where ? 'WHERE ' . implode(' AND ', $where) : ''; -// ========================= -// Verbindungen laden -// ========================= $connections = $sql->get( "SELECT c.id, @@ -125,10 +113,22 @@ $connections = $sql->get( $params ); -// ========================= -// Filter-Daten -// ========================= -$devices = $sql->get("SELECT id, name FROM devices ORDER BY name", "", []); +$devices = $sql->get('SELECT id, name FROM devices ORDER BY name', '', []); + +$selectedConnection = null; +if ($selectedConnectionId > 0) { + foreach ((array)$connections as $entry) { + if ((int)($entry['id'] ?? 0) === $selectedConnectionId) { + $selectedConnection = $entry; + break; + } + } +} + +if ($selectedConnection === null && !empty($connections)) { + $selectedConnection = $connections[0]; + $selectedConnectionId = (int)($selectedConnection['id'] ?? 0); +} $selectedDevice = null; $selectedDevicePorts = []; @@ -140,7 +140,7 @@ if ($deviceId > 0) { FROM devices d LEFT JOIN device_types dt ON d.device_type_id = dt.id WHERE d.id = ?", - "i", + 'i', [$deviceId] ); @@ -159,7 +159,7 @@ if ($deviceId > 0) { JOIN device_ports dp ON dp.id = dpm.device_port_id WHERE dp.device_id = ? ) p", - "ii", + 'ii', [$deviceId, $deviceId] )['cnt'] ?? 0); @@ -187,7 +187,7 @@ if ($deviceId > 0) { OR (c.port_b_type = 'patchpanel' AND e2.endpoint_type = 'floor_patchpanel') ) WHERE e1.owner_device_id = ? OR e2.owner_device_id = ?", - "ii", + 'ii', [$deviceId, $deviceId] )['cnt'] ?? 0); @@ -207,7 +207,7 @@ if ($deviceId > 0) { ) p ORDER BY sort_id LIMIT 12", - "ii", + 'ii', [$deviceId, $deviceId] ); @@ -230,159 +230,247 @@ if ($deviceId > 0) { } } +$buildListUrl = static function (array $extra = []) use ($search, $deviceId): string { + $query = ['module' => 'connections', 'action' => 'list']; + if ($search !== '') { + $query['search'] = $search; + } + if ($deviceId > 0) { + $query['device_id'] = $deviceId; + } + foreach ($extra as $key => $value) { + if ($value === null || $value === '') { + continue; + } + $query[$key] = $value; + } + return '?' . http_build_query($query); +}; ?> -
-

Netzwerkverbindungen

+
+
+

Netzwerkverbindungen

- -
-
- - +
+ + + - + - + + + + + + + + Reset + + Neue Verbindung + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - Reset - + Neue Verbindung - + +
Von (Geraet -> Port)Nach (Geraet -> Port)VLANsBeschreibungStatusAktionen
+
+ +
+
+ +
+ + + + + + Warnung + + OK + + + Details + Bearbeiten + Von/Nach tauschen + + Loeschen + +
+ +
+

Keine Verbindungen gefunden.

+

+ + Erste Verbindung anlegen + +

+
+
- - - - - - - - - - - - - - - - - - + - - - - - - - - - - -
Von (Gerät → Port)Nach (Gerät → Port)VLANsBeschreibungStatusAktionen
-
- -
-
- -
- - - - - - - - - ⚠️ Warnung - + - Bearbeiten - Von/Nach tauschen - - Löschen - -
- - -
-

Keine Verbindungen gefunden.

-

- - Erste Verbindung anlegen - -

-
- +

+ +

Ports (max. 12)

+
    + +
  • + +
+ + +

Ausgewaehltes Geraet

+

Bitte ein Geraet im Filter auswaehlen.

+ + +
- + diff --git a/app/modules/dashboard/list.php b/app/modules/dashboard/list.php index 71c4593..1cdeb5e 100644 --- a/app/modules/dashboard/list.php +++ b/app/modules/dashboard/list.php @@ -1,15 +1,9 @@ $sql->single("SELECT COUNT(*) as cnt FROM devices", "", [])['cnt'] ?? 0, 'device_types' => $sql->single("SELECT COUNT(*) as cnt FROM device_types", "", [])['cnt'] ?? 0, @@ -18,7 +12,6 @@ $stats = [ 'locations' => $sql->single("SELECT COUNT(*) as cnt FROM locations", "", [])['cnt'] ?? 0, ]; -// Recent devices $recentDevices = $sql->get( "SELECT d.id, d.name, dt.name as type_name, r.name as rack_name, f.name as floor_name FROM devices d @@ -26,12 +19,41 @@ $recentDevices = $sql->get( LEFT JOIN racks r ON d.rack_id = r.id LEFT JOIN floors f ON r.floor_id = f.id ORDER BY d.id DESC LIMIT 5", - "", [] + "", + [] ); +$topologyDevices = $sql->get( + "SELECT + d.id AS device_id, + d.name AS device_name, + dt.name AS device_type_name, + r.id AS rack_id, + r.name AS rack_name, + f.id AS floor_id, + f.name AS floor_name + FROM devices d + LEFT JOIN device_types dt ON dt.id = d.device_type_id + LEFT JOIN racks r ON r.id = d.rack_id + LEFT JOIN floors f ON f.id = r.floor_id + ORDER BY floor_name, rack_name, device_name", + "", + [] +); + +$topologyPayload = array_map(static function (array $row): array { + return [ + 'device_id' => (int)($row['device_id'] ?? 0), + 'device_name' => (string)($row['device_name'] ?? ''), + 'device_type_name' => (string)($row['device_type_name'] ?? ''), + 'rack_id' => (int)($row['rack_id'] ?? 0), + 'rack_name' => (string)($row['rack_name'] ?? ''), + 'floor_id' => (int)($row['floor_id'] ?? 0), + 'floor_name' => (string)($row['floor_name'] ?? ''), + ]; +}, $topologyDevices); ?> -

Dashboard

@@ -39,35 +61,63 @@ $recentDevices = $sql->get(

- +
+
+

Gesamt-Topologie-Wand

+
+ + + +
+
+

Mausrad zoomt, Ziehen verschiebt. Klick auf einen Punkt zoomt auf den Rack-Kontext und oeffnet die Detailkarte.

+ + + + + + + + + + +
+
-

+

Standorte

- Verwalten → + Verwalten ->
-

-

Gerätetypen

- Verwalten → +

+

Geraetetypen

+ Verwalten ->
-

-

Geräte

- Verwalten → +

+

Geraete

+ Verwalten ->
-

+

Racks

- Verwalten → + Verwalten ->
- -

Zuletzt hinzugefügt

+

Zuletzt hinzugefuegt

@@ -82,20 +132,379 @@ $recentDevices = $sql->get( - - - - - + + + + +
BearbeitenBearbeiten
-

Noch keine Geräte vorhanden. Starten Sie mit Gerätetypen.

+

Noch keine Geraete vorhanden. Starten Sie mit Geraetetypen.

+ + + diff --git a/app/templates/header.php b/app/templates/header.php index 12e43fd..766327f 100644 --- a/app/templates/header.php +++ b/app/templates/header.php @@ -21,6 +21,7 @@ +