diff --git a/NOTES.md b/NOTES.md new file mode 100644 index 0000000..3838587 --- /dev/null +++ b/NOTES.md @@ -0,0 +1,4 @@ +# Notizen + +https://chatgpt.com/share/698517b9-b1e4-800e-bbd8-d207dfb326f0 + diff --git a/README.md b/README.md index 885eb56..1383029 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,252 @@ # netwatch +### Stockwerksplan (SVG) +- Pro Stockwerk ein SVG +- Enthält: + - Räume (benennbar, nummerierbar) + - Netzwerkdosen (frei platzierbar) +- Elemente sind: + - Verschiebbar + - Nachträglich anpassbar + - Eindeutig referenzierbar + +### Netzwerkdose +- Name / Nummer +- Raum +- Anzahl Ports +- Porttypen (z. B. RJ45, Glasfaser, BNC) +- Ports sind vollständig normale Ports im System + +--- + +## Gerätetypen + +### Gerätetyp +Definiert das **Aussehen und die Port-Geometrie** eines Geräts. + +**Attribute** +- Name +- Kategorie: + - Switch + - Server + - Patchpanel + - Sonstiges +- Darstellung: + - SVG **oder** + - JPG/PNG + +### Portdefinition im Bild +Beim Anlegen eines Gerätetyps: +- Bild wird angezeigt +- Ports werden per Klick gesetzt: + - X/Y relativ zum Bild + - Portname + - Porttyp +- Diese Portdefinition ist die Vorlage für alle Geräte dieses Typs + +--- + +## Geräte + +### Gerät +Instanz eines Gerätetyps. + +**Attribute** +- Name +- Gerätetyp +- Standort / Rack / Stockwerk +- Position im Rack: + - Start-HE + - Höhe in HE +- Seriennummer (optional) +- Kommentar + +Ports werden automatisch aus dem Gerätetyp erzeugt. + +--- + +## Switches & Ports + +### Ports +- Name / Nummer +- Porttyp (frei definierbar) +- Geschwindigkeit(en) +- Status (aktiv / deaktiviert) +- VLAN-Zuweisung +- Modus: + - Access + - Trunk + - Hybrid + - Custom (Freitext) + +--- + +## Module (z. B. SFP, Spezialkarten) + +### Modul +Eigenständige Komponente, die in einen Port eingesetzt wird. + +**Eigenschaften** +- Name +- Typ (z. B. SFP, QSFP, BNC-Modul) +- Kompatible Porttypen +- Eigene Ports (z. B. LC Duplex) + +### Logik +``` +Switch-Port → Modul → Modul-Port → Verbindung +``` + + +Module können selbst Ports besitzen und sind vollwertige Verbindungspartner. + +--- + +## Verbindungstypen + +### Verbindungstyp (frei definierbar) +- Name +- Medium: + - Kupfer + - Glasfaser + - Koax + - Sonstiges +- Duplex: + - Half + - Full + - Custom +- Max. Geschwindigkeit (optional) +- Darstellung: + - Farbe + - Linientyp (durchgezogen, gestrichelt) + +Beispiele: +- RJ45 Cat6 +- LC-LC Singlemode +- BNC Token Ring +- Proprietär XYZ + +--- + +## Verbindungen + +### Verbindung +- Port A ↔ Port B +- Verbindungstyp +- VLAN(s) +- Modus +- Kommentar + +Verbindungen werden: +- logisch gespeichert +- grafisch in allen relevanten SVGs dargestellt + +--- + +## Grafische Ansichten + +### Rack-Ansicht (SVG) +- Frontansicht mit HE-Raster +- Geräte: + - korrekt skaliert + - drag & drop +- Kabel: + - Linien zwischen Portpunkten + - Farbe gemäß Verbindungstyp + - Hover zeigt Details + +--- + +### Netzwerkansicht (Graph) +- Geräte als Nodes +- Verbindungen als Edges +- Ports optional sichtbar +- Layout: + - automatisch (Force-Layout) + - manuell verschiebbar +- Filter: + - VLAN + - Verbindungstyp + - Standort + +--- + +### Stockwerks- & Raumansicht +- SVG-Plan pro Stockwerk +- Netzwerkdosen: + - anklickbar + - Ports sichtbar +- Verbindungen zu Racks / Switches darstellbar + +--- + +## Datenbank (konzeptionell) + +### Zentrale Tabellen +- `locations` +- `buildings` +- `floors` +- `rooms` +- `floor_svgs` +- `network_outlets` +- `device_types` +- `device_type_ports` +- `devices` +- `device_ports` +- `modules` +- `module_ports` +- `connection_types` +- `connections` +- `vlans` +- `racks` + +Custom-Eigenschaften werden teilweise als JSON gespeichert, um Erweiterungen +ohne Schema-Brüche zu ermöglichen. + +--- + +## Erweiterbarkeit + +Geplant, aber nicht initial: +- Mehrbenutzerfähigkeit +- Historisierung / Änderungsverlauf +- Rechte & Rollen +- Export (JSON, CSV, PDF) +- Theming / Design-System +- API + +--- + +## Projektphasen + +### Phase 1 – Fundament +- Docker Compose +- Basisdatenbank +- CRUD für: + - Verbindungstypen + - Gerätetypen + - Standorte + +### Phase 2 – Grafik & Geräte +- Gerätetyp-Editor mit Port-Klick +- Rack-Ansicht +- Geräteplatzierung + +### Phase 3 – Verkabelung +- Verbindungslogik +- Module +- VLANs +- Visuelle Kabeldarstellung + +### Phase 4 – Gebäudepläne +- Stockwerks-SVG +- Räume +- Netzwerkdosen +- Verknüpfung mit Geräten + +--- + +## Zielzustand +Ein zuverlässiges, verständliches und visuell präzises Werkzeug zur +Netzwerkdokumentation, das **nicht einschränkt**, sondern reale, +auch unkonventionelle Infrastrukturen korrekt abbilden kann. diff --git a/app/.htaccess b/app/.htaccess new file mode 100644 index 0000000..76006f3 --- /dev/null +++ b/app/.htaccess @@ -0,0 +1,48 @@ +# ========================= +# Grundschutz +# ========================= + +# Kein Directory-Listing +Options -Indexes + +# Schutz für sensible Dateien + + Require all denied + + +# TODO: ggf. weitere Dateien schützen, z.B. uploads oder tmp + +# ========================= +# Rewrite zu index.php +# ========================= +RewriteEngine On + +# Alles auf index.php umleiten, außer echte Dateien/Verzeichnisse +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule ^(.*)$ index.php [QSA,L] + +# ========================= +# Standard-Dokument +# ========================= +DirectoryIndex index.php + +# ========================= +# Security Headers +# ========================= + + Header set X-Content-Type-Options "nosniff" + Header set X-Frame-Options "SAMEORIGIN" + Header set X-XSS-Protection "1; mode=block" + Header always set Referrer-Policy "no-referrer-when-downgrade" + Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:;" + + +# ========================= +# Upload-Sicherheit +# ========================= + + Require all denied + + +# TODO: Optional: Upload-Verzeichnisse (device_types, floorplans) via .htaccess zusätzlich schützen diff --git a/app/api/connections.php b/app/api/connections.php new file mode 100644 index 0000000..0da16c9 --- /dev/null +++ b/app/api/connections.php @@ -0,0 +1,2 @@ + bewusst simpel & erweiterbar + */ + +/* ========================= + * Konfiguration + * ========================= */ + +// TODO: Standort / Rack / View-Kontext vom Backend setzen +const CONTEXT_ID = null; + +// TODO: API-Endpunkte definieren +const API_LOAD_NETWORK = '/api/network_view.php?action=load'; +const API_SAVE_POSITIONS = '/api/network_view.php?action=save_positions'; + +/* ========================= + * State + * ========================= */ + +let svgElement = null; + +let devices = []; // Geräte inkl. Position +let connections = []; // Verbindungen zwischen Ports + +let selectedDeviceId = null; +let isDragging = false; +let dragOffset = { x: 0, y: 0 }; + +/* ========================= + * Initialisierung + * ========================= */ + +document.addEventListener('DOMContentLoaded', () => { + svgElement = document.querySelector('#network-svg'); + + if (!svgElement) { + console.warn('Network View: #network-svg nicht gefunden'); + return; + } + + bindSvgEvents(); + loadNetwork(); +}); + +/* ========================= + * Events + * ========================= */ + +function bindSvgEvents() { + svgElement.addEventListener('mousemove', onMouseMove); + svgElement.addEventListener('mouseup', onMouseUp); + svgElement.addEventListener('click', onSvgClick); +} + +/* ========================= + * Laden + * ========================= */ + +function loadNetwork() { + if (!CONTEXT_ID) { + console.warn('CONTEXT_ID nicht gesetzt'); + return; + } + + fetch(`${API_LOAD_NETWORK}&context_id=${CONTEXT_ID}`) + .then(res => res.json()) + .then(data => { + // TODO: Datenstruktur validieren + devices = data.devices || []; + connections = data.connections || []; + + renderAll(); + }) + .catch(err => { + console.error('Fehler beim Laden der Netzwerkansicht', err); + }); +} + +/* ========================= + * Rendering + * ========================= */ + +function renderAll() { + clearSvg(); + + renderConnections(); + renderDevices(); +} + +function clearSvg() { + while (svgElement.firstChild) { + svgElement.removeChild(svgElement.firstChild); + } +} + +/* ---------- Geräte ---------- */ + +function renderDevices() { + devices.forEach(device => renderDevice(device)); +} + +function renderDevice(device) { + const group = document.createElementNS('http://www.w3.org/2000/svg', 'g'); + group.classList.add('device-node'); + group.dataset.id = device.id; + + group.setAttribute( + 'transform', + `translate(${device.x || 0}, ${device.y || 0})` + ); + + // TODO: Gerätetyp (SVG oder JPG) korrekt laden + const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); + rect.setAttribute('width', 120); + rect.setAttribute('height', 60); + rect.setAttribute('rx', 6); + + rect.addEventListener('mousedown', (e) => { + startDrag(e, device.id); + e.stopPropagation(); + }); + + // Label + const text = document.createElementNS('http://www.w3.org/2000/svg', 'text'); + text.setAttribute('x', 60); + text.setAttribute('y', 35); + text.setAttribute('text-anchor', 'middle'); + text.textContent = device.name || 'Device'; + + group.appendChild(rect); + group.appendChild(text); + + // TODO: Ports als kleine Kreise anlegen (Position aus Portdefinition) + // TODO: Ports klickbar machen (für Verbindungs-Erstellung) + + svgElement.appendChild(group); +} + +/* ---------- Verbindungen ---------- */ + +function renderConnections() { + connections.forEach(conn => renderConnection(conn)); +} + +function renderConnection(connection) { + // TODO: Quell- & Ziel-Port-Koordinaten berechnen + // TODO: unterschiedliche Verbindungstypen (Farbe, Strichart, Dicke) + + const line = document.createElementNS('http://www.w3.org/2000/svg', 'line'); + + line.setAttribute('x1', 0); + line.setAttribute('y1', 0); + line.setAttribute('x2', 100); + line.setAttribute('y2', 100); + + line.classList.add('connection-line'); + + svgElement.appendChild(line); +} + +/* ========================= + * Interaktion + * ========================= */ + +function onSvgClick(event) { + // Klick ins Leere -> Auswahl aufheben + if (event.target === svgElement) { + selectedDeviceId = null; + updateSelection(); + } +} + +function startDrag(event, deviceId) { + const device = getDeviceById(deviceId); + if (!device) return; + + isDragging = true; + selectedDeviceId = deviceId; + + const point = getSvgCoordinates(event); + dragOffset.x = (device.x || 0) - point.x; + dragOffset.y = (device.y || 0) - point.y; + + updateSelection(); +} + +function onMouseMove(event) { + if (!isDragging || !selectedDeviceId) return; + + const device = getDeviceById(selectedDeviceId); + if (!device) return; + + const point = getSvgCoordinates(event); + + device.x = point.x + dragOffset.x; + device.y = point.y + dragOffset.y; + + renderAll(); +} + +function onMouseUp() { + if (!isDragging) return; + + isDragging = false; + + // TODO: Positionen optional automatisch speichern +} + +/* ========================= + * Auswahl + * ========================= */ + +function updateSelection() { + svgElement.querySelectorAll('.device-node').forEach(el => { + el.classList.toggle( + 'selected', + el.dataset.id === String(selectedDeviceId) + ); + }); + + // TODO: Sidebar mit Gerätedetails füllen +} + +/* ========================= + * Speichern + * ========================= */ + +function savePositions() { + fetch(API_SAVE_POSITIONS, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + context_id: CONTEXT_ID, + devices: devices.map(d => ({ + id: d.id, + x: d.x, + y: d.y + })) + }) + }) + .then(res => res.json()) + .then(data => { + // TODO: Erfolg / Fehler anzeigen + console.log('Positionen gespeichert', data); + }) + .catch(err => { + console.error('Fehler beim Speichern', err); + }); +} + +/* ========================= + * Hilfsfunktionen + * ========================= */ + +function getSvgCoordinates(event) { + const pt = svgElement.createSVGPoint(); + pt.x = event.clientX; + pt.y = event.clientY; + + const transformed = pt.matrixTransform(svgElement.getScreenCTM().inverse()); + return { x: transformed.x, y: transformed.y }; +} + +function getDeviceById(id) { + return devices.find(d => d.id === id); +} + +/* ========================= + * Keyboard Shortcuts + * ========================= */ + +document.addEventListener('keydown', (e) => { + if (e.key === 'Escape') { + selectedDeviceId = null; + updateSelection(); + } + + // TODO: Delete -> Gerät entfernen? +}); diff --git a/app/assets/js/svg-editor.js b/app/assets/js/svg-editor.js new file mode 100644 index 0000000..709fb4c --- /dev/null +++ b/app/assets/js/svg-editor.js @@ -0,0 +1,258 @@ +// Logik für den SVG-Port-Editor (Klicks, Drag & Drop, Speichern) +/** + * svg-editor.js + * + * Logik für den SVG-Port-Editor: + * - Ports per Klick anlegen + * - Ports auswählen + * - Ports verschieben (Drag & Drop) + * - Ports löschen + * - Ports laden / speichern + * + * Abhängigkeiten: keine (Vanilla JS) + */ + +/* ========================= + * Konfiguration + * ========================= */ + +// TODO: vom Backend setzen (z. B. via data-Attribut) +const DEVICE_TYPE_ID = null; + +// TODO: API-Endpunkte festlegen +const API_LOAD_PORTS = '/api/device_type_ports.php?action=load'; +const API_SAVE_PORTS = '/api/device_type_ports.php?action=save'; + +/* ========================= + * State + * ========================= */ + +let svgElement = null; +let ports = []; +let selectedPortId = null; +let isDragging = false; +let dragOffset = { x: 0, y: 0 }; + +/* ========================= + * Initialisierung + * ========================= */ + +document.addEventListener('DOMContentLoaded', () => { + svgElement = document.querySelector('#device-svg'); + + if (!svgElement) { + console.warn('SVG Editor: #device-svg nicht gefunden'); + return; + } + + bindSvgEvents(); + loadPorts(); +}); + +/* ========================= + * SVG Events + * ========================= */ + +function bindSvgEvents() { + svgElement.addEventListener('click', onSvgClick); + svgElement.addEventListener('mousemove', onSvgMouseMove); + svgElement.addEventListener('mouseup', onSvgMouseUp); +} + +/* ========================= + * Port-Erstellung + * ========================= */ + +function onSvgClick(event) { + // Klick auf bestehenden Port? + if (event.target.classList.contains('port-point')) { + selectPort(event.target.dataset.id); + return; + } + + // TODO: Modifier-Key prüfen (z. B. nur mit SHIFT neuen Port erstellen?) + const point = getSvgCoordinates(event); + + createPort(point.x, point.y); +} + +function createPort(x, y) { + const id = generateTempId(); + + const port = { + id: id, + name: `Port ${ports.length + 1}`, + port_type_id: null, // TODO: Default-Porttyp? + x: x, + y: y, + comment: '' + }; + + ports.push(port); + renderPort(port); + selectPort(id); +} + +/* ========================= + * Rendering + * ========================= */ + +function renderPort(port) { + const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); + + circle.setAttribute('cx', port.x); + circle.setAttribute('cy', port.y); + circle.setAttribute('r', 6); + circle.classList.add('port-point'); + circle.dataset.id = port.id; + + circle.addEventListener('mousedown', (e) => { + startDrag(e, port.id); + e.stopPropagation(); + }); + + svgElement.appendChild(circle); +} + +function rerenderPorts() { + svgElement.querySelectorAll('.port-point').forEach(p => p.remove()); + ports.forEach(renderPort); +} + +/* ========================= + * Auswahl + * ========================= */ + +function selectPort(id) { + selectedPortId = id; + + document.querySelectorAll('.port-point').forEach(el => { + el.classList.toggle('selected', el.dataset.id === id); + }); + + // TODO: Sidebar-Felder mit Portdaten füllen +} + +/* ========================= + * Drag & Drop + * ========================= */ + +function startDrag(event, portId) { + const port = getPortById(portId); + if (!port) return; + + isDragging = true; + selectedPortId = portId; + + const point = getSvgCoordinates(event); + dragOffset.x = port.x - point.x; + dragOffset.y = port.y - point.y; +} + +function onSvgMouseMove(event) { + if (!isDragging || !selectedPortId) return; + + const port = getPortById(selectedPortId); + if (!port) return; + + const point = getSvgCoordinates(event); + + port.x = point.x + dragOffset.x; + port.y = point.y + dragOffset.y; + + rerenderPorts(); +} + +function onSvgMouseUp() { + isDragging = false; +} + +/* ========================= + * Löschen + * ========================= */ + +function deleteSelectedPort() { + if (!selectedPortId) return; + + // TODO: Sicherheitsabfrage (confirm) + ports = ports.filter(p => p.id !== selectedPortId); + selectedPortId = null; + + rerenderPorts(); + + // TODO: Sidebar zurücksetzen +} + +/* ========================= + * Laden / Speichern + * ========================= */ + +function loadPorts() { + if (!DEVICE_TYPE_ID) { + console.warn('DEVICE_TYPE_ID nicht gesetzt'); + return; + } + + fetch(`${API_LOAD_PORTS}&device_type_id=${DEVICE_TYPE_ID}`) + .then(res => res.json()) + .then(data => { + // TODO: Datenformat validieren + ports = data; + rerenderPorts(); + }) + .catch(err => { + console.error('Fehler beim Laden der Ports', err); + }); +} + +function savePorts() { + if (!DEVICE_TYPE_ID) return; + + fetch(API_SAVE_PORTS, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + device_type_id: DEVICE_TYPE_ID, + ports: ports + }) + }) + .then(res => res.json()) + .then(data => { + // TODO: Erfolg / Fehler anzeigen + console.log('Ports gespeichert', data); + }) + .catch(err => { + console.error('Fehler beim Speichern', err); + }); +} + +/* ========================= + * Hilfsfunktionen + * ========================= */ + +function getSvgCoordinates(event) { + const pt = svgElement.createSVGPoint(); + pt.x = event.clientX; + pt.y = event.clientY; + + const transformed = pt.matrixTransform(svgElement.getScreenCTM().inverse()); + return { x: transformed.x, y: transformed.y }; +} + +function getPortById(id) { + return ports.find(p => p.id === id); +} + +function generateTempId() { + return 'tmp_' + Math.random().toString(36).substr(2, 9); +} + +/* ========================= + * Keyboard Shortcuts + * ========================= */ + +document.addEventListener('keydown', (e) => { + if (e.key === 'Delete') { + deleteSelectedPort(); + } +}); diff --git a/app/bootstrap.php b/app/bootstrap.php new file mode 100644 index 0000000..aba3307 --- /dev/null +++ b/app/bootstrap.php @@ -0,0 +1,48 @@ +Die Seite existiert noch nicht.

