feat: Implement initial application structure with network view and SVG editor

- Added network-view.js for visualizing network topology with devices and connections.
- Introduced svg-editor.js for managing ports on device types with drag-and-drop functionality.
- Created bootstrap.php for application initialization, including configuration and database connection.
- Established config.php for centralized configuration settings.
- Developed index.php as the main entry point with module-based routing.
- Integrated _sql.php for database abstraction.
- Added auth.php for single-user authentication handling.
- Included helpers.php for utility functions.
- Created modules for managing connections, device types, devices, and floors.
- Implemented database schema in init.sql for locations, buildings, floors, rooms, network outlets, devices, and connections.
- Added Docker support with docker-compose.yml for web and database services.
- Documented database structure and UI/UX concepts in respective markdown files.
This commit is contained in:
Troy Grunt
2026-02-05 23:41:54 +01:00
parent 13995695db
commit 5066262fca
39 changed files with 1829 additions and 0 deletions

4
NOTES.md Normal file
View File

@@ -0,0 +1,4 @@
# Notizen
https://chatgpt.com/share/698517b9-b1e4-800e-bbd8-d207dfb326f0

250
README.md
View File

@@ -1,2 +1,252 @@
# netwatch # 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.

48
app/.htaccess Normal file
View File

@@ -0,0 +1,48 @@
# =========================
# Grundschutz
# =========================
# Kein Directory-Listing
Options -Indexes
# Schutz für sensible Dateien
<FilesMatch "(\.env|\.git|config\.php|_sql\.php)">
Require all denied
</FilesMatch>
# 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
# =========================
<IfModule mod_headers.c>
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:;"
</IfModule>
# =========================
# Upload-Sicherheit
# =========================
<FilesMatch "\.(php|phtml|php3|php4|php5|php7|phps)$">
Require all denied
</FilesMatch>
# TODO: Optional: Upload-Verzeichnisse (device_types, floorplans) via .htaccess zusätzlich schützen

2
app/api/connections.php Normal file
View File

@@ -0,0 +1,2 @@
<?php
// API für Verbindungsdaten (Netzwerkansicht)

View File

@@ -0,0 +1,2 @@
<?php
// Laden und Speichern von Port-Punkten für den SVG-Port-Editor

2
app/api/upload.php Normal file
View File

@@ -0,0 +1,2 @@
<?php
// Datei-Upload für Gerätebilder und SVGs

1
app/assets/css/app.css Normal file
View File

@@ -0,0 +1 @@
/* Zentrales Stylesheet (Layout, Farben, Komponenten) */

1
app/assets/js/app.js Normal file
View File

@@ -0,0 +1 @@
// Globale JS-Funktionen, Initialisierung

View File

@@ -0,0 +1,289 @@
// Netzwerk-Graph-Ansicht (Nodes, Kanten, Filter)
/**
* network-view.js
*
* Darstellung der Netzwerk-Topologie:
* - Geräte als Nodes
* - Ports als Ankerpunkte
* - Verbindungen als Linien
* - Freie / selbstdefinierte Verbindungstypen
*
* Kein Layout-Framework (kein D3, kein Cytoscape)
* -> 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?
});

258
app/assets/js/svg-editor.js Normal file
View File

@@ -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();
}
});

48
app/bootstrap.php Normal file
View File

@@ -0,0 +1,48 @@
<?php
/**
* bootstrap.php
*
* Initialisierung der Anwendung
* - Config laden
* - Session starten
* - DB-Verbindung über _sql.php
* - Helper einbinden
*/
/* =========================
* Config laden
* ========================= */
require_once __DIR__ . '/config.php';
// TODO: Config-Datei mit DB-Zugang, Pfaden, globalen Settings füllen
/* =========================
* Session starten
* ========================= */
session_start();
// TODO: Single-User Auth prüfen
// z.B. $_SESSION['user'] setzen oder Login erzwingen
/* =========================
* DB-Verbindung initialisieren
* ========================= */
require_once __DIR__ . '/lib/_sql.php';
// TODO: Host, User, Passwort, DB aus config.php nutzen
$sql = new SQL(
DB_HOST, // z.B. localhost
DB_USER, // z.B. netdoc
DB_PASSWORD, // z.B. netdoc
DB_NAME // z.B. netdoc
);
/* =========================
* Helper laden
* ========================= */
require_once __DIR__ . '/lib/helpers.php';
// TODO: Globale Funktionen: escape, redirect, flash messages, etc.
/* =========================
* Optional: Fehlerbehandlung
* ========================= */
// error_reporting(E_ALL);
// ini_set('display_errors', 1);

