diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..cff6c9d
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,52 @@
+# AGENTS.md
+
+## Ziel der Datei
+Dieses Dokument beschreibt, welche Informationen ich als Agent für `p:\netwatch` erwarten würde: Projektziel, Setup, Regeln, Skills, bekannte Issues, Kontext & Einschränkungen.
+
+## Projektüberblick
+- Name: **netwatch** – ein Netzwerk-Dokumentations- und Verkabelungsverwaltungs-Tool (Alpha v0.2, Core-Module funktionsfähig, Stand: 13. Februar 2026).
+- Features: Dashboard, Gerätetypen-/Geräteverwaltung, Racks/Floors mit SVG-Planung, Verbindungen inkl. VLANs, Module, grafische Ansichten (Rack, Netzwerkgraph, Stockwerke/Räume).
+- Datenmodell: zentrales SQL-Schema (`locations`, `device_types`, `devices`, `connections` etc.) mit JSON-Erweiterungsmöglichkeiten.
+- Projektphasen (Phase 1–4) sind im README gelistet, siehe letzte Abschnitte.
+
+## Schneller Projektstart
+```powershell
+docker-compose up -d --build
+# danach: http://localhost
+```
+Das Docker-Setup (Compose + Portainer) liegt in `docker-compose.yml` und `docker-portainer.yml`, ergänzende Infos in `Dockerfile`.
+
+## Skills & Nutzungshinweise
+- **skill-creator** – Anleitung zum Erstellen bzw. Erweitern eigener Skills. Pfad: `C:/Users/s.titz/.codex/skills/.system/skill-creator/SKILL.md`.
+- **skill-installer** – Anleitung zum Installieren zusätzlicher Skills aus Kurationslisten oder GitHub. Pfad: `C:/Users/s.titz/.codex/skills/.system/skill-installer/SKILL.md`.
+
+Wenn ein Skill genannt wird (z. B. `$skill-creator`) oder die Aufgabe exakt zur Beschreibung passt, muss dieser Skill in dem Turn verwendet werden. Skills immer erst öffnen (`SKILL.md`), nur nötige Teile lesen, relative Pfade innerhalb des Skill-Verzeichnisses auflösen. Bei mehreren Skills: minimaler Satz in sinnvoller Reihenfolge, kurz ankündigen, warum welche Skills genutzt wurden.
+
+## Lokale Arbeitsregeln
+- Arbeitsumgebung: Windows, Pfad `P:\netwatch`, Shell `powershell`. Schreibzugriff für mich hier ist verboten; Änderungen müssen vom Nutzer übernommen werden.
+- Suche: Nutze `rg`/`rg --files` statt `grep`/`find` für Geschwindigkeit.
+- Codeänderungen: Nur ASCII-Zeichen einführen (außer bestehende Dateien nutzen Unicode); Formate ohne `apply_patch` nur wenn nötig; preferiere `apply_patch`.
+- Keine destruktiven Git-Befehle ohne ausdrückliche Aufforderung (z. B. keinen `reset --hard`).
+- Tests/Builds: Wenn nötig, nenne passende Tests / Prüfmethoden als nächsten Schritt.
+- Kommunikation: Verwende beim Antworten absolute Datumsangaben (z. B. „13. Februar 2026“) wenn sich jemand auf „heute/morgen“ bezieht, um Missverständnisse zu vermeiden.
+
+## Bekannte Bugs (aus `BUGS.md`)
+- Gerät löschen funktioniert nicht (Status unklar).
+- Gerätetypen SVG-Modul: Malfunktion.
+- Ports Drag & Drop (Funktion unklar).
+- Beim Erstellen von Gerätetypen soll ein voreingestelltes Rechteck basierend auf 19-Zoll & HE-Größe erzeugt werden, das als Grundgerüst dient.
+- Device-Typ-Erstellung: Klick auf Objekt-Typ-Button, dann Drag-Drop für Diagonale und Loslassen fixiert Position.
+
+## Weitere Ressourcen
+- `NEXT_STEPS.md` (aktuelles ToDo / Roadmap).
+- `IMPLEMENTATION_STATUS.md` (Status-Tracking).
+- `README.md` (Feature- und Architekturübersicht).
+
+## Besonderheiten / Kommunikation
+- Aktuelles Datum: Freitag, 13. Februar 2026 (nicht überschreiben).
+- Keine Netzwerkanfragen möglich; Referenzen nur lokal nutzen.
+- Wenn ein Agent spezielle Instruktionen benötigt (z. B. Skill-Anwendung), immer darauf hinweisen und ggf. den Nutzer nach Bestätigung fragen.
+
+## Einschränkungen
+- Sandbox ist lesend; bitte selbst `AGENTS.md` anlegen.
+- Jegliche Ausgaben/Antworten sollten den Developer-Guidelines folgen (kurz, teamorientiert, klare nächste Schritte).
diff --git a/README.md b/README.md
index 1b42ea5..085703c 100644
--- a/README.md
+++ b/README.md
@@ -232,8 +232,26 @@ Verbindungen werden:
- Netzwerkdosen:
- anklickbar
- Ports sichtbar
+- Patchpanels:
+ - Fest installierte Patchfelder, die im Raum- bzw. Stockwerksplan als eigene Objekte auftauchen.
+ - Sie stammen nicht aus dem `devices`-Modul, sondern zeigen die permanente Verdrahtung zwischen Patchpanels und Anschlussdosen.
+ - Die untereinander verbundenen Patchpanels lassen sich direkt auf der SVG-Stockwerkskarte verorten, damit jeder Port physisch nachvollziehbar bleibt.
- Verbindungen zu Racks / Switches darstellbar
+### TODO: Patchpanel-Infrastruktur
+- [ ] Floorplans erweitern, damit Patchpanels als feste Infrastrukturobjekte (nicht als rack-basierte `devices`) angelegt, verschoben und mit `x/y`/Größe verankert werden.
+- [ ] Backend und SVG-Editor dahingehend adaptieren, dass Patchpanel-Ports unabhängig von Racks definiert werden können.
+- [ ] Patchpanel ↔ Patchpanel- und Patchpanel ↔ Netzwerkbuchse-Verbindungen als permanente Kabel zwischen Floorplan-Objekten darstellen und über die `connections`-Tabelle verwalten.
+- [ ] UI/Schema-Dokumentation aktualisieren (README + Datenbank-Docs) sowie neue SQL-Tabellen (`floor_patchpanels` / `floor_patchpanel_ports`) fertigstellen.
+- [ ] Floorplan-Filter/Legend leiten die Nutzung dieser Infrastrukturobjekte, Kampagnen und Search & Filter integrieren.
+
+### Stockwerksinfrastruktur-Modul
+- Das neue Modul „Stockwerksinfrastruktur“ sammelt Patchpanels und Wandbuchsen an einem Ort.
+- Patchfelder bekommen feste X/Y-Positionen, Maße, Portanzahl und verknüpfen zu Floorplans.
+- Wandbuchsen sind direkt mit Räumen verbunden, können aber auch später im SVG verteilt werden.
+- Ziel: Die Floorplan-Grafik zeigt die permanente Infrastruktur samt fest verlegter Kabelverläufe.
+- TODO: SVG-Editor um Drag & Drop für diese Objekte erweitern und Klicks direkt mit dem Modul verbinden.
+
---
## Datenbank (konzeptionell)
diff --git a/app/index.php b/app/index.php
index c1f8614..b2c3b23 100644
--- a/app/index.php
+++ b/app/index.php
@@ -27,7 +27,7 @@ $module = $_GET['module'] ?? 'dashboard';
$action = $_GET['action'] ?? 'list';
// Whitelist der Module
-$validModules = ['dashboard', 'locations', 'buildings', 'device_types', 'devices', 'racks', 'floors', 'connections', 'port_types'];
+$validModules = ['dashboard', 'locations', 'buildings', 'device_types', 'devices', 'racks', 'floors', 'floor_infrastructure', 'connections', 'port_types'];
// Whitelist der Aktionen
$validActions = ['list', 'edit', 'save', 'ports', 'delete'];
diff --git a/app/modules/floor_infrastructure/edit.php b/app/modules/floor_infrastructure/edit.php
new file mode 100644
index 0000000..81d9389
--- /dev/null
+++ b/app/modules/floor_infrastructure/edit.php
@@ -0,0 +1,177 @@
+get("SELECT id, name FROM floors ORDER BY name", "", []);
+$rooms = $sql->get(
+ "SELECT r.id, r.name, f.name AS floor_name
+ FROM rooms r
+ LEFT JOIN floors f ON f.id = r.floor_id
+ ORDER BY f.name, r.name",
+ "",
+ []
+);
+
+$panel = null;
+$outlet = null;
+$pageTitle = $type === 'outlet' ? 'Wandbuchse bearbeiten' : 'Patchpanel bearbeiten';
+
+if ($type === 'patchpanel' && $id > 0) {
+ $panel = $sql->single(
+ "SELECT * FROM floor_patchpanels WHERE id = ?",
+ "i",
+ [$id]
+ );
+ if ($panel) {
+ $pageTitle = "Patchpanel bearbeiten: " . htmlspecialchars($panel['name']);
+ }
+}
+
+if ($type === 'outlet' && $id > 0) {
+ $outlet = $sql->single(
+ "SELECT * FROM network_outlets WHERE id = ?",
+ "i",
+ [$id]
+ );
+ if ($outlet) {
+ $pageTitle = "Wandbuchse bearbeiten: " . htmlspecialchars($outlet['name']);
+ }
+}
+?>
+
+
+
+
+
+
+
+
diff --git a/app/modules/floor_infrastructure/list.php b/app/modules/floor_infrastructure/list.php
new file mode 100644
index 0000000..ad9db53
--- /dev/null
+++ b/app/modules/floor_infrastructure/list.php
@@ -0,0 +1,196 @@
+get("SELECT id, name FROM floors ORDER BY name", "", []);
+
+$where = '';
+$types = '';
+$params = [];
+
+if ($floorId > 0) {
+ $where = "WHERE p.floor_id = ?";
+ $types = "i";
+ $params[] = $floorId;
+}
+
+$patchPanels = $sql->get(
+ "SELECT p.*, f.name AS floor_name
+ FROM floor_patchpanels p
+ LEFT JOIN floors f ON f.id = p.floor_id
+ $where
+ ORDER BY f.name, p.name",
+ $types,
+ $params
+);
+
+$networkOutlets = $sql->get(
+ "SELECT o.*, r.name AS room_name, f.name AS floor_name
+ FROM network_outlets o
+ LEFT JOIN rooms r ON r.id = o.room_id
+ LEFT JOIN floors f ON f.id = r.floor_id
+ ORDER BY f.name, r.name, o.name",
+ "",
+ []
+);
+?>
+
+
Die eingetragenen Patchpanels und Wandbuchsen erscheinen später als feste Objekte auf dem Stockwerks-SVG. Die Polygon-Positionen werden momentan noch durch numerische X/Y-Werte gesteuert.
+
TODO: SVG-Editor mit Drag & Drop für diese Objekte erweitern (siehe Stockwerke-Modul).
+
+
+
+
diff --git a/app/modules/floor_infrastructure/save.php b/app/modules/floor_infrastructure/save.php
new file mode 100644
index 0000000..5a61f27
--- /dev/null
+++ b/app/modules/floor_infrastructure/save.php
@@ -0,0 +1,57 @@
+ 0) {
+ $sql->set(
+ "UPDATE floor_patchpanels SET name = ?, floor_id = ?, pos_x = ?, pos_y = ?, width = ?, height = ?, port_count = ?, comment = ? WHERE id = ?",
+ "siiiiisii",
+ [$name, $floorId, $posX, $posY, $width, $height, $portCount, $comment, $id]
+ );
+ } else {
+ $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]
+ );
+ }
+} 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'] ?? '');
+
+ if ($id > 0) {
+ $sql->set(
+ "UPDATE network_outlets SET name = ?, room_id = ?, x = ?, y = ?, comment = ? WHERE id = ?",
+ "siiisi",
+ [$name, $roomId, $x, $y, $comment, $id]
+ );
+ } else {
+ $sql->set(
+ "INSERT INTO network_outlets (name, room_id, x, y, comment) VALUES (?, ?, ?, ?, ?)",
+ "siiis",
+ [$name, $roomId, $x, $y, $comment]
+ );
+ }
+}
+
+header('Location: ?module=floor_infrastructure&action=list');
+exit;
diff --git a/app/templates/header.php b/app/templates/header.php
index 51abd6f..f527764 100644
--- a/app/templates/header.php
+++ b/app/templates/header.php
@@ -40,6 +40,7 @@
'port_types' => 'Porttypen',
'devices' => 'Geräte',
'racks' => 'Racks',
+ 'floor_infrastructure' => 'Infrastruktur',
'connections' => 'Verbindungen',
];
?>
diff --git a/doc/DATABASE.md b/doc/DATABASE.md
index de0e4f7..1df743d 100644
--- a/doc/DATABASE.md
+++ b/doc/DATABASE.md
@@ -95,6 +95,41 @@ Einzelne Ports einer Netzwerkdose.
---
+### Patchpanel-Objekte im Floorplan
+Patchpanels haben eine Sonderstellung: Sie sind fest verkabelte Bündel von Ports und werden **nicht** wie normale `devices` im Rack verschoben, sondern als eigene Floorplan-Objekte fest auf einem Stockwerk positioniert.
+
+**Verwendung**
+- Repräsentieren physische Patchfelder auf dem Stockwerksplan.
+- Werden im SVG mit festen `x`/`y`-Koordinaten verankert und symbolisieren deren reale Position.
+- Ermöglichen die Dokumentation der dauerhaften Verbindungen zu anderen Patchpanels, Netzwerkdosen oder Geräteports ohne Rack-Neuanlage.
+
+**Grafische Attribute**
+- `x`, `y`, `width`, `height` für die Visualisierung im Floorplan.
+- `name`/`label` und `port_count` für die eindeutige Kennzeichnung.
+- Optional: `connection_group` oder `cross_connect_id`, um zusammengehörige Patchfelder zu bündeln.
+
+**Verbindungen**
+- Jeder Patchpanel-Port wird über `connections` mit passenden Gegenstellen verbunden (andere Patchpanels, Dosen, Geräteports, Module).
+- Da sie Teil der Floorplan-Grafik sind, lassen sich die permanenten Kabelverbindungen direkt auf der Stockwerkskarte darstellen.
+
+### Tabellenstruktur `floor_patchpanels`
+Die Bühne für Patchpanel-Objekte auf dem Stockwerkplan.
+- `floor_id` referenziert das Stockwerk, in dem das Panel liegt.
+- `pos_x`, `pos_y`, `width`, `height` definieren das feste Rechteck auf der SVG.
+- `port_count` und `comment` beschreiben die Kapazität und zusätzliche Hinweise.
+
+### Tabellenstruktur `floor_patchpanel_ports`
+- Jeder Eintrag ist ein physischer Port eines Patchpanels.
+- Attributes: Panel-Referenz, `name`, `port_type_id`, optionale VLAN- bzw. Status-Attribute.
+- Ports werden über `connections` sowohl mit anderen Patchpanels als auch mit Netzwerkbuchsen (`network_outlet_ports`) oder Gerätports verbunden; dadurch lassen sich Router-Kabel grafisch darstellen.
+
+**TODO**
+- [ ] Floorplan- und CRUD-Module so erweitern, dass Patchpanels als Floor-Objekte verwaltet und deren Ports gepflegt werden können (`floor_patchpanels`, `floor_patchpanel_ports`).
+- [ ] Verbindungen zwischen Patchpanel ↔ Patchpanel und Patchpanel ↔ Netzwerkbuchse standardisiert in der `connections`-Logik abbilden.
+- [ ] UI/CSV/Export/Dokumentation nachziehen, damit Planer sofort sehen, wo die permanent installierten Kabel verlaufen.
+
+---
+
## 3. Racks & physische Infrastruktur
### `racks`
diff --git a/init.sql b/init.sql
index d826c10..87ad7ef 100644
--- a/init.sql
+++ b/init.sql
@@ -338,6 +338,41 @@ CREATE TABLE `network_outlet_ports` (
-- --------------------------------------------------------
+--
+-- Tabellenstruktur für Tabelle `floor_patchpanels`
+--
+
+CREATE TABLE `floor_patchpanels` (
+ `id` int(11) NOT NULL,
+ `floor_id` int(11) NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `pos_x` int(11) NOT NULL DEFAULT 0,
+ `pos_y` int(11) NOT NULL DEFAULT 0,
+ `width` int(11) NOT NULL DEFAULT 0,
+ `height` int(11) NOT NULL DEFAULT 0,
+ `port_count` int(11) NOT NULL DEFAULT 0,
+ `comment` text DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;
+
+-- --------------------------------------------------------
+
+--
+-- Tabellenstruktur für Tabelle `floor_patchpanel_ports`
+--
+
+CREATE TABLE `floor_patchpanel_ports` (
+ `id` int(11) NOT NULL,
+ `patchpanel_id` int(11) NOT NULL,
+ `name` varchar(50) NOT NULL,
+ `port_type_id` int(11) DEFAULT NULL,
+ `status` enum('active','inactive','pending') NOT NULL DEFAULT 'active',
+ `comment` text DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;
+
+-- TODO: Port-Konfiguration (Patchpanel ↔ Patchpanel, Patchpanel ↔ Netzwerkbuchse) wird über die `connections`-Tabelle geregelt.
+
+-- --------------------------------------------------------
+
--
-- Tabellenstruktur für Tabelle `port_types`
--
@@ -509,6 +544,21 @@ ALTER TABLE `network_outlet_ports`
ADD PRIMARY KEY (`id`),
ADD KEY `outlet_id` (`outlet_id`);
+--
+-- Indizes für die Tabelle `floor_patchpanels`
+--
+ALTER TABLE `floor_patchpanels`
+ ADD PRIMARY KEY (`id`),
+ ADD KEY `floor_id` (`floor_id`);
+
+--
+-- Indizes für die Tabelle `floor_patchpanel_ports`
+--
+ALTER TABLE `floor_patchpanel_ports`
+ ADD PRIMARY KEY (`id`),
+ ADD KEY `patchpanel_id` (`patchpanel_id`),
+ ADD KEY `port_type_id` (`port_type_id`);
+
--
-- Indizes für die Tabelle `port_types`
--
@@ -624,8 +674,20 @@ ALTER TABLE `network_outlet_ports`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
--
--- AUTO_INCREMENT für Tabelle `port_types`
+-- AUTO_INCREMENT für Tabelle `floor_patchpanels`
--
+ALTER TABLE `floor_patchpanels`
+ MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
+
+--
+-- AUTO_INCREMENT für Tabelle `floor_patchpanel_ports`
+--
+ALTER TABLE `floor_patchpanel_ports`
+ MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
+
+--
+-- AUTO_INCREMENT für Tabelle `port_types`
+--
ALTER TABLE `port_types`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;
@@ -714,8 +776,21 @@ ALTER TABLE `network_outlet_ports`
ADD CONSTRAINT `network_outlet_ports_ibfk_1` FOREIGN KEY (`outlet_id`) REFERENCES `network_outlets` (`id`) ON DELETE CASCADE;
--
--- Constraints der Tabelle `racks`
+-- Constraints der Tabelle `floor_patchpanels`
--
+ALTER TABLE `floor_patchpanels`
+ ADD CONSTRAINT `floor_patchpanels_ibfk_1` FOREIGN KEY (`floor_id`) REFERENCES `floors` (`id`) ON DELETE CASCADE;
+
+--
+-- Constraints der Tabelle `floor_patchpanel_ports`
+--
+ALTER TABLE `floor_patchpanel_ports`
+ ADD CONSTRAINT `floor_patchpanel_ports_ibfk_1` FOREIGN KEY (`patchpanel_id`) REFERENCES `floor_patchpanels` (`id`) ON DELETE CASCADE,
+ ADD CONSTRAINT `floor_patchpanel_ports_ibfk_2` FOREIGN KEY (`port_type_id`) REFERENCES `port_types` (`id`);
+
+--
+-- Constraints der Tabelle `racks`
+--
ALTER TABLE `racks`
ADD CONSTRAINT `racks_ibfk_1` FOREIGN KEY (`floor_id`) REFERENCES `floors` (`id`) ON DELETE CASCADE;