"; +} + +/* ========================= + * Template-Footer laden + * ========================= */ +require_once __DIR__ . '/templates/footer.php'; diff --git a/app/lib/_sql.php b/app/lib/_sql.php new file mode 100644 index 0000000..beb9bf7 --- /dev/null +++ b/app/lib/_sql.php @@ -0,0 +1,2 @@ + + + + + + + + diff --git a/app/templates/header.php b/app/templates/header.php new file mode 100644 index 0000000..7569fc5 --- /dev/null +++ b/app/templates/header.php @@ -0,0 +1,33 @@ + + + + + + Netzwerk-Dokumentation + + + + + + + + + + + + +
+

Netzwerk-Dokumentation

+ + + +
+ +
diff --git a/app/templates/layout.php b/app/templates/layout.php new file mode 100644 index 0000000..888bcc7 --- /dev/null +++ b/app/templates/layout.php @@ -0,0 +1,28 @@ + + + + +
+ + Inhalt fehlt

"; + } + ?> +
+ + diff --git a/app/uploads/device_types/.keep b/app/uploads/device_types/.keep new file mode 100644 index 0000000..63519ca --- /dev/null +++ b/app/uploads/device_types/.keep @@ -0,0 +1 @@ +# Upload-Verzeichnis für Gerätebilder diff --git a/app/uploads/floorplans/.keep b/app/uploads/floorplans/.keep new file mode 100644 index 0000000..6a093f3 --- /dev/null +++ b/app/uploads/floorplans/.keep @@ -0,0 +1 @@ +# Upload-Verzeichnis für Stockwerks-SVGs diff --git a/doc/DATABASE.md b/doc/DATABASE.md new file mode 100644 index 0000000..4f27c22 --- /dev/null +++ b/doc/DATABASE.md @@ -0,0 +1,282 @@ +# Datenbankdokumentation + +Dieses Dokument beschreibt das Datenbankschema des +netzwerkbasierten Dokumentations- und Verkabelungstools. + +Die Datenbank ist so entworfen, dass sie: +- reale Netzwerkinfrastrukturen exakt abbildet +- keine Annahmen über Topologien erzwingt +- auch exotische Technologien unterstützt +- langfristig erweiterbar bleibt + +Die Datenbank ist für **Single-User-Betrieb** ausgelegt. + +--- + +## Überblick + +Die Datenbank lässt sich grob in folgende Bereiche gliedern: + +1. Standort- & Gebäude-Struktur +2. Racks & physische Infrastruktur +3. Gerätetypen & Geräte +4. Ports, Module & Verbindungstypen +5. Logische & physische Verbindungen + +--- + +## 1. Standort- & Gebäude-Struktur + +### `locations` +Repräsentiert einen übergeordneten Standort (z. B. Firmencampus, Außenstelle). + +**Verwendung** +- Oberste organisatorische Ebene +- Kann mehrere Gebäude enthalten + +--- + +### `buildings` +Ein Gebäude innerhalb eines Standorts. + +**Verwendung** +- Gruppiert Stockwerke +- Ermöglicht mehrere Gebäude pro Standort + +**Beziehung** +- Gehört zu genau einem Standort + +--- + +### `floors` +Ein Stockwerk innerhalb eines Gebäudes. + +**Verwendung** +- Träger für Stockwerkspläne (SVG) +- Gruppiert Räume, Racks und Netzwerkdosen + +**Besonderheiten** +- `svg_path` verweist auf den grafischen Stockwerksplan + +--- + +### `rooms` +Ein Raum innerhalb eines Stockwerks. + +**Verwendung** +- Dient zur räumlichen Zuordnung von Netzwerkdosen +- Kann im SVG-Plan positioniert werden + +**Grafische Attribute** +- `x`, `y`, `width`, `height` zur visuellen Darstellung im Stockwerks-SVG + +--- + +## 2. Netzwerkdosen + +### `network_outlets` +Physische Netzwerkdose innerhalb eines Raums. + +**Verwendung** +- Repräsentiert Wand- oder Bodendosen +- Kann mehrere Ports besitzen + +**Grafische Attribute** +- `x`, `y` zur Platzierung im Stockwerksplan + +--- + +### `network_outlet_ports` +Einzelne Ports einer Netzwerkdose. + +**Verwendung** +- Jeder Port ist ein vollwertiger Verbindungspunkt +- Kann direkt mit Geräten, Switches oder Modulen verbunden werden + +--- + +## 3. Racks & physische Infrastruktur + +### `racks` +Ein Serverschrank oder Netzwerkschrank. + +**Verwendung** +- Befindet sich auf einem Stockwerk +- Enthält Geräte + +**Wichtige Attribute** +- `height_he`: Gesamthöhe des Racks in Höheneinheiten (HE) + +--- + +## 4. Gerätetypen & Geräte + +### `device_types` +Definiert eine Gerätevorlage. + +**Verwendung** +- Bestimmt Aussehen, Portanzahl und Portpositionen +- Wird beim Anlegen eines Geräts instanziiert + +**Grafik** +- Unterstützt SVG und Bitmap (PNG/JPG) +- Grundlage für alle grafischen Ansichten + +--- + +### `device_type_ports` +Portdefinitionen eines Gerätetyps. + +**Verwendung** +- Definiert, wo sich Ports im Bild befinden +- Wird beim Erzeugen eines Geräts kopiert + +**Grafische Attribute** +- `x`, `y`: relative Position im Gerätebild + +**Metadata** +- JSON-Feld für erweiterte Eigenschaften + +--- + +### `devices` +Konkretes Gerät in der Infrastruktur. + +**Verwendung** +- Instanz eines Gerätetyps +- Kann in einem Rack platziert werden + +**Rack-Attribute** +- `rack_position_he` +- `rack_height_he` + +--- + +### `device_ports` +Ports eines konkreten Geräts. + +**Verwendung** +- Entstehen aus `device_type_ports` +- Tragen den aktuellen Betriebszustand + +**Technische Attribute** +- Status (aktiv / deaktiviert) +- VLAN-Konfiguration +- Modus (Access, Trunk, Custom) + +--- + +## 5. Port- & Verbindungstypen + +### `port_types` +Definiert die physische oder logische Art eines Ports. + +**Beispiele** +- RJ45 +- SFP +- LC +- BNC +- Proprietär + +**Zweck** +- Einheitliche Typisierung von Ports +- Grundlage für Modul-Kompatibilität + +--- + +### `connection_types` +Definiert die Art einer Verbindung. + +**Verwendung** +- Bestimmt technische Eigenschaften +- Steuert grafische Darstellung + +**Grafische Attribute** +- Linienfarbe +- Linienart (durchgezogen, gestrichelt, gepunktet) + +--- + +## 6. Module (z. B. SFP) + +### `modules` +Ein Modul, das in einen Port eingesetzt werden kann. + +**Verwendung** +- SFP, SFP+, QSFP, Medienkonverter, Spezialkarten +- Kann selbst Ports besitzen + +--- + +### `module_ports` +Ports eines Moduls. + +**Verwendung** +- Verbindungspunkt nach außen +- Typischerweise Glasfaser- oder Spezialports + +--- + +### `device_port_modules` +Verknüpfung zwischen Geräteport und eingesetztem Modul. + +**Verwendung** +- Erlaubt modulare Portstrukturen +- Unterstützt komplexe Hardware-Topologien + +--- + +## 7. VLANs + +### `vlans` +Definiert VLANs unabhängig von Ports. + +**Verwendung** +- Wiederverwendbare VLAN-Definitionen +- Referenz in Port- und Verbindungs-Konfigurationen + +--- + +## 8. Verbindungen + +### `connections` +Zentrale Tabelle für alle Verbindungen. + +**Verwendung** +- Verbindet beliebige Ports miteinander +- Unterstützt: + - Geräteports + - Modulports + - Dosenports + +**Designentscheidung** +- Ports werden polymorph referenziert (`port_type`, `port_id`) +- Ermöglicht maximale Flexibilität ohne Schemaänderungen + +**Zusatzinformationen** +- VLAN-Konfiguration +- Betriebsmodus +- Freitext-Kommentare + +--- + +## Designphilosophie + +- **Grafik ist Teil des Modells** +- **Ports sind universelle Verbindungspunkte** +- **Keine Einschränkung auf Ethernet** +- **Keine Annahmen über Netzwerk-Topologie** +- **Erweiterbarkeit vor Perfektion** + +--- + +## Hinweis zur Erweiterung + +Das Schema ist vorbereitet für: +- Mehrbenutzerbetrieb +- Historisierung +- API-Nutzung +- Exporte +- Design-Themes + +Diese Funktionen sind bewusst **nicht Bestandteil der ersten Version**. diff --git a/doc/DATEISTRUKTUR.md b/doc/DATEISTRUKTUR.md new file mode 100644 index 0000000..10ae07c --- /dev/null +++ b/doc/DATEISTRUKTUR.md @@ -0,0 +1,11 @@ +# Dateistruktur + +```php +Gerätetypen"; +$content = ob_get_clean(); + +include __DIR__ . '/../templates/layout.php'; +``` \ No newline at end of file diff --git a/doc/UI-KONZEPT.md b/doc/UI-KONZEPT.md new file mode 100644 index 0000000..b67237e --- /dev/null +++ b/doc/UI-KONZEPT.md @@ -0,0 +1,223 @@ +# UI-/UX-Konzept: SVG-Port-Editor + +Der SVG-Port-Editor dient zur Definition von Ports auf Gerätetypen +(Switches, Patchpanels, Server, Module etc.). + +Er ist ein **technischer Editor**, kein Grafiktool, und verfolgt folgende Ziele: +- präzise Port-Positionierung +- schnelle Bedienbarkeit +- Fehlertoleranz +- spätere Design-Erweiterbarkeit + +--- + +## Zielgruppe + +- Technische Administratoren +- Netzwerkplaner +- Dokumentationsverantwortliche + +Keine Design-Kenntnisse notwendig. + +--- + +## Grundidee + +Der Editor arbeitet nach dem Prinzip: + +> **Bild hochladen → Ports klicken → Ports konfigurieren** + +Ports werden als **logische Punkte** auf einer Gerätegrafik definiert und +später in allen Ansichten (Rack, Netzwerk, Verbindungen) wiederverwendet. + +--- + +## Seitenaufbau + +### Layout (Desktop) + +``` ++------------------------------------------------------+ +| Header: Gerätetyp bearbeiten | ++----------------------+-------------------------------+ +| Sidebar (Ports) | Hauptbereich (SVG / Bild) | +| | | +| + Port hinzufügen | [ Gerätebild ] | +| | o o o o | +| Portliste | | +| - Port 1 | | +| - Port 2 | | +| - Port 3 | | +| | | ++----------------------+-------------------------------+ +| Footer: Speichern / Abbrechen | ++------------------------------------------------------+ +``` + + +--- + +## Schritt 1: Gerätetyp anlegen + +### Pflichtangaben +- Name +- Kategorie (Switch, Server, Patchpanel, Sonstiges) +- Bild (SVG oder PNG/JPG) + +Nach dem Speichern wird automatisch der **Port-Editor** geöffnet. + +--- + +## Schritt 2: Port-Editor – Interaktion + +### Port hinzufügen + +**Interaktion** +- Klick auf das Bild +- An der Klickposition erscheint ein neuer Portpunkt + +**Visuelle Darstellung** +- Kreis (Standard: grau) +- Nummer / Name daneben + +--- + +### Port auswählen + +- Klick auf bestehenden Port +- Port wird hervorgehoben +- Sidebar zeigt Port-Eigenschaften + +--- + +### Port verschieben + +- Drag & Drop +- Snap optional (Raster später erweiterbar) +- Position wird relativ zum Bild gespeichert + +--- + +### Port löschen + +- Button in Sidebar +- Sicherheitsabfrage + +--- + +## Sidebar: Port-Eigenschaften + +### Basisfelder +- Portname (z. B. `Gi1/0/1`, `Port 1`) +- Porttyp (Dropdown, frei definierbar) +- Kommentar (optional) + +### Erweiterte Eigenschaften (optional) +- Standard-Geschwindigkeit +- Default-Modus +- Metadata (JSON, optional, für Sonderfälle) + +--- + +## Visuelle Zustände von Ports + +| Zustand | Darstellung | +|------------------|---------------------------------| +| Normal | Grauer Kreis | +| Hover | Hervorgehoben | +| Aktiv / selektiert | Farblich markiert | +| Fehlerhaft | Rot / Warnsymbol | + +--- + +## Koordinatensystem + +- Ports speichern **relative Koordinaten** + - Prozentual oder ViewBox-basiert +- Unabhängig von Bildauflösung +- Garantiert korrekte Skalierung: + - Rack-Ansicht + - Netzwerkansicht + - Zoom + +--- + +## SVG vs. Bitmap + +### SVG +- Ports werden als eigene SVG-Layer dargestellt +- Exakte Skalierung +- Ideal für professionelle Gerätebilder + +### Bitmap (PNG/JPG) +- Ports als Overlay-Layer +- Relative Positionierung +- Einfacher Einstieg + +Beide Varianten nutzen **identische Logik**. + +--- + +## Validierungen + +Beim Speichern wird geprüft: +- Portnamen eindeutig innerhalb des Gerätetyps +- Porttyp gesetzt +- Position innerhalb des Bildbereichs + +Warnungen statt harter Fehler, wo sinnvoll. + +--- + +## UX-Details + +### Undo (optional, später) +- Letzte Aktion rückgängig machen + +### Keyboard-Shortcuts (optional) +- `DEL`: Port löschen +- `ESC`: Auswahl aufheben + +--- + +## Speichern + +- Autosave optional +- Manuelles Speichern immer möglich +- Nach Speichern: + - Ports stehen für neue Geräte zur Verfügung + - Bestehende Geräte bleiben unverändert (keine Retro-Änderung) + +--- + +## Abgrenzung: Gerät vs. Gerätetyp + +Wichtiges UX-Prinzip: +- **Port-Editor arbeitet ausschließlich auf Gerätetypen** +- Konkrete Geräte: + - übernehmen die Portstruktur + - können Ports nicht verschieben + - nur konfigurieren (VLAN, Status etc.) + +--- + +## Erweiterungen (nicht initial) + +- Raster / Magnetlinien +- Port-Gruppierung (z. B. Portblöcke) +- Automatische Nummerierung +- Copy/Paste von Ports +- Zoom & Pan +- Touch-Unterstützung + +--- + +## Zielzustand + +Der SVG-Port-Editor soll: +- technisch korrekt +- schnell erlernbar +- präzise +- robust gegen Sonderfälle + +sein – ohne den Nutzer mit Design- oder Grafikdetails zu belasten. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..1986a38 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,40 @@ +services: + web: + image: php:8.3-apache + container_name: netdoc_web + ports: + - "80:80" + volumes: + - ./app:/var/www/html + depends_on: + - db + restart: "no" + + db: + image: mariadb:11 + container_name: netdoc_db + environment: + MARIADB_ROOT_PASSWORD: root + MARIADB_DATABASE: netdoc + MARIADB_USER: netdoc + MARIADB_PASSWORD: netdoc + volumes: + - db_data:/var/lib/mysql + - ./init.sql:/docker-entrypoint-initdb.d/init.sql + restart: "no" + + phpmyadmin: + image: phpmyadmin/phpmyadmin + container_name: netdoc_phpmyadmin + ports: + - "8080:80" + environment: + PMA_HOST: db + PMA_USER: netdoc + PMA_PASSWORD: netdoc + depends_on: + - db + restart: "no" + +volumes: + db_data: diff --git a/init.sql b/init.sql new file mode 100644 index 0000000..187ec53 --- /dev/null +++ b/init.sql @@ -0,0 +1,185 @@ +CREATE TABLE locations ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + comment TEXT +) ENGINE=InnoDB; + +CREATE TABLE buildings ( + id INT AUTO_INCREMENT PRIMARY KEY, + location_id INT NOT NULL, + name VARCHAR(255) NOT NULL, + comment TEXT, + FOREIGN KEY (location_id) REFERENCES locations(id) + ON DELETE CASCADE +) ENGINE=InnoDB; + +CREATE TABLE floors ( + id INT AUTO_INCREMENT PRIMARY KEY, + building_id INT NOT NULL, + name VARCHAR(255) NOT NULL, + level INT, + svg_path VARCHAR(255), + comment TEXT, + FOREIGN KEY (building_id) REFERENCES buildings(id) + ON DELETE CASCADE +) ENGINE=InnoDB; + +CREATE TABLE rooms ( + id INT AUTO_INCREMENT PRIMARY KEY, + floor_id INT NOT NULL, + name VARCHAR(255) NOT NULL, + number VARCHAR(50), + x INT, + y INT, + width INT, + height INT, + comment TEXT, + FOREIGN KEY (floor_id) REFERENCES floors(id) + ON DELETE CASCADE +) ENGINE=InnoDB; + +CREATE TABLE network_outlets ( + id INT AUTO_INCREMENT PRIMARY KEY, + room_id INT NOT NULL, + name VARCHAR(255) NOT NULL, + x INT, + y INT, + comment TEXT, + FOREIGN KEY (room_id) REFERENCES rooms(id) + ON DELETE CASCADE +) ENGINE=InnoDB; + +CREATE TABLE network_outlet_ports ( + id INT AUTO_INCREMENT PRIMARY KEY, + outlet_id INT NOT NULL, + name VARCHAR(50) NOT NULL, + port_type_id INT, + FOREIGN KEY (outlet_id) REFERENCES network_outlets(id) + ON DELETE CASCADE +) ENGINE=InnoDB; + +CREATE TABLE racks ( + id INT AUTO_INCREMENT PRIMARY KEY, + floor_id INT NOT NULL, + name VARCHAR(255) NOT NULL, + height_he INT NOT NULL, + comment TEXT, + FOREIGN KEY (floor_id) REFERENCES floors(id) + ON DELETE CASCADE +) ENGINE=InnoDB; + +CREATE TABLE device_types ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + category ENUM('switch','server','patchpanel','other') NOT NULL, + image_path VARCHAR(255), + image_type ENUM('svg','bitmap') NOT NULL, + comment TEXT +) ENGINE=InnoDB; + +CREATE TABLE device_type_ports ( + id INT AUTO_INCREMENT PRIMARY KEY, + device_type_id INT NOT NULL, + name VARCHAR(50) NOT NULL, + port_type_id INT, + x INT NOT NULL, + y INT NOT NULL, + metadata JSON, + FOREIGN KEY (device_type_id) REFERENCES device_types(id) + ON DELETE CASCADE +) ENGINE=InnoDB; + +CREATE TABLE devices ( + id INT AUTO_INCREMENT PRIMARY KEY, + device_type_id INT NOT NULL, + rack_id INT, + name VARCHAR(255) NOT NULL, + rack_position_he INT, + rack_height_he INT, + serial_number VARCHAR(255), + comment TEXT, + FOREIGN KEY (device_type_id) REFERENCES device_types(id), + FOREIGN KEY (rack_id) REFERENCES racks(id) + ON DELETE SET NULL +) ENGINE=InnoDB; + +CREATE TABLE device_ports ( + id INT AUTO_INCREMENT PRIMARY KEY, + device_id INT NOT NULL, + name VARCHAR(50) NOT NULL, + port_type_id INT, + status ENUM('active','disabled') DEFAULT 'active', + mode VARCHAR(50), + vlan_config JSON, + FOREIGN KEY (device_id) REFERENCES devices(id) + ON DELETE CASCADE +) ENGINE=InnoDB; + +CREATE TABLE port_types ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(100) NOT NULL, + medium ENUM('copper','fiber','coax','other') NOT NULL, + comment TEXT +) ENGINE=InnoDB; + +CREATE TABLE connection_types ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + medium ENUM('copper','fiber','coax','other') NOT NULL, + duplex ENUM('half','full','custom') DEFAULT 'custom', + max_speed VARCHAR(50), + color VARCHAR(20), + line_style ENUM('solid','dashed','dotted') DEFAULT 'solid', + comment TEXT +) ENGINE=InnoDB; + +CREATE TABLE modules ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + module_type VARCHAR(100), + comment TEXT +) ENGINE=InnoDB; + +CREATE TABLE module_ports ( + id INT AUTO_INCREMENT PRIMARY KEY, + module_id INT NOT NULL, + name VARCHAR(50) NOT NULL, + port_type_id INT, + FOREIGN KEY (module_id) REFERENCES modules(id) + ON DELETE CASCADE +) ENGINE=InnoDB; + +CREATE TABLE device_port_modules ( + id INT AUTO_INCREMENT PRIMARY KEY, + device_port_id INT NOT NULL, + module_id INT NOT NULL, + FOREIGN KEY (device_port_id) REFERENCES device_ports(id) + ON DELETE CASCADE, + FOREIGN KEY (module_id) REFERENCES modules(id) + ON DELETE CASCADE +) ENGINE=InnoDB; + +CREATE TABLE vlans ( + id INT AUTO_INCREMENT PRIMARY KEY, + vlan_id INT NOT NULL, + name VARCHAR(255), + comment TEXT +) ENGINE=InnoDB; + +CREATE TABLE connections ( + id INT AUTO_INCREMENT PRIMARY KEY, + connection_type_id INT NOT NULL, + + port_a_type ENUM('device','module','outlet') NOT NULL, + port_a_id INT NOT NULL, + + port_b_type ENUM('device','module','outlet') NOT NULL, + port_b_id INT NOT NULL, + + vlan_config JSON, + mode VARCHAR(50), + comment TEXT, + + FOREIGN KEY (connection_type_id) REFERENCES connection_types(id) +) ENGINE=InnoDB; +