2
app/config.php Normal file
View File

@@ -0,0 +1,2 @@
<?php
// Zentrale Konfiguration (DB-Zugangsdaten, Pfade, globale Settings)

67
app/index.php Normal file
View File

@@ -0,0 +1,67 @@
<?php
// Einstiegspunkt der Anwendung, Routing zur jeweiligen Modul-Seite
/**
* index.php
*
* Einstiegspunkt der Anwendung
* - Single-User
* - Modulbasiertes Routing
* - Basierend auf _sql.php
* - HTML-Layout via templates/layout.php
*/
/* =========================
* Bootstrap
* ========================= */
require_once __DIR__ . '/bootstrap.php'; // lädt config, DB, helper
// TODO: Session starten / Single-User-Auth prüfen
/* =========================
* Routing
* ========================= */
// Standard-Modul / Aktion
$module = $_GET['module'] ?? 'dashboard';
$action = $_GET['action'] ?? 'list';
// Whitelist der Module
$validModules = ['dashboard', 'device_types', 'devices', 'racks', 'floors', 'connections'];
// Whitelist der Aktionen
$validActions = ['list', 'edit', 'save', 'ports'];
// Prüfen auf gültige Werte
if (!in_array($module, $validModules)) {
// TODO: Fehlerseite anzeigen
die('Ungültiges Modul');
}
if (!in_array($action, $validActions)) {
// TODO: Fehlerseite anzeigen
die('Ungültige Aktion');
}
/* =========================
* Template-Header laden
* ========================= */
require_once __DIR__ . '/templates/header.php';
// TODO: ggf. Navigation einbinden
/* =========================
* Modul laden
* ========================= */
$modulePath = __DIR__ . "/modules/$module/$action.php";
if (file_exists($modulePath)) {
require_once $modulePath;
} else {
// TODO: Fehlerseite oder 404
echo "<p>Die Seite existiert noch nicht.</p>";
}
/* =========================
* Template-Footer laden
* ========================= */
require_once __DIR__ . '/templates/footer.php';

2
app/lib/_sql.php Normal file
View File

@@ -0,0 +1,2 @@
<?php
// Datenbank-Abstraktionsklasse (bereits vorhanden, hier eingebunden)

2
app/lib/auth.php Normal file
View File

@@ -0,0 +1,2 @@
<?php
// Single-User-Authentifizierung (Login, Session-Handling)

2
app/lib/helpers.php Normal file
View File

@@ -0,0 +1,2 @@
<?php
// Hilfsfunktionen (Escaping, Redirects, Formatierungen)

View File

@@ -0,0 +1,2 @@
<?php
// Anzeige aller Verbindungen (Filter, Übersicht)

View File

@@ -0,0 +1,2 @@
<?php
// Verbindung anlegen oder ändern

View File

@@ -0,0 +1,2 @@
<?php
// Gerätetyp anlegen oder bearbeiten (Formular)

View File

@@ -0,0 +1,2 @@
<?php
// Liste aller Gerätetypen anzeigen

View File

@@ -0,0 +1,2 @@
<?php
// SVG-Port-Editor für einen Gerätetyp

View File

@@ -0,0 +1,2 @@
<?php
// Gerätetyp speichern (INSERT / UPDATE)

View File

@@ -0,0 +1,2 @@
<?php
// Gerät anlegen oder bearbeiten

View File

@@ -0,0 +1,2 @@
<?php
// Liste aller Geräte anzeigen

View File

@@ -0,0 +1,2 @@
<?php
// Gerät speichern (Rack-Position, Typ, Name)

View File

@@ -0,0 +1,2 @@
<?php
// Stockwerk bearbeiten inkl. SVG-Plan

View File

@@ -0,0 +1,2 @@
<?php
// Übersicht aller Stockwerke

View File

@@ -0,0 +1,2 @@
<?php
// Rack anlegen oder bearbeiten

View File

@@ -0,0 +1,2 @@
<?php
// Übersicht aller Racks

19
app/templates/footer.php Normal file
View File

@@ -0,0 +1,19 @@
<?php
/**
* footer.php
*
* HTML-Footer, Scripts, evtl. Modale oder Notifications
* Wird am Ende jeder Seite eingebunden
*/
?>
</main>
<footer>
<p>&copy; <?php echo date('Y'); ?> Meine Firma - Netzwerk Dokumentation</p>
<!-- TODO: Optional: Statusanzeige, Debug-Info, Session-Hinweis -->
</footer>
<!-- TODO: evtl. JS für modale Fenster oder Flash Messages -->
</body>
</html>

33
app/templates/header.php Normal file
View File

@@ -0,0 +1,33 @@
<?php
/**
* header.php
*
* HTML-Kopf, CSS / JS einbinden, Navigation
* Wird am Anfang jeder Seite eingebunden
*/
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Netzwerk-Dokumentation</title>
<!-- CSS -->
<link rel="stylesheet" href="/assets/css/app.css">
<!-- JS -->
<script src="/assets/js/app.js" defer></script>
<script src="/assets/js/svg-editor.js" defer></script>
<script src="/assets/js/network-view.js" defer></script>
<!-- TODO: Meta-Tags, Favicon -->
</head>
<body>
<header>
<h1>Netzwerk-Dokumentation</h1>
<!-- TODO: Navigation einfügen -->
<!-- Beispiel: Links zu Dashboard, Gerätetypen, Geräte, Racks, Floors, Connections -->
</header>
<main>

28
app/templates/layout.php Normal file
View File

@@ -0,0 +1,28 @@
<?php
/**
* layout.php
*
* Grundlayout: Header + Content + Footer
* Kann als Basis-Template dienen, falls Module HTML ausgeben
*
* Beispiel-Aufruf in Modul:
* include __DIR__ . '/../templates/layout.php';
*
* TODO: In Zukunft: zentrales Template-System (z.B. mit $content)
*/
?>
<?php include __DIR__ . '/header.php'; ?>
<div class="content-wrapper">
<!-- TODO: Dynamischen Content hier einfügen -->
<?php
if (isset($content)) {
echo $content;
} else {
echo "<p>Inhalt fehlt</p>";
}
?>
</div>
<?php include __DIR__ . '/footer.php'; ?>

View File

@@ -0,0 +1 @@
# Upload-Verzeichnis für Gerätebilder

View File

@@ -0,0 +1 @@
# Upload-Verzeichnis für Stockwerks-SVGs

282
doc/DATABASE.md Normal file
View File

@@ -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**.

11
doc/DATEISTRUKTUR.md Normal file
View File

@@ -0,0 +1,11 @@
# Dateistruktur
```php
<?php
ob_start();
// HTML / Modul-Logik
echo "<h2>Gerätetypen</h2>";
$content = ob_get_clean();
include __DIR__ . '/../templates/layout.php';
```

223
doc/UI-KONZEPT.md Normal file
View File

@@ -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.

40
docker-compose.yml Normal file
View File

@@ -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:

185
init.sql Normal file
View File

@@ -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;