Compare commits
3 Commits
12141485ae
...
ce4ef5527f
| Author | SHA1 | Date | |
|---|---|---|---|
| ce4ef5527f | |||
| 4a23713d31 | |||
| 510a248edb |
15
TODO.md
15
TODO.md
@@ -4,6 +4,21 @@ Zentrale Sammlung aller TODO-Markierungen im Repository (Stand: 13. Februar 2026
|
|||||||
|
|
||||||
Hinweis: Die Eintraege sind direkt aus den Quelldateien aggregiert.
|
Hinweis: Die Eintraege sind direkt aus den Quelldateien aggregiert.
|
||||||
|
|
||||||
|
## Arbeitsnotizen (16. Februar 2026)
|
||||||
|
|
||||||
|
- [x] API-Basis umgesetzt: `app/api/connections.php`, `app/api/device_type_ports.php`, `app/api/upload.php` auf aktuelles Schema gebracht (Auth, Validierung, Existenzpruefungen, Fehlerantworten).
|
||||||
|
- [x] Bootstrap/Auth/Config/Routing umgesetzt: `app/config.php`, `app/bootstrap.php`, `app/lib/_sql.php`, `app/lib/auth.php`, `app/index.php`.
|
||||||
|
- [x] Frontend-Grundlagen aktualisiert: `app/assets/js/app.js`, `app/assets/js/dashboard.js`, `app/assets/js/svg-editor.js`, `app/assets/js/network-view.js`.
|
||||||
|
- [x] Delete-Flow fuer zentrale Module umgesetzt: `buildings`, `floors`, `racks`, `device_types`, `floor_infrastructure`.
|
||||||
|
- [x] Legacy-Mock ersetzt: `app/modules/device_types/ports.php` lauffaehig gemacht (anzeigen, hinzufuegen, loeschen).
|
||||||
|
- [x] TODO-Reste in `header.php`, `footer.php`, `layout.php`, `floor_infrastructure/edit.php` entfernt.
|
||||||
|
|
||||||
|
Offene Blocker / naechste Punkte:
|
||||||
|
- [ ] `app/modules/connections/list.php`: Detailbereich fuer ausgewaehlte Verbindung sowie Bearbeiten/Loeschen im UI fehlen noch.
|
||||||
|
- [ ] `app/modules/dashboard/list.php`: grosse zoombare Gesamt-Topologie-Wand (fachlich/grafisch groesseres Feature).
|
||||||
|
- [ ] `app/lib/helpers.php`: generischer Sammel-TODO ohne konkreten Scope.
|
||||||
|
- [ ] Vollstaendiger End-to-End-Test aktuell nicht moeglich, da in dieser Shell kein `php` CLI verfuegbar ist.
|
||||||
|
|
||||||
## app\api\connections.php
|
## app\api\connections.php
|
||||||
|
|
||||||
- [ ] L15: // TODO: Single-User-Auth prüfen
|
- [ ] L15: // TODO: Single-User-Auth prüfen
|
||||||
|
|||||||
@@ -1,194 +1,273 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* app/api/connections.php
|
* app/api/connections.php
|
||||||
*
|
|
||||||
* API für Netzwerkverbindungen (Port ↔ Port)
|
|
||||||
* - Laden der Topologie
|
|
||||||
* - Anlegen / Bearbeiten / Löschen von Verbindungen
|
|
||||||
* - Unterstützt freie Verbindungstypen (Kupfer, LWL, BNC, Token Ring, etc.)
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
require_once __DIR__ . '/../bootstrap.php';
|
require_once __DIR__ . '/../bootstrap.php';
|
||||||
|
requireAuth();
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
// TODO: Single-User-Auth prüfen
|
|
||||||
// if (!$_SESSION['user']) { http_response_code(403); exit; }
|
|
||||||
|
|
||||||
$action = $_GET['action'] ?? 'load';
|
$action = $_GET['action'] ?? 'load';
|
||||||
|
|
||||||
/* =========================
|
|
||||||
* Router
|
|
||||||
* ========================= */
|
|
||||||
|
|
||||||
switch ($action) {
|
switch ($action) {
|
||||||
|
|
||||||
case 'load':
|
case 'load':
|
||||||
loadConnections($sql);
|
loadConnections($sql);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'save':
|
case 'save':
|
||||||
saveConnection($sql);
|
saveConnection($sql);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'delete':
|
case 'delete':
|
||||||
deleteConnection($sql);
|
deleteConnection($sql);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
http_response_code(400);
|
jsonError('Unbekannte Aktion', 400);
|
||||||
echo json_encode(['error' => 'Unbekannte Aktion']);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* =========================
|
function jsonError(string $message, int $status = 400): void
|
||||||
* Aktionen
|
|
||||||
* ========================= */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lädt alle Geräte + Ports + Verbindungen für eine Netzwerkansicht
|
|
||||||
*/
|
|
||||||
function loadConnections($sql)
|
|
||||||
{
|
{
|
||||||
$contextId = $_GET['context_id'] ?? null;
|
http_response_code($status);
|
||||||
|
echo json_encode(['error' => $message]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
if (!$contextId) {
|
function normalizeEndpointType(string $type): ?string
|
||||||
http_response_code(400);
|
{
|
||||||
echo json_encode(['error' => 'context_id fehlt']);
|
$map = [
|
||||||
return;
|
'device' => 'device',
|
||||||
|
'device_ports' => 'device',
|
||||||
|
'module' => 'module',
|
||||||
|
'module_ports' => 'module',
|
||||||
|
'outlet' => 'outlet',
|
||||||
|
'network_outlet_ports' => 'outlet',
|
||||||
|
'patchpanel' => 'patchpanel',
|
||||||
|
'floor_patchpanel_ports' => 'patchpanel',
|
||||||
|
];
|
||||||
|
|
||||||
|
$key = strtolower(trim($type));
|
||||||
|
return $map[$key] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function endpointExists($sql, string $type, int $id): bool
|
||||||
|
{
|
||||||
|
if ($id <= 0) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Kontext definieren (Standort, Rack, Floor, gesamtes Netz)
|
if ($type === 'device') {
|
||||||
|
$row = $sql->single('SELECT id FROM device_ports WHERE id = ?', 'i', [$id]);
|
||||||
|
return !empty($row);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($type === 'module') {
|
||||||
|
$row = $sql->single('SELECT id FROM module_ports WHERE id = ?', 'i', [$id]);
|
||||||
|
return !empty($row);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($type === 'outlet') {
|
||||||
|
$row = $sql->single('SELECT id FROM network_outlet_ports WHERE id = ?', 'i', [$id]);
|
||||||
|
return !empty($row);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($type === 'patchpanel') {
|
||||||
|
$row = $sql->single('SELECT id FROM floor_patchpanel_ports WHERE id = ?', 'i', [$id]);
|
||||||
|
return !empty($row);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadConnections($sql): void
|
||||||
|
{
|
||||||
|
$contextType = strtolower(trim((string)($_GET['context_type'] ?? 'all')));
|
||||||
|
$contextId = isset($_GET['context_id']) ? (int)$_GET['context_id'] : 0;
|
||||||
|
|
||||||
|
$where = '';
|
||||||
|
$bindType = '';
|
||||||
|
$bindValues = [];
|
||||||
|
|
||||||
|
if ($contextType !== 'all') {
|
||||||
|
if ($contextId <= 0) {
|
||||||
|
jsonError('context_id fehlt oder ist ungueltig', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($contextType === 'location') {
|
||||||
|
$where = ' WHERE b.location_id = ?';
|
||||||
|
$bindType = 'i';
|
||||||
|
$bindValues = [$contextId];
|
||||||
|
} elseif ($contextType === 'building') {
|
||||||
|
$where = ' WHERE f.building_id = ?';
|
||||||
|
$bindType = 'i';
|
||||||
|
$bindValues = [$contextId];
|
||||||
|
} elseif ($contextType === 'floor') {
|
||||||
|
$where = ' WHERE r.floor_id = ?';
|
||||||
|
$bindType = 'i';
|
||||||
|
$bindValues = [$contextId];
|
||||||
|
} elseif ($contextType === 'rack') {
|
||||||
|
$where = ' WHERE d.rack_id = ?';
|
||||||
|
$bindType = 'i';
|
||||||
|
$bindValues = [$contextId];
|
||||||
|
} else {
|
||||||
|
jsonError('Ungueltiger Kontext. Erlaubt: all, location, building, floor, rack', 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------- Geräte ---------- */
|
|
||||||
$devices = $sql->get(
|
$devices = $sql->get(
|
||||||
"SELECT id, name, device_type_id, pos_x, pos_y
|
"SELECT d.id, d.name, d.device_type_id, d.rack_id, 0 AS pos_x, 0 AS pos_y
|
||||||
FROM devices
|
FROM devices d
|
||||||
WHERE context_id = ?",
|
LEFT JOIN racks r ON r.id = d.rack_id
|
||||||
"i",
|
LEFT JOIN floors f ON f.id = r.floor_id
|
||||||
[$contextId]
|
LEFT JOIN buildings b ON b.id = f.building_id" . $where,
|
||||||
|
$bindType,
|
||||||
|
$bindValues
|
||||||
);
|
);
|
||||||
|
|
||||||
/* ---------- Ports ---------- */
|
|
||||||
$ports = $sql->get(
|
$ports = $sql->get(
|
||||||
"SELECT p.id, p.device_id, p.name, p.port_type_id
|
"SELECT dp.id, dp.device_id, dp.name, dp.port_type_id
|
||||||
FROM ports p
|
FROM device_ports dp
|
||||||
JOIN devices d ON d.id = p.device_id
|
JOIN devices d ON d.id = dp.device_id
|
||||||
WHERE d.context_id = ?",
|
LEFT JOIN racks r ON r.id = d.rack_id
|
||||||
"i",
|
LEFT JOIN floors f ON f.id = r.floor_id
|
||||||
[$contextId]
|
LEFT JOIN buildings b ON b.id = f.building_id" . $where,
|
||||||
|
$bindType,
|
||||||
|
$bindValues
|
||||||
);
|
);
|
||||||
|
|
||||||
/* ---------- Verbindungen ---------- */
|
|
||||||
$connections = $sql->get(
|
$connections = $sql->get(
|
||||||
"SELECT
|
"SELECT id, connection_type_id, port_a_type, port_a_id, port_b_type, port_b_id, vlan_config, mode, comment
|
||||||
c.id,
|
FROM connections",
|
||||||
c.connection_type_id,
|
'',
|
||||||
c.port_a_id,
|
|
||||||
c.port_b_id,
|
|
||||||
c.vlan,
|
|
||||||
c.mode,
|
|
||||||
c.comment
|
|
||||||
FROM connections c",
|
|
||||||
"",
|
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
'devices' => $devices,
|
'devices' => $devices ?: [],
|
||||||
'ports' => $ports,
|
'ports' => $ports ?: [],
|
||||||
'connections' => $connections
|
'connections' => $connections ?: []
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function resolveConnectionTypeId($sql, array $data): int
|
||||||
* Speichert eine Verbindung (neu oder Update)
|
|
||||||
*/
|
|
||||||
function saveConnection($sql)
|
|
||||||
{
|
{
|
||||||
$data = json_decode(file_get_contents('php://input'), true);
|
if (!empty($data['connection_type_id'])) {
|
||||||
|
$requestedId = (int)$data['connection_type_id'];
|
||||||
if (!$data) {
|
$exists = $sql->single('SELECT id FROM connection_types WHERE id = ?', 'i', [$requestedId]);
|
||||||
http_response_code(400);
|
if (!$exists) {
|
||||||
echo json_encode(['error' => 'Ungültige JSON-Daten']);
|
jsonError('connection_type_id existiert nicht', 400);
|
||||||
return;
|
}
|
||||||
|
return $requestedId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Validierung
|
$defaultType = $sql->single('SELECT id FROM connection_types ORDER BY id ASC LIMIT 1');
|
||||||
// - port_a_id vorhanden
|
if (!$defaultType) {
|
||||||
// - port_b_id vorhanden
|
jsonError('Kein Verbindungstyp vorhanden', 400);
|
||||||
// - Verbindungstyp erlaubt
|
}
|
||||||
|
|
||||||
|
return (int)$defaultType['id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveConnection($sql): void
|
||||||
|
{
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
jsonError('Methode nicht erlaubt', 405);
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
if (!is_array($data)) {
|
||||||
|
jsonError('Ungueltige JSON-Daten', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$portAType = normalizeEndpointType((string)($data['port_a_type'] ?? ''));
|
||||||
|
$portBType = normalizeEndpointType((string)($data['port_b_type'] ?? ''));
|
||||||
|
$portAId = (int)($data['port_a_id'] ?? 0);
|
||||||
|
$portBId = (int)($data['port_b_id'] ?? 0);
|
||||||
|
|
||||||
|
if ($portAType === null || $portBType === null) {
|
||||||
|
jsonError('port_a_type/port_b_type ungueltig', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($portAId <= 0 || $portBId <= 0) {
|
||||||
|
jsonError('port_a_id und port_b_id sind erforderlich', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($portAType === $portBType && $portAId === $portBId) {
|
||||||
|
jsonError('Port A und Port B duerfen nicht identisch sein', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!endpointExists($sql, $portAType, $portAId) || !endpointExists($sql, $portBType, $portBId)) {
|
||||||
|
jsonError('Mindestens ein Endpunkt existiert nicht', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$connectionTypeId = resolveConnectionTypeId($sql, $data);
|
||||||
|
|
||||||
|
$vlanConfig = $data['vlan_config'] ?? null;
|
||||||
|
if (is_array($vlanConfig)) {
|
||||||
|
$vlanConfig = json_encode($vlanConfig);
|
||||||
|
} elseif (!is_string($vlanConfig) && $vlanConfig !== null) {
|
||||||
|
jsonError('vlan_config muss String, Array oder null sein', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$mode = isset($data['mode']) ? (string)$data['mode'] : null;
|
||||||
|
$comment = isset($data['comment']) ? (string)$data['comment'] : null;
|
||||||
|
|
||||||
if (!empty($data['id'])) {
|
if (!empty($data['id'])) {
|
||||||
// UPDATE
|
$id = (int)$data['id'];
|
||||||
|
$existing = $sql->single('SELECT id FROM connections WHERE id = ?', 'i', [$id]);
|
||||||
|
if (!$existing) {
|
||||||
|
jsonError('Verbindung existiert nicht', 404);
|
||||||
|
}
|
||||||
|
|
||||||
$rows = $sql->set(
|
$rows = $sql->set(
|
||||||
"UPDATE connections
|
'UPDATE connections
|
||||||
SET connection_type_id = ?, port_a_id = ?, port_b_id = ?, vlan = ?, mode = ?, comment = ?
|
SET connection_type_id = ?, port_a_type = ?, port_a_id = ?, port_b_type = ?, port_b_id = ?, vlan_config = ?, mode = ?, comment = ?
|
||||||
WHERE id = ?",
|
WHERE id = ?',
|
||||||
"iiiissi",
|
'isisisssi',
|
||||||
[
|
[$connectionTypeId, $portAType, $portAId, $portBType, $portBId, $vlanConfig, $mode, $comment, $id]
|
||||||
$data['connection_type_id'],
|
|
||||||
$data['port_a_id'],
|
|
||||||
$data['port_b_id'],
|
|
||||||
$data['vlan'],
|
|
||||||
$data['mode'],
|
|
||||||
$data['comment'],
|
|
||||||
$data['id']
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
echo json_encode([
|
if ($rows === false) {
|
||||||
'status' => 'updated',
|
jsonError('Update fehlgeschlagen', 500);
|
||||||
'rows' => $rows
|
}
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
// INSERT
|
|
||||||
$id = $sql->set(
|
|
||||||
"INSERT INTO connections
|
|
||||||
(connection_type_id, port_a_id, port_b_id, vlan, mode, comment)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?)",
|
|
||||||
"iiiiss",
|
|
||||||
[
|
|
||||||
$data['connection_type_id'],
|
|
||||||
$data['port_a_id'],
|
|
||||||
$data['port_b_id'],
|
|
||||||
$data['vlan'],
|
|
||||||
$data['mode'],
|
|
||||||
$data['comment']
|
|
||||||
],
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
echo json_encode([
|
echo json_encode(['status' => 'updated', 'rows' => $rows]);
|
||||||
'status' => 'created',
|
|
||||||
'id' => $id
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Löscht eine Verbindung
|
|
||||||
*/
|
|
||||||
function deleteConnection($sql)
|
|
||||||
{
|
|
||||||
$id = $_GET['id'] ?? null;
|
|
||||||
|
|
||||||
if (!$id) {
|
|
||||||
http_response_code(400);
|
|
||||||
echo json_encode(['error' => 'ID fehlt']);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Prüfen, ob Verbindung existiert
|
$id = $sql->set(
|
||||||
|
'INSERT INTO connections (connection_type_id, port_a_type, port_a_id, port_b_type, port_b_id, vlan_config, mode, comment)
|
||||||
$rows = $sql->set(
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
|
||||||
"DELETE FROM connections WHERE id = ?",
|
'isisisss',
|
||||||
"i",
|
[$connectionTypeId, $portAType, $portAId, $portBType, $portBId, $vlanConfig, $mode, $comment],
|
||||||
[$id]
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
echo json_encode([
|
if ($id === false) {
|
||||||
'status' => 'deleted',
|
jsonError('Insert fehlgeschlagen', 500);
|
||||||
'rows' => $rows
|
}
|
||||||
]);
|
|
||||||
|
echo json_encode(['status' => 'created', 'id' => $id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteConnection($sql): void
|
||||||
|
{
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST' && $_SERVER['REQUEST_METHOD'] !== 'DELETE') {
|
||||||
|
jsonError('Methode nicht erlaubt', 405);
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
|
||||||
|
if ($id <= 0) {
|
||||||
|
jsonError('ID fehlt', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$existing = $sql->single('SELECT id FROM connections WHERE id = ?', 'i', [$id]);
|
||||||
|
if (!$existing) {
|
||||||
|
jsonError('Verbindung existiert nicht', 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$rows = $sql->set('DELETE FROM connections WHERE id = ?', 'i', [$id]);
|
||||||
|
if ($rows === false) {
|
||||||
|
jsonError('Loeschen fehlgeschlagen', 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['status' => 'deleted', 'rows' => $rows]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,175 +1,199 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* app/api/device_type_ports.php
|
* app/api/device_type_ports.php
|
||||||
*
|
|
||||||
* API für Ports von Gerätetypen
|
|
||||||
* - Laden der Port-Definitionen (SVG-Port-Editor)
|
|
||||||
* - Speichern (Position, Typ, Name)
|
|
||||||
* - Löschen einzelner Ports
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
require_once __DIR__ . '/../bootstrap.php';
|
require_once __DIR__ . '/../bootstrap.php';
|
||||||
|
requireAuth();
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
// TODO: Single-User-Auth prüfen
|
|
||||||
// if (!$_SESSION['user']) { http_response_code(403); exit; }
|
|
||||||
|
|
||||||
$action = $_GET['action'] ?? 'load';
|
$action = $_GET['action'] ?? 'load';
|
||||||
|
|
||||||
/* =========================
|
|
||||||
* Router
|
|
||||||
* ========================= */
|
|
||||||
|
|
||||||
switch ($action) {
|
switch ($action) {
|
||||||
|
|
||||||
case 'load':
|
case 'load':
|
||||||
loadPorts($sql);
|
loadPorts($sql);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'save':
|
case 'save':
|
||||||
savePorts($sql);
|
savePorts($sql);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'delete':
|
case 'delete':
|
||||||
deletePort($sql);
|
deletePort($sql);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
http_response_code(400);
|
jsonError('Unbekannte Aktion', 400);
|
||||||
echo json_encode(['error' => 'Unbekannte Aktion']);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* =========================
|
function jsonError(string $message, int $status = 400): void
|
||||||
* Aktionen
|
|
||||||
* ========================= */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lädt alle Ports eines Gerätetyps
|
|
||||||
*/
|
|
||||||
function loadPorts($sql)
|
|
||||||
{
|
{
|
||||||
$deviceTypeId = $_GET['device_type_id'] ?? null;
|
http_response_code($status);
|
||||||
|
echo json_encode(['error' => $message]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
if (!$deviceTypeId) {
|
function loadPorts($sql): void
|
||||||
http_response_code(400);
|
{
|
||||||
echo json_encode(['error' => 'device_type_id fehlt']);
|
$deviceTypeId = isset($_GET['device_type_id']) ? (int)$_GET['device_type_id'] : 0;
|
||||||
return;
|
if ($deviceTypeId <= 0) {
|
||||||
|
jsonError('device_type_id fehlt', 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
$ports = $sql->get(
|
$ports = $sql->get(
|
||||||
"SELECT
|
'SELECT id, name, port_type_id, x, y, metadata
|
||||||
id,
|
|
||||||
name,
|
|
||||||
port_type_id,
|
|
||||||
pos_x,
|
|
||||||
pos_y,
|
|
||||||
comment
|
|
||||||
FROM device_type_ports
|
FROM device_type_ports
|
||||||
WHERE device_type_id = ?
|
WHERE device_type_id = ?
|
||||||
ORDER BY id ASC",
|
ORDER BY id ASC',
|
||||||
"i",
|
'i',
|
||||||
[$deviceTypeId]
|
[$deviceTypeId]
|
||||||
);
|
);
|
||||||
|
|
||||||
echo json_encode($ports);
|
echo json_encode($ports ?: []);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function validatePortTypeId($sql, $portTypeId): ?int
|
||||||
* Speichert alle Ports eines Gerätetyps
|
|
||||||
* (Bulk-Save aus dem SVG-Editor)
|
|
||||||
*/
|
|
||||||
function savePorts($sql)
|
|
||||||
{
|
{
|
||||||
$data = json_decode(file_get_contents('php://input'), true);
|
if ($portTypeId === null || $portTypeId === '' || (int)$portTypeId <= 0) {
|
||||||
|
return null;
|
||||||
if (!$data || empty($data['device_type_id']) || !is_array($data['ports'])) {
|
|
||||||
http_response_code(400);
|
|
||||||
echo json_encode(['error' => 'Ungültige Daten']);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$deviceTypeId = $data['device_type_id'];
|
$value = (int)$portTypeId;
|
||||||
$ports = $data['ports'];
|
$exists = $sql->single('SELECT id FROM port_types WHERE id = ?', 'i', [$value]);
|
||||||
|
if (!$exists) {
|
||||||
|
jsonError('port_type_id ist ungueltig', 400);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Transaktion starten (falls SQL-Klasse das unterstützt)
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($ports as $port) {
|
function savePorts($sql): void
|
||||||
|
{
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
jsonError('Methode nicht erlaubt', 405);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Validierung:
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
// - name nicht leer
|
if (!is_array($data) || empty($data['device_type_id']) || !isset($data['ports']) || !is_array($data['ports'])) {
|
||||||
// - pos_x / pos_y numerisch
|
jsonError('Ungueltige Daten', 400);
|
||||||
// - port_type_id erlaubt
|
}
|
||||||
|
|
||||||
if (!empty($port['id']) && !str_starts_with($port['id'], 'tmp_')) {
|
$deviceTypeId = (int)$data['device_type_id'];
|
||||||
|
if ($deviceTypeId <= 0) {
|
||||||
|
jsonError('device_type_id ist ungueltig', 400);
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------- UPDATE ---------- */
|
$deviceType = $sql->single('SELECT id FROM device_types WHERE id = ?', 'i', [$deviceTypeId]);
|
||||||
$sql->set(
|
if (!$deviceType) {
|
||||||
"UPDATE device_type_ports
|
jsonError('Geraetetyp existiert nicht', 404);
|
||||||
SET name = ?, port_type_id = ?, pos_x = ?, pos_y = ?, comment = ?
|
}
|
||||||
WHERE id = ? AND device_type_id = ?",
|
|
||||||
"siddsii",
|
$sql->set('START TRANSACTION');
|
||||||
[
|
|
||||||
$port['name'],
|
foreach ($data['ports'] as $index => $port) {
|
||||||
$port['port_type_id'],
|
if (!is_array($port)) {
|
||||||
$port['x'],
|
$sql->set('ROLLBACK');
|
||||||
$port['y'],
|
jsonError('Port-Eintrag an Position ' . $index . ' ist ungueltig', 400);
|
||||||
$port['comment'],
|
}
|
||||||
$port['id'],
|
|
||||||
$deviceTypeId
|
$name = trim((string)($port['name'] ?? ''));
|
||||||
]
|
if ($name === '') {
|
||||||
|
$sql->set('ROLLBACK');
|
||||||
|
jsonError('Port-Name darf nicht leer sein', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$x = $port['x'] ?? null;
|
||||||
|
$y = $port['y'] ?? null;
|
||||||
|
if (!is_numeric($x) || !is_numeric($y)) {
|
||||||
|
$sql->set('ROLLBACK');
|
||||||
|
jsonError('x und y muessen numerisch sein', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$x = (int)round((float)$x);
|
||||||
|
$y = (int)round((float)$y);
|
||||||
|
$portTypeId = validatePortTypeId($sql, $port['port_type_id'] ?? null);
|
||||||
|
|
||||||
|
$metadataRaw = $port['metadata'] ?? null;
|
||||||
|
$metadata = null;
|
||||||
|
if (is_array($metadataRaw)) {
|
||||||
|
$metadata = json_encode($metadataRaw);
|
||||||
|
} elseif (is_string($metadataRaw) && $metadataRaw !== '') {
|
||||||
|
json_decode($metadataRaw, true);
|
||||||
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
$sql->set('ROLLBACK');
|
||||||
|
jsonError('metadata ist kein gueltiges JSON', 400);
|
||||||
|
}
|
||||||
|
$metadata = $metadataRaw;
|
||||||
|
}
|
||||||
|
|
||||||
|
$isExisting = !empty($port['id']) && !str_starts_with((string)$port['id'], 'tmp_');
|
||||||
|
if ($isExisting) {
|
||||||
|
$portId = (int)$port['id'];
|
||||||
|
$ok = $sql->set(
|
||||||
|
'UPDATE device_type_ports
|
||||||
|
SET name = ?, port_type_id = ?, x = ?, y = ?, metadata = ?
|
||||||
|
WHERE id = ? AND device_type_id = ?',
|
||||||
|
'siiisii',
|
||||||
|
[$name, $portTypeId, $x, $y, $metadata, $portId, $deviceTypeId]
|
||||||
);
|
);
|
||||||
|
|
||||||
} else {
|
if ($ok === false) {
|
||||||
|
$sql->set('ROLLBACK');
|
||||||
|
jsonError('Update fehlgeschlagen', 500);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------- INSERT ---------- */
|
$ok = $sql->set(
|
||||||
$sql->set(
|
'INSERT INTO device_type_ports (device_type_id, name, port_type_id, x, y, metadata)
|
||||||
"INSERT INTO device_type_ports
|
VALUES (?, ?, ?, ?, ?, ?)',
|
||||||
(device_type_id, name, port_type_id, pos_x, pos_y, comment)
|
'isiiis',
|
||||||
VALUES (?, ?, ?, ?, ?, ?)",
|
[$deviceTypeId, $name, $portTypeId, $x, $y, $metadata],
|
||||||
"isidds",
|
true
|
||||||
[
|
);
|
||||||
$deviceTypeId,
|
|
||||||
$port['name'],
|
if ($ok === false) {
|
||||||
$port['port_type_id'],
|
$sql->set('ROLLBACK');
|
||||||
$port['x'],
|
jsonError('Insert fehlgeschlagen', 500);
|
||||||
$port['y'],
|
|
||||||
$port['comment']
|
|
||||||
],
|
|
||||||
true
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
echo json_encode([
|
$sql->set('COMMIT');
|
||||||
'status' => 'ok'
|
|
||||||
]);
|
echo json_encode(['status' => 'ok']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function deletePort($sql): void
|
||||||
* Löscht einen einzelnen Port
|
|
||||||
*/
|
|
||||||
function deletePort($sql)
|
|
||||||
{
|
{
|
||||||
$id = $_GET['id'] ?? null;
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST' && $_SERVER['REQUEST_METHOD'] !== 'DELETE') {
|
||||||
|
jsonError('Methode nicht erlaubt', 405);
|
||||||
if (!$id) {
|
|
||||||
http_response_code(400);
|
|
||||||
echo json_encode(['error' => 'ID fehlt']);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Prüfen, ob Port existiert und nicht verwendet wird
|
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
|
||||||
|
if ($id <= 0) {
|
||||||
|
jsonError('ID fehlt', 400);
|
||||||
|
}
|
||||||
|
|
||||||
$rows = $sql->set(
|
$port = $sql->single('SELECT id, device_type_id, name FROM device_type_ports WHERE id = ?', 'i', [$id]);
|
||||||
"DELETE FROM device_type_ports WHERE id = ?",
|
if (!$port) {
|
||||||
"i",
|
jsonError('Port existiert nicht', 404);
|
||||||
[$id]
|
}
|
||||||
|
|
||||||
|
$usage = $sql->single(
|
||||||
|
'SELECT COUNT(*) AS cnt
|
||||||
|
FROM devices d
|
||||||
|
JOIN device_ports dp ON dp.device_id = d.id
|
||||||
|
WHERE d.device_type_id = ? AND dp.name = ?',
|
||||||
|
'is',
|
||||||
|
[(int)$port['device_type_id'], (string)$port['name']]
|
||||||
);
|
);
|
||||||
|
|
||||||
echo json_encode([
|
if (!empty($usage) && (int)$usage['cnt'] > 0) {
|
||||||
'status' => 'deleted',
|
jsonError('Port wird bereits von realen Geraeten genutzt und kann nicht geloescht werden', 409);
|
||||||
'rows' => $rows
|
}
|
||||||
]);
|
|
||||||
|
$rows = $sql->set('DELETE FROM device_type_ports WHERE id = ?', 'i', [$id]);
|
||||||
|
if ($rows === false) {
|
||||||
|
jsonError('Loeschen fehlgeschlagen', 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['status' => 'deleted', 'rows' => $rows]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,133 +1,89 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* app/api/upload.php
|
* app/api/upload.php
|
||||||
*
|
|
||||||
* Zentrale Upload-API
|
|
||||||
* - Gerätetyp-Bilder (SVG / JPG / PNG)
|
|
||||||
* - Floorpläne (SVG)
|
|
||||||
* - Rack-Ansichten
|
|
||||||
*
|
|
||||||
* KEINE Logik für automatische Zuordnung
|
|
||||||
* -> Upload + Rückgabe von Pfad / Metadaten
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
require_once __DIR__ . '/../bootstrap.php';
|
require_once __DIR__ . '/../bootstrap.php';
|
||||||
|
requireAuth();
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
// TODO: Single-User-Auth prüfen
|
$baseUploadDir = defined('UPLOAD_BASE_DIR') ? UPLOAD_BASE_DIR : (__DIR__ . '/../uploads');
|
||||||
// if (!$_SESSION['user']) { http_response_code(403); exit; }
|
$maxFileSize = defined('UPLOAD_MAX_FILE_SIZE') ? (int)UPLOAD_MAX_FILE_SIZE : (5 * 1024 * 1024);
|
||||||
|
$allowedCategories = defined('UPLOAD_ALLOWED_CATEGORIES') ? UPLOAD_ALLOWED_CATEGORIES : ['misc'];
|
||||||
/* =========================
|
|
||||||
* Konfiguration
|
|
||||||
* ========================= */
|
|
||||||
|
|
||||||
// TODO: Upload-Basisverzeichnis aus config.php
|
|
||||||
$baseUploadDir = __DIR__ . '/../uploads';
|
|
||||||
|
|
||||||
// Erlaubte Typen
|
|
||||||
$allowedMimeTypes = [
|
$allowedMimeTypes = [
|
||||||
'image/svg+xml',
|
'image/svg+xml' => 'svg',
|
||||||
'image/png',
|
'image/png' => 'png',
|
||||||
'image/jpeg'
|
'image/jpeg' => 'jpg',
|
||||||
];
|
];
|
||||||
|
|
||||||
// TODO: Max. Dateigröße festlegen (z.B. 5MB)
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
$maxFileSize = 5 * 1024 * 1024;
|
jsonError('Methode nicht erlaubt', 405);
|
||||||
|
}
|
||||||
/* =========================
|
|
||||||
* Validierung
|
|
||||||
* ========================= */
|
|
||||||
|
|
||||||
if (empty($_FILES['file'])) {
|
if (empty($_FILES['file'])) {
|
||||||
http_response_code(400);
|
jsonError('Keine Datei hochgeladen', 400);
|
||||||
echo json_encode(['error' => 'Keine Datei hochgeladen']);
|
|
||||||
exit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$file = $_FILES['file'];
|
$file = $_FILES['file'];
|
||||||
|
if (!is_array($file) || $file['error'] !== UPLOAD_ERR_OK) {
|
||||||
if ($file['error'] !== UPLOAD_ERR_OK) {
|
jsonError('Upload-Fehler', 400);
|
||||||
http_response_code(400);
|
|
||||||
echo json_encode(['error' => 'Upload-Fehler']);
|
|
||||||
exit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($file['size'] > $maxFileSize) {
|
if ((int)$file['size'] > $maxFileSize) {
|
||||||
http_response_code(400);
|
jsonError('Datei zu gross', 400);
|
||||||
echo json_encode(['error' => 'Datei zu groß']);
|
|
||||||
exit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MIME-Type prüfen
|
|
||||||
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
||||||
$mimeType = finfo_file($finfo, $file['tmp_name']);
|
$mimeType = finfo_file($finfo, $file['tmp_name']);
|
||||||
finfo_close($finfo);
|
finfo_close($finfo);
|
||||||
|
|
||||||
if (!in_array($mimeType, $allowedMimeTypes)) {
|
if (!isset($allowedMimeTypes[$mimeType])) {
|
||||||
http_response_code(400);
|
jsonError('Dateityp nicht erlaubt', 400);
|
||||||
echo json_encode(['error' => 'Dateityp nicht erlaubt']);
|
|
||||||
exit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* =========================
|
$category = strtolower(trim((string)($_POST['category'] ?? 'misc')));
|
||||||
* Zielverzeichnis
|
if ($category === '' || !in_array($category, $allowedCategories, true)) {
|
||||||
* ========================= */
|
jsonError('Ungueltige Kategorie', 400);
|
||||||
|
|
||||||
// TODO: Kategorie definieren (device_types, floors, racks, etc.)
|
|
||||||
$category = $_POST['category'] ?? 'misc';
|
|
||||||
|
|
||||||
// Zielpfad
|
|
||||||
$targetDir = $baseUploadDir . '/' . preg_replace('/[^a-z0-9_-]/i', '', $category);
|
|
||||||
|
|
||||||
// Verzeichnis anlegen falls nötig
|
|
||||||
if (!is_dir($targetDir)) {
|
|
||||||
mkdir($targetDir, 0755, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* =========================
|
$targetDir = rtrim($baseUploadDir, '/\\') . DIRECTORY_SEPARATOR . $category;
|
||||||
* Dateiname
|
if (!is_dir($targetDir) && !mkdir($targetDir, 0755, true) && !is_dir($targetDir)) {
|
||||||
* ========================= */
|
jsonError('Upload-Verzeichnis konnte nicht erstellt werden', 500);
|
||||||
|
}
|
||||||
|
|
||||||
// Originalname bereinigen
|
$extension = $allowedMimeTypes[$mimeType];
|
||||||
$extension = pathinfo($file['name'], PATHINFO_EXTENSION);
|
$filename = sprintf('%s_%s.%s', $category, bin2hex(random_bytes(16)), $extension);
|
||||||
|
$targetPath = $targetDir . DIRECTORY_SEPARATOR . $filename;
|
||||||
// TODO: Eindeutigen Namen besser definieren (UUID?)
|
|
||||||
$filename = uniqid('upload_', true) . '.' . strtolower($extension);
|
|
||||||
|
|
||||||
$targetPath = $targetDir . '/' . $filename;
|
|
||||||
|
|
||||||
/* =========================
|
|
||||||
* Datei speichern
|
|
||||||
* ========================= */
|
|
||||||
|
|
||||||
if (!move_uploaded_file($file['tmp_name'], $targetPath)) {
|
if (!move_uploaded_file($file['tmp_name'], $targetPath)) {
|
||||||
http_response_code(500);
|
jsonError('Datei konnte nicht gespeichert werden', 500);
|
||||||
echo json_encode(['error' => 'Datei konnte nicht gespeichert werden']);
|
|
||||||
exit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* =========================
|
$publicPath = '/uploads/' . $category . '/' . $filename;
|
||||||
* Optional: DB-Eintrag
|
$uploadId = null;
|
||||||
* ========================= */
|
|
||||||
|
|
||||||
// TODO: Optional in Tabelle `uploads` speichern
|
$uploadTableExists = $sql->single("SHOW TABLES LIKE 'uploads'");
|
||||||
// $uploadId = $sql->set(
|
if (!empty($uploadTableExists)) {
|
||||||
// "INSERT INTO uploads (filename, path, mime_type, category)
|
$uploadId = $sql->set(
|
||||||
// VALUES (?, ?, ?, ?)",
|
'INSERT INTO uploads (filename, path, mime_type, category) VALUES (?, ?, ?, ?)',
|
||||||
// "ssss",
|
'ssss',
|
||||||
// [$filename, $targetPath, $mimeType, $category],
|
[$filename, $publicPath, $mimeType, $category],
|
||||||
// true
|
true
|
||||||
// );
|
);
|
||||||
|
}
|
||||||
/* =========================
|
|
||||||
* Antwort
|
|
||||||
* ========================= */
|
|
||||||
|
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
'status' => 'ok',
|
'status' => 'ok',
|
||||||
'filename' => $filename,
|
'filename' => $filename,
|
||||||
'path' => str_replace(__DIR__ . '/..', '', $targetPath),
|
'path' => $publicPath,
|
||||||
'mime_type' => $mimeType
|
'mime_type' => $mimeType,
|
||||||
// 'id' => $uploadId ?? null
|
'id' => $uploadId,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
function jsonError(string $message, int $status = 400): void
|
||||||
|
{
|
||||||
|
http_response_code($status);
|
||||||
|
echo json_encode(['error' => $message]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|||||||
4
app/assets/icons/favicon.svg
Normal file
4
app/assets/icons/favicon.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||||
|
<rect width="64" height="64" rx="12" fill="#0c4da2"/>
|
||||||
|
<path d="M14 22h36v6H14zm0 14h36v6H14z" fill="#ffffff"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 183 B |
@@ -1,135 +1,110 @@
|
|||||||
/**
|
/**
|
||||||
* app/assets/js/app.js
|
* app/assets/js/app.js
|
||||||
*
|
|
||||||
* Zentrale JS-Datei für die Webanwendung
|
|
||||||
* - Initialisiert alle Module
|
|
||||||
* - SVG-Editor, Netzwerk-Ansicht, Drag & Drop, Floorplan
|
|
||||||
* - Event-Handler, globale Variablen
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// =========================
|
|
||||||
// Global Variables / Config
|
|
||||||
// =========================
|
|
||||||
|
|
||||||
window.APP = {
|
window.APP = {
|
||||||
deviceTypes: [], // TODO: alle Gerätetypen laden
|
state: {
|
||||||
devices: [], // TODO: alle Geräte laden
|
deviceTypes: [],
|
||||||
racks: [], // TODO: alle Racks laden
|
devices: [],
|
||||||
floors: [], // TODO: alle Floors laden
|
racks: [],
|
||||||
connections: [], // TODO: alle Verbindungen laden
|
floors: [],
|
||||||
|
connections: [],
|
||||||
|
},
|
||||||
|
capabilities: {
|
||||||
|
hasGlobalDataApi: false,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// =========================
|
|
||||||
// Init Functions
|
|
||||||
// =========================
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
initViewModules();
|
||||||
console.log('App initialized');
|
|
||||||
|
|
||||||
// =========================
|
|
||||||
// SVG-Port-Editor initialisieren
|
|
||||||
// =========================
|
|
||||||
// TODO: import / init svg-editor.js
|
|
||||||
// if (window.SVGEditor) window.SVGEditor.init();
|
|
||||||
|
|
||||||
// =========================
|
|
||||||
// Netzwerk-Ansicht initialisieren
|
|
||||||
// =========================
|
|
||||||
// TODO: import / init network-view.js
|
|
||||||
// if (window.NetworkView) window.NetworkView.init();
|
|
||||||
|
|
||||||
// =========================
|
|
||||||
// Drag & Drop für Floors / Racks / Devices
|
|
||||||
// =========================
|
|
||||||
// TODO: init drag & drop logic
|
|
||||||
|
|
||||||
// =========================
|
|
||||||
// Event-Handler für Buttons / Forms
|
|
||||||
// =========================
|
|
||||||
initEventHandlers();
|
initEventHandlers();
|
||||||
});
|
});
|
||||||
|
|
||||||
// =========================
|
function initViewModules() {
|
||||||
// Event Handler Setup
|
if (typeof window.Dashboard?.init === 'function') {
|
||||||
// =========================
|
window.Dashboard.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Both modules are loaded via script tags in header.php.
|
||||||
|
// They are self-initializing and only run when expected DOM nodes exist.
|
||||||
|
window.dispatchEvent(new CustomEvent('app:modules-initialized'));
|
||||||
|
}
|
||||||
|
|
||||||
function initEventHandlers() {
|
function initEventHandlers() {
|
||||||
|
bindFormSubmitButton('#save-device-type', 'form[action*="module=device_types"][action*="save"]');
|
||||||
// TODO: Save-Button Device-Type
|
bindFormSubmitButton('#save-device', 'form[action*="module=devices"][action*="save"]');
|
||||||
const saveDeviceTypeBtn = document.querySelector('#save-device-type');
|
bindFormSubmitButton('#save-floor', 'form[action*="module=floors"][action*="save"]');
|
||||||
if (saveDeviceTypeBtn) {
|
bindFormSubmitButton('#save-rack', 'form[action*="module=racks"][action*="save"]');
|
||||||
saveDeviceTypeBtn.addEventListener('click', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
// TODO: Save Device-Type via AJAX
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Save-Button Device
|
|
||||||
const saveDeviceBtn = document.querySelector('#save-device');
|
|
||||||
if (saveDeviceBtn) {
|
|
||||||
saveDeviceBtn.addEventListener('click', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
// TODO: Save Device via AJAX
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Save-Button Floor
|
|
||||||
const saveFloorBtn = document.querySelector('#save-floor');
|
|
||||||
if (saveFloorBtn) {
|
|
||||||
saveFloorBtn.addEventListener('click', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
// TODO: Save Floor via AJAX
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Save-Button Rack
|
|
||||||
const saveRackBtn = document.querySelector('#save-rack');
|
|
||||||
if (saveRackBtn) {
|
|
||||||
saveRackBtn.addEventListener('click', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
// TODO: Save Rack via AJAX
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Weitere Event-Handler (Import, Export, Filter)
|
|
||||||
|
|
||||||
document.querySelectorAll('[data-confirm-delete]').forEach((btn) => {
|
document.querySelectorAll('[data-confirm-delete]').forEach((btn) => {
|
||||||
btn.addEventListener('click', (event) => {
|
btn.addEventListener('click', (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const message = btn.getAttribute('data-confirm-message') || 'Aktion ausführen?';
|
const message = btn.getAttribute('data-confirm-message') || 'Aktion ausfuehren?';
|
||||||
if (confirm(message)) {
|
if (confirm(message)) {
|
||||||
alert(btn.getAttribute('data-confirm-feedback') || 'Diese Funktion ist noch nicht verfügbar.');
|
const href = btn.getAttribute('href') || btn.dataset.href;
|
||||||
|
if (href) {
|
||||||
|
window.location.href = href;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('[data-filter-submit]').forEach((el) => {
|
||||||
|
el.addEventListener('change', () => {
|
||||||
|
const form = el.closest('form');
|
||||||
|
if (form) {
|
||||||
|
form.requestSubmit();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================
|
function bindFormSubmitButton(buttonSelector, formSelector) {
|
||||||
// Utility Functions
|
const button = document.querySelector(buttonSelector);
|
||||||
// =========================
|
if (!button) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
button.addEventListener('click', (event) => {
|
||||||
* AJAX Request Helper
|
event.preventDefault();
|
||||||
* @param {string} url
|
const form = button.closest('form') || document.querySelector(formSelector);
|
||||||
* @param {object} data
|
if (form) {
|
||||||
* @param {function} callback
|
form.requestSubmit();
|
||||||
*/
|
}
|
||||||
function ajaxPost(url, data, callback) {
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function ajaxPost(url, data, callback, onError) {
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
xhr.open('POST', url, true);
|
xhr.open('POST', url, true);
|
||||||
xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
|
xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
|
||||||
xhr.onload = function() {
|
xhr.onload = function onLoad() {
|
||||||
if (xhr.status >= 200 && xhr.status < 300) {
|
if (xhr.status >= 200 && xhr.status < 300) {
|
||||||
callback(JSON.parse(xhr.responseText));
|
let parsed = null;
|
||||||
} else {
|
try {
|
||||||
console.error('AJAX Error:', xhr.statusText);
|
parsed = JSON.parse(xhr.responseText);
|
||||||
|
} catch (error) {
|
||||||
|
if (typeof onError === 'function') {
|
||||||
|
onError(error);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback(parsed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof onError === 'function') {
|
||||||
|
onError(new Error('AJAX error: ' + xhr.status));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
xhr.onerror = function onXhrError() {
|
||||||
|
if (typeof onError === 'function') {
|
||||||
|
onError(new Error('Netzwerkfehler'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
xhr.send(JSON.stringify(data));
|
xhr.send(JSON.stringify(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: weitere Utility-Funktionen (DOM-Helper, SVG-Helper, etc.)
|
window.APP.ajaxPost = ajaxPost;
|
||||||
|
|
||||||
// Dashboard initialisieren
|
|
||||||
if (window.Dashboard) window.Dashboard.init();
|
|
||||||
|
|||||||
@@ -1,99 +1,35 @@
|
|||||||
/**
|
/**
|
||||||
* app/assets/js/dashboard.js
|
* app/assets/js/dashboard.js
|
||||||
*
|
|
||||||
* Dashboard-Modul
|
|
||||||
* - Zentrale Übersicht aller Grundfunktionen
|
|
||||||
* - Einstiegspunkt für das Tool
|
|
||||||
* - Kann später Status, Warnungen, Statistiken anzeigen
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
window.Dashboard = (function () {
|
window.Dashboard = (function () {
|
||||||
|
|
||||||
// =========================
|
|
||||||
// Interne Daten
|
|
||||||
// =========================
|
|
||||||
|
|
||||||
const modules = [
|
const modules = [
|
||||||
{
|
{ id: 'device_types', label: 'Geraetetypen', description: 'Geraetetypen und Port-Definitionen', url: '?module=device_types&action=list', icon: 'DT' },
|
||||||
id: 'device_types',
|
{ id: 'devices', label: 'Geraete', description: 'Physische Geraete in Racks und Raeumen', url: '?module=devices&action=list', icon: 'DV' },
|
||||||
label: 'Gerätetypen',
|
{ id: 'connections', label: 'Verbindungen', description: 'Kabel, Ports und VLANs', url: '?module=connections&action=list', icon: 'CN' },
|
||||||
description: 'Gerätetypen, Port-Definitionen, Module',
|
{ id: 'floors', label: 'Stockwerke', description: 'Standorte, Gebaeude und Etagen', url: '?module=floors&action=list', icon: 'FL' },
|
||||||
url: '/app/device_types/list.php',
|
{ id: 'racks', label: 'Racks', description: 'Racks und Positionierung', url: '?module=racks&action=list', icon: 'RK' },
|
||||||
icon: '🔌'
|
{ id: 'infra', label: 'Infrastruktur', description: 'Patchpanels und Wandbuchsen', url: '?module=floor_infrastructure&action=list', icon: 'IF' }
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'devices',
|
|
||||||
label: 'Geräte',
|
|
||||||
description: 'Physische Geräte in Racks und Räumen',
|
|
||||||
url: '/app/devices/list.php',
|
|
||||||
icon: '🖥️'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'connections',
|
|
||||||
label: 'Verbindungen',
|
|
||||||
description: 'Kabel, Ports, VLANs, Protokolle',
|
|
||||||
url: '/app/connections/list.php',
|
|
||||||
icon: '🧵'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'floors',
|
|
||||||
label: 'Standorte & Stockwerke',
|
|
||||||
description: 'Gebäude, Etagen, Räume, Dosen',
|
|
||||||
url: '/app/floors/list.php',
|
|
||||||
icon: '🏢'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'racks',
|
|
||||||
label: 'Serverschränke',
|
|
||||||
description: 'Racks, Positionierung, Höheneinheiten',
|
|
||||||
url: '/app/racks/list.php',
|
|
||||||
icon: '🗄️'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'network_view',
|
|
||||||
label: 'Netzwerk-Ansicht',
|
|
||||||
description: 'Grafische Netzwerkdarstellung',
|
|
||||||
url: '/network.php',
|
|
||||||
icon: '🌐'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'svg_editor',
|
|
||||||
label: 'SVG-Port-Editor',
|
|
||||||
description: 'Ports auf Gerätetypen definieren',
|
|
||||||
url: '/svg-editor.php',
|
|
||||||
icon: '✏️'
|
|
||||||
}
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// =========================
|
|
||||||
// Public API
|
|
||||||
// =========================
|
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
console.log('Dashboard initialized');
|
const container = document.querySelector('#dashboard-modules');
|
||||||
|
if (container) {
|
||||||
|
renderModules(container);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Dashboard-Container ermitteln
|
loadStats();
|
||||||
// const container = document.querySelector('#dashboard');
|
showWarnings();
|
||||||
|
renderRecentChanges();
|
||||||
// TODO: Module rendern
|
|
||||||
// renderModules(container);
|
|
||||||
|
|
||||||
// TODO: Optional: Status-Daten laden (Counts, Warnings)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================
|
|
||||||
// Render Functions
|
|
||||||
// =========================
|
|
||||||
|
|
||||||
function renderModules(container) {
|
function renderModules(container) {
|
||||||
if (!container) return;
|
|
||||||
|
|
||||||
container.innerHTML = '';
|
container.innerHTML = '';
|
||||||
|
|
||||||
modules.forEach(module => {
|
modules.forEach((module) => {
|
||||||
const el = document.createElement('div');
|
const el = document.createElement('a');
|
||||||
el.className = 'dashboard-tile';
|
el.className = 'dashboard-tile';
|
||||||
|
el.href = module.url;
|
||||||
el.innerHTML = `
|
el.innerHTML = `
|
||||||
<div class="dashboard-icon">${module.icon}</div>
|
<div class="dashboard-icon">${module.icon}</div>
|
||||||
<div class="dashboard-content">
|
<div class="dashboard-content">
|
||||||
@@ -101,30 +37,54 @@ window.Dashboard = (function () {
|
|||||||
<p>${module.description}</p>
|
<p>${module.description}</p>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
el.addEventListener('click', () => {
|
|
||||||
window.location.href = module.url;
|
|
||||||
});
|
|
||||||
|
|
||||||
container.appendChild(el);
|
container.appendChild(el);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================
|
function loadStats() {
|
||||||
// Optional Erweiterungen
|
const stats = {
|
||||||
// =========================
|
devices: countRows('.device-list tbody tr'),
|
||||||
|
connections: countRows('.connection-list tbody tr'),
|
||||||
|
outlets: countRows('.infra-table tbody tr')
|
||||||
|
};
|
||||||
|
|
||||||
// TODO: loadStats() → Anzahl Geräte, offene Ports, unverbundene Dosen
|
const target = document.querySelector('[data-dashboard-stats]');
|
||||||
// TODO: showWarnings() → unverbundene Ports, VLAN-Konflikte
|
if (!target) {
|
||||||
// TODO: RecentChanges() → letzte Änderungen
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// =========================
|
target.textContent = `Geraete: ${stats.devices} | Verbindungen: ${stats.connections} | Infrastruktur-Eintraege: ${stats.outlets}`;
|
||||||
// Expose Public Methods
|
}
|
||||||
// =========================
|
|
||||||
|
|
||||||
return {
|
function showWarnings() {
|
||||||
init,
|
const target = document.querySelector('[data-dashboard-warnings]');
|
||||||
// renderModules // optional öffentlich machen
|
if (!target) {
|
||||||
};
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const warnings = [];
|
||||||
|
if (countRows('.device-list tbody tr') === 0) {
|
||||||
|
warnings.push('Noch keine Geraete vorhanden');
|
||||||
|
}
|
||||||
|
if (countRows('.connection-list tbody tr') === 0) {
|
||||||
|
warnings.push('Noch keine Verbindungen vorhanden');
|
||||||
|
}
|
||||||
|
|
||||||
|
target.textContent = warnings.length ? warnings.join(' | ') : 'Keine offenen Warnungen erkannt';
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderRecentChanges() {
|
||||||
|
const target = document.querySelector('[data-dashboard-recent]');
|
||||||
|
if (!target) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
target.textContent = 'Letzte Aenderungen werden serverseitig noch nicht protokolliert.';
|
||||||
|
}
|
||||||
|
|
||||||
|
function countRows(selector) {
|
||||||
|
return document.querySelectorAll(selector).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { init };
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -66,10 +66,24 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (window.confirm('Diesen Gerätetyp wirklich löschen? Alle zugeordneten Geräte werden angepasst.')) {
|
if (!window.confirm('Diesen Geraetetyp wirklich loeschen?')) {
|
||||||
// TODO: Delete-Endpoint/Flow ist noch nicht implementiert.
|
return;
|
||||||
window.alert('Löschen noch nicht implementiert');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fetch('?module=device_types&action=delete', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
|
||||||
|
body: 'id=' + encodeURIComponent(id)
|
||||||
|
})
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => {
|
||||||
|
if (data && data.success) {
|
||||||
|
window.location.href = '?module=device_types&action=list';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.alert((data && data.message) ? data.message : 'Loeschen fehlgeschlagen');
|
||||||
|
})
|
||||||
|
.catch(() => window.alert('Loeschen fehlgeschlagen'));
|
||||||
});
|
});
|
||||||
|
|
||||||
deleteButton.dataset.deleteBound = '1';
|
deleteButton.dataset.deleteBound = '1';
|
||||||
|
|||||||
@@ -13,8 +13,20 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (window.confirm('Diesen Geraetetyp wirklich loeschen?')) {
|
if (window.confirm('Diesen Geraetetyp wirklich loeschen?')) {
|
||||||
// TODO: AJAX-Delete implementieren
|
fetch('?module=device_types&action=delete', {
|
||||||
window.alert('Loeschen noch nicht implementiert');
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
|
||||||
|
body: 'id=' + encodeURIComponent(id)
|
||||||
|
})
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => {
|
||||||
|
if (data && data.success) {
|
||||||
|
window.location.reload();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.alert((data && data.message) ? data.message : 'Loeschen fehlgeschlagen');
|
||||||
|
})
|
||||||
|
.catch(() => window.alert('Loeschen fehlgeschlagen'));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -21,16 +21,34 @@
|
|||||||
.catch(() => alert('Loeschen fehlgeschlagen'));
|
.catch(() => alert('Loeschen fehlgeschlagen'));
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleBuildingDelete() {
|
function handleBuildingDelete(id) {
|
||||||
if (confirm('Dieses Gebaeude wirklich loeschen? Alle Stockwerke werden geloescht.')) {
|
if (!confirm('Dieses Gebaeude wirklich loeschen? Alle Stockwerke werden geloescht.')) {
|
||||||
alert('Loeschen noch nicht implementiert');
|
return;
|
||||||
}
|
}
|
||||||
|
postDelete('?module=buildings&action=delete&id=' + encodeURIComponent(id))
|
||||||
|
.then((data) => {
|
||||||
|
if (data && (data.success || data.status === 'ok')) {
|
||||||
|
window.location.reload();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
alert((data && (data.message || data.error)) ? (data.message || data.error) : 'Loeschen fehlgeschlagen');
|
||||||
|
})
|
||||||
|
.catch(() => alert('Loeschen fehlgeschlagen'));
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleFloorDelete() {
|
function handleFloorDelete(id) {
|
||||||
if (confirm('Dieses Stockwerk wirklich loeschen? Alle Raeume und Racks werden geloescht.')) {
|
if (!confirm('Dieses Stockwerk wirklich loeschen? Alle Raeume und Racks werden geloescht.')) {
|
||||||
alert('Loeschen noch nicht implementiert');
|
return;
|
||||||
}
|
}
|
||||||
|
postDelete('?module=floors&action=delete&id=' + encodeURIComponent(id))
|
||||||
|
.then((data) => {
|
||||||
|
if (data && data.success) {
|
||||||
|
window.location.reload();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
alert((data && data.message) ? data.message : 'Loeschen fehlgeschlagen');
|
||||||
|
})
|
||||||
|
.catch(() => alert('Loeschen fehlgeschlagen'));
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleRoomDelete(id) {
|
function handleRoomDelete(id) {
|
||||||
|
|||||||
@@ -1,61 +1,23 @@
|
|||||||
// 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
|
|
||||||
*/
|
|
||||||
|
|
||||||
(() => {
|
(() => {
|
||||||
/* =========================
|
const svgElement = document.querySelector('#network-svg');
|
||||||
* Konfiguration
|
if (!svgElement) {
|
||||||
* ========================= */
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Standort / Rack / View-Kontext vom Backend setzen
|
const CONTEXT_TYPE = svgElement.dataset.contextType || 'all';
|
||||||
const CONTEXT_ID = null;
|
const CONTEXT_ID = Number(svgElement.dataset.contextId || 0);
|
||||||
|
const API_LOAD_NETWORK = '/api/connections.php?action=load';
|
||||||
// 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';
|
const API_SAVE_POSITIONS = '/api/network_view.php?action=save_positions';
|
||||||
|
|
||||||
/* =========================
|
let devices = [];
|
||||||
* State
|
let ports = [];
|
||||||
* ========================= */
|
let connections = [];
|
||||||
|
|
||||||
let svgElement = null;
|
|
||||||
|
|
||||||
let devices = []; // Geräte inkl. Position
|
|
||||||
let connections = []; // Verbindungen zwischen Ports
|
|
||||||
|
|
||||||
let selectedDeviceId = null;
|
let selectedDeviceId = null;
|
||||||
let isDragging = false;
|
let isDragging = false;
|
||||||
let dragOffset = { x: 0, y: 0 };
|
let dragOffset = { x: 0, y: 0 };
|
||||||
|
|
||||||
/* =========================
|
bindSvgEvents();
|
||||||
* Initialisierung
|
loadNetwork();
|
||||||
* ========================= */
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
svgElement = document.querySelector('#network-svg');
|
|
||||||
|
|
||||||
if (!svgElement) {
|
|
||||||
console.warn('Network View: #network-svg nicht gefunden');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bindSvgEvents();
|
|
||||||
loadNetwork();
|
|
||||||
});
|
|
||||||
|
|
||||||
/* =========================
|
|
||||||
* Events
|
|
||||||
* ========================= */
|
|
||||||
|
|
||||||
function bindSvgEvents() {
|
function bindSvgEvents() {
|
||||||
svgElement.addEventListener('mousemove', onMouseMove);
|
svgElement.addEventListener('mousemove', onMouseMove);
|
||||||
@@ -63,37 +25,40 @@ function bindSvgEvents() {
|
|||||||
svgElement.addEventListener('click', onSvgClick);
|
svgElement.addEventListener('click', onSvgClick);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* =========================
|
function buildLoadUrl() {
|
||||||
* Laden
|
const params = new URLSearchParams();
|
||||||
* ========================= */
|
params.set('action', 'load');
|
||||||
|
params.set('context_type', CONTEXT_TYPE);
|
||||||
|
if (CONTEXT_TYPE !== 'all') {
|
||||||
|
params.set('context_id', String(CONTEXT_ID));
|
||||||
|
}
|
||||||
|
return '/api/connections.php?' + params.toString();
|
||||||
|
}
|
||||||
|
|
||||||
function loadNetwork() {
|
function loadNetwork() {
|
||||||
if (!CONTEXT_ID) {
|
fetch(buildLoadUrl())
|
||||||
console.warn('CONTEXT_ID nicht gesetzt');
|
.then((res) => res.json())
|
||||||
return;
|
.then((data) => {
|
||||||
}
|
if (!data || !Array.isArray(data.devices) || !Array.isArray(data.connections)) {
|
||||||
|
throw new Error('Antwortformat ungueltig');
|
||||||
fetch(`${API_LOAD_NETWORK}&context_id=${CONTEXT_ID}`)
|
}
|
||||||
.then(res => res.json())
|
|
||||||
.then(data => {
|
|
||||||
// TODO: Datenstruktur validieren
|
|
||||||
devices = data.devices || [];
|
|
||||||
connections = data.connections || [];
|
|
||||||
|
|
||||||
|
devices = data.devices.map((device, index) => ({
|
||||||
|
...device,
|
||||||
|
x: Number(device.pos_x ?? device.x ?? 50 + (index % 6) * 150),
|
||||||
|
y: Number(device.pos_y ?? device.y ?? 60 + Math.floor(index / 6) * 120)
|
||||||
|
}));
|
||||||
|
ports = Array.isArray(data.ports) ? data.ports : [];
|
||||||
|
connections = data.connections;
|
||||||
renderAll();
|
renderAll();
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch((err) => {
|
||||||
console.error('Fehler beim Laden der Netzwerkansicht', err);
|
console.error('Fehler beim Laden der Netzwerkansicht', err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* =========================
|
|
||||||
* Rendering
|
|
||||||
* ========================= */
|
|
||||||
|
|
||||||
function renderAll() {
|
function renderAll() {
|
||||||
clearSvg();
|
clearSvg();
|
||||||
|
|
||||||
renderConnections();
|
renderConnections();
|
||||||
renderDevices();
|
renderDevices();
|
||||||
}
|
}
|
||||||
@@ -104,34 +69,27 @@ function clearSvg() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- Geräte ---------- */
|
|
||||||
|
|
||||||
function renderDevices() {
|
function renderDevices() {
|
||||||
devices.forEach(device => renderDevice(device));
|
devices.forEach((device) => renderDevice(device));
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderDevice(device) {
|
function renderDevice(device) {
|
||||||
const group = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
const group = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
||||||
group.classList.add('device-node');
|
group.classList.add('device-node');
|
||||||
group.dataset.id = device.id;
|
group.dataset.id = device.id;
|
||||||
|
group.setAttribute('transform', `translate(${device.x || 0}, ${device.y || 0})`);
|
||||||
|
|
||||||
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');
|
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
||||||
rect.setAttribute('width', 120);
|
rect.setAttribute('width', 120);
|
||||||
rect.setAttribute('height', 60);
|
rect.setAttribute('height', 60);
|
||||||
rect.setAttribute('rx', 6);
|
rect.setAttribute('rx', 6);
|
||||||
|
rect.classList.add('device-node-rect');
|
||||||
|
|
||||||
rect.addEventListener('mousedown', (e) => {
|
rect.addEventListener('mousedown', (e) => {
|
||||||
startDrag(e, device.id);
|
startDrag(e, device.id);
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Label
|
|
||||||
const text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
const text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
||||||
text.setAttribute('x', 60);
|
text.setAttribute('x', 60);
|
||||||
text.setAttribute('y', 35);
|
text.setAttribute('y', 35);
|
||||||
@@ -141,40 +99,59 @@ function renderDevice(device) {
|
|||||||
group.appendChild(rect);
|
group.appendChild(rect);
|
||||||
group.appendChild(text);
|
group.appendChild(text);
|
||||||
|
|
||||||
// TODO: Ports als kleine Kreise anlegen (Position aus Portdefinition)
|
const devicePorts = ports.filter((port) => Number(port.device_id) === Number(device.id));
|
||||||
// TODO: Ports klickbar machen (für Verbindungs-Erstellung)
|
const spacing = 120 / (Math.max(1, devicePorts.length) + 1);
|
||||||
|
devicePorts.forEach((port, index) => {
|
||||||
|
const dot = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
||||||
|
dot.setAttribute('cx', String(Math.round((index + 1) * spacing)));
|
||||||
|
dot.setAttribute('cy', '62');
|
||||||
|
dot.setAttribute('r', '3');
|
||||||
|
dot.classList.add('device-port-dot');
|
||||||
|
dot.dataset.portId = String(port.id);
|
||||||
|
dot.dataset.deviceId = String(device.id);
|
||||||
|
dot.addEventListener('click', (event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
console.info('Port ausgewaehlt', port.id);
|
||||||
|
});
|
||||||
|
group.appendChild(dot);
|
||||||
|
});
|
||||||
|
|
||||||
svgElement.appendChild(group);
|
svgElement.appendChild(group);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- Verbindungen ---------- */
|
|
||||||
|
|
||||||
function renderConnections() {
|
function renderConnections() {
|
||||||
connections.forEach(conn => renderConnection(conn));
|
connections.forEach((connection) => renderConnection(connection));
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderConnection(connection) {
|
function renderConnection(connection) {
|
||||||
// TODO: Quell- & Ziel-Port-Koordinaten berechnen
|
const sourcePort = ports.find((port) => Number(port.id) === Number(connection.port_a_id));
|
||||||
// TODO: unterschiedliche Verbindungstypen (Farbe, Strichart, Dicke)
|
const targetPort = ports.find((port) => Number(port.id) === Number(connection.port_b_id));
|
||||||
|
if (!sourcePort || !targetPort) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceDevice = devices.find((device) => Number(device.id) === Number(sourcePort.device_id));
|
||||||
|
const targetDevice = devices.find((device) => Number(device.id) === Number(targetPort.device_id));
|
||||||
|
if (!sourceDevice || !targetDevice) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
||||||
|
line.setAttribute('x1', String(sourceDevice.x + 60));
|
||||||
|
line.setAttribute('y1', String(sourceDevice.y + 60));
|
||||||
|
line.setAttribute('x2', String(targetDevice.x + 60));
|
||||||
|
line.setAttribute('y2', String(targetDevice.y + 60));
|
||||||
|
|
||||||
line.setAttribute('x1', 0);
|
const isFiber = String(connection.mode || '').toLowerCase().includes('fiber');
|
||||||
line.setAttribute('y1', 0);
|
|
||||||
line.setAttribute('x2', 100);
|
|
||||||
line.setAttribute('y2', 100);
|
|
||||||
|
|
||||||
line.classList.add('connection-line');
|
line.classList.add('connection-line');
|
||||||
|
line.setAttribute('stroke', isFiber ? '#2f6fef' : '#1f8b4c');
|
||||||
|
line.setAttribute('stroke-width', isFiber ? '2.5' : '2');
|
||||||
|
line.setAttribute('stroke-dasharray', isFiber ? '6 4' : '');
|
||||||
|
|
||||||
svgElement.appendChild(line);
|
svgElement.appendChild(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* =========================
|
|
||||||
* Interaktion
|
|
||||||
* ========================= */
|
|
||||||
|
|
||||||
function onSvgClick(event) {
|
function onSvgClick(event) {
|
||||||
// Klick ins Leere -> Auswahl aufheben
|
|
||||||
if (event.target === svgElement) {
|
if (event.target === svgElement) {
|
||||||
selectedDeviceId = null;
|
selectedDeviceId = null;
|
||||||
updateSelection();
|
updateSelection();
|
||||||
@@ -202,7 +179,6 @@ function onMouseMove(event) {
|
|||||||
if (!device) return;
|
if (!device) return;
|
||||||
|
|
||||||
const point = getSvgCoordinates(event);
|
const point = getSvgCoordinates(event);
|
||||||
|
|
||||||
device.x = point.x + dragOffset.x;
|
device.x = point.x + dragOffset.x;
|
||||||
device.y = point.y + dragOffset.y;
|
device.y = point.y + dragOffset.y;
|
||||||
|
|
||||||
@@ -210,31 +186,28 @@ function onMouseMove(event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onMouseUp() {
|
function onMouseUp() {
|
||||||
if (!isDragging) return;
|
if (!isDragging) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
isDragging = false;
|
isDragging = false;
|
||||||
|
|
||||||
// TODO: Positionen optional automatisch speichern
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* =========================
|
|
||||||
* Auswahl
|
|
||||||
* ========================= */
|
|
||||||
|
|
||||||
function updateSelection() {
|
function updateSelection() {
|
||||||
svgElement.querySelectorAll('.device-node').forEach(el => {
|
svgElement.querySelectorAll('.device-node').forEach((el) => {
|
||||||
el.classList.toggle(
|
el.classList.toggle('selected', el.dataset.id === String(selectedDeviceId));
|
||||||
'selected',
|
|
||||||
el.dataset.id === String(selectedDeviceId)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Sidebar mit Gerätedetails füllen
|
const sidebar = document.querySelector('[data-network-selected-device]');
|
||||||
}
|
if (!sidebar) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/* =========================
|
const device = getDeviceById(selectedDeviceId);
|
||||||
* Speichern
|
sidebar.textContent = device
|
||||||
* ========================= */
|
? `${device.name} (ID ${device.id})`
|
||||||
|
: 'Kein Geraet ausgewaehlt';
|
||||||
|
}
|
||||||
|
|
||||||
function savePositions() {
|
function savePositions() {
|
||||||
fetch(API_SAVE_POSITIONS, {
|
fetch(API_SAVE_POSITIONS, {
|
||||||
@@ -242,27 +215,21 @@ function savePositions() {
|
|||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
context_id: CONTEXT_ID,
|
context_id: CONTEXT_ID,
|
||||||
devices: devices.map(d => ({
|
devices: devices.map((device) => ({ id: device.id, x: device.x, y: device.y }))
|
||||||
id: d.id,
|
|
||||||
x: d.x,
|
|
||||||
y: d.y
|
|
||||||
}))
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.then(res => res.json())
|
.then((res) => res.json())
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
// TODO: Erfolg / Fehler anzeigen
|
if (data?.error) {
|
||||||
console.log('Positionen gespeichert', data);
|
throw new Error(data.error);
|
||||||
})
|
}
|
||||||
.catch(err => {
|
alert('Positionen gespeichert');
|
||||||
console.error('Fehler beim Speichern', err);
|
})
|
||||||
});
|
.catch((err) => {
|
||||||
|
alert('Positionen konnten nicht gespeichert werden: ' + err.message);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* =========================
|
|
||||||
* Hilfsfunktionen
|
|
||||||
* ========================= */
|
|
||||||
|
|
||||||
function getSvgCoordinates(event) {
|
function getSvgCoordinates(event) {
|
||||||
const pt = svgElement.createSVGPoint();
|
const pt = svgElement.createSVGPoint();
|
||||||
pt.x = event.clientX;
|
pt.x = event.clientX;
|
||||||
@@ -273,19 +240,23 @@ function getSvgCoordinates(event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getDeviceById(id) {
|
function getDeviceById(id) {
|
||||||
return devices.find(d => d.id === id);
|
return devices.find((device) => Number(device.id) === Number(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* =========================
|
document.addEventListener('keydown', (event) => {
|
||||||
* Keyboard Shortcuts
|
if (event.key === 'Escape') {
|
||||||
* ========================= */
|
|
||||||
|
|
||||||
document.addEventListener('keydown', (e) => {
|
|
||||||
if (e.key === 'Escape') {
|
|
||||||
selectedDeviceId = null;
|
selectedDeviceId = null;
|
||||||
updateSelection();
|
updateSelection();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Delete -> Gerät entfernen?
|
if (event.key === 'Delete' && selectedDeviceId) {
|
||||||
|
console.warn('Delete von Geraeten ist in der Netzwerkansicht noch nicht implementiert.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key.toLowerCase() === 's' && (event.ctrlKey || event.metaKey)) {
|
||||||
|
event.preventDefault();
|
||||||
|
savePositions();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -1,92 +1,60 @@
|
|||||||
// 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)
|
|
||||||
*/
|
|
||||||
|
|
||||||
(() => {
|
(() => {
|
||||||
/* =========================
|
const svgElement = document.querySelector('#device-svg');
|
||||||
* Konfiguration
|
if (!svgElement) {
|
||||||
* ========================= */
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: vom Backend setzen (z. B. via data-Attribut)
|
const DEVICE_TYPE_ID = Number(svgElement.dataset.deviceTypeId || 0);
|
||||||
const DEVICE_TYPE_ID = null;
|
|
||||||
|
|
||||||
// TODO: API-Endpunkte festlegen
|
|
||||||
const API_LOAD_PORTS = '/api/device_type_ports.php?action=load';
|
const API_LOAD_PORTS = '/api/device_type_ports.php?action=load';
|
||||||
const API_SAVE_PORTS = '/api/device_type_ports.php?action=save';
|
const API_SAVE_PORTS = '/api/device_type_ports.php?action=save';
|
||||||
|
const DEFAULT_PORT_TYPE_ID = null;
|
||||||
|
|
||||||
/* =========================
|
|
||||||
* State
|
|
||||||
* ========================= */
|
|
||||||
|
|
||||||
let svgElement = null;
|
|
||||||
let ports = [];
|
let ports = [];
|
||||||
let selectedPortId = null;
|
let selectedPortId = null;
|
||||||
let isDragging = false;
|
let isDragging = false;
|
||||||
let dragOffset = { x: 0, y: 0 };
|
let dragOffset = { x: 0, y: 0 };
|
||||||
|
|
||||||
/* =========================
|
bindSvgEvents();
|
||||||
* Initialisierung
|
loadPorts();
|
||||||
* ========================= */
|
|
||||||
|
|
||||||
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() {
|
function bindSvgEvents() {
|
||||||
svgElement.addEventListener('click', onSvgClick);
|
svgElement.addEventListener('click', onSvgClick);
|
||||||
svgElement.addEventListener('mousemove', onSvgMouseMove);
|
svgElement.addEventListener('mousemove', onSvgMouseMove);
|
||||||
svgElement.addEventListener('mouseup', onSvgMouseUp);
|
svgElement.addEventListener('mouseup', onSvgMouseUp);
|
||||||
|
|
||||||
|
const saveButton = document.querySelector('[data-save-svg-ports]');
|
||||||
|
if (saveButton) {
|
||||||
|
saveButton.addEventListener('click', (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
savePorts();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* =========================
|
|
||||||
* Port-Erstellung
|
|
||||||
* ========================= */
|
|
||||||
|
|
||||||
function onSvgClick(event) {
|
function onSvgClick(event) {
|
||||||
// Klick auf bestehenden Port?
|
|
||||||
if (event.target.classList.contains('port-point')) {
|
if (event.target.classList.contains('port-point')) {
|
||||||
selectPort(event.target.dataset.id);
|
selectPort(event.target.dataset.id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Modifier-Key prüfen (z. B. nur mit SHIFT neuen Port erstellen?)
|
// New ports are only created while SHIFT is held.
|
||||||
const point = getSvgCoordinates(event);
|
if (!event.shiftKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const point = getSvgCoordinates(event);
|
||||||
createPort(point.x, point.y);
|
createPort(point.x, point.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createPort(x, y) {
|
function createPort(x, y) {
|
||||||
const id = generateTempId();
|
const id = generateTempId();
|
||||||
|
|
||||||
const port = {
|
const port = {
|
||||||
id: id,
|
id,
|
||||||
name: `Port ${ports.length + 1}`,
|
name: `Port ${ports.length + 1}`,
|
||||||
port_type_id: null, // TODO: Default-Porttyp?
|
port_type_id: DEFAULT_PORT_TYPE_ID,
|
||||||
x: x,
|
x,
|
||||||
y: y,
|
y,
|
||||||
comment: ''
|
metadata: null
|
||||||
};
|
};
|
||||||
|
|
||||||
ports.push(port);
|
ports.push(port);
|
||||||
@@ -94,13 +62,8 @@ function createPort(x, y) {
|
|||||||
selectPort(id);
|
selectPort(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* =========================
|
|
||||||
* Rendering
|
|
||||||
* ========================= */
|
|
||||||
|
|
||||||
function renderPort(port) {
|
function renderPort(port) {
|
||||||
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
||||||
|
|
||||||
circle.setAttribute('cx', port.x);
|
circle.setAttribute('cx', port.x);
|
||||||
circle.setAttribute('cy', port.y);
|
circle.setAttribute('cy', port.y);
|
||||||
circle.setAttribute('r', 6);
|
circle.setAttribute('r', 6);
|
||||||
@@ -116,27 +79,39 @@ function renderPort(port) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function rerenderPorts() {
|
function rerenderPorts() {
|
||||||
svgElement.querySelectorAll('.port-point').forEach(p => p.remove());
|
svgElement.querySelectorAll('.port-point').forEach((node) => node.remove());
|
||||||
ports.forEach(renderPort);
|
ports.forEach(renderPort);
|
||||||
|
if (selectedPortId !== null) {
|
||||||
|
selectPort(selectedPortId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* =========================
|
|
||||||
* Auswahl
|
|
||||||
* ========================= */
|
|
||||||
|
|
||||||
function selectPort(id) {
|
function selectPort(id) {
|
||||||
selectedPortId = id;
|
selectedPortId = id;
|
||||||
|
|
||||||
document.querySelectorAll('.port-point').forEach(el => {
|
document.querySelectorAll('.port-point').forEach((el) => {
|
||||||
el.classList.toggle('selected', el.dataset.id === id);
|
el.classList.toggle('selected', el.dataset.id === String(id));
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Sidebar-Felder mit Portdaten füllen
|
const selected = getPortById(id);
|
||||||
|
fillSidebar(selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* =========================
|
function fillSidebar(port) {
|
||||||
* Drag & Drop
|
const nameField = document.querySelector('[data-port-name]');
|
||||||
* ========================= */
|
const typeField = document.querySelector('[data-port-type-id]');
|
||||||
|
const xField = document.querySelector('[data-port-x]');
|
||||||
|
const yField = document.querySelector('[data-port-y]');
|
||||||
|
|
||||||
|
if (nameField) nameField.value = port?.name || '';
|
||||||
|
if (typeField) typeField.value = port?.port_type_id || '';
|
||||||
|
if (xField) xField.value = port ? Math.round(port.x) : '';
|
||||||
|
if (yField) yField.value = port ? Math.round(port.y) : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetSidebar() {
|
||||||
|
fillSidebar(null);
|
||||||
|
}
|
||||||
|
|
||||||
function startDrag(event, portId) {
|
function startDrag(event, portId) {
|
||||||
const port = getPortById(portId);
|
const port = getPortById(portId);
|
||||||
@@ -157,7 +132,6 @@ function onSvgMouseMove(event) {
|
|||||||
if (!port) return;
|
if (!port) return;
|
||||||
|
|
||||||
const point = getSvgCoordinates(event);
|
const point = getSvgCoordinates(event);
|
||||||
|
|
||||||
port.x = point.x + dragOffset.x;
|
port.x = point.x + dragOffset.x;
|
||||||
port.y = point.y + dragOffset.y;
|
port.y = point.y + dragOffset.y;
|
||||||
|
|
||||||
@@ -168,92 +142,95 @@ function onSvgMouseUp() {
|
|||||||
isDragging = false;
|
isDragging = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* =========================
|
|
||||||
* Löschen
|
|
||||||
* ========================= */
|
|
||||||
|
|
||||||
function deleteSelectedPort() {
|
function deleteSelectedPort() {
|
||||||
if (!selectedPortId) return;
|
if (!selectedPortId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Sicherheitsabfrage (confirm)
|
if (!confirm('Ausgewaehlten Port loeschen?')) {
|
||||||
ports = ports.filter(p => p.id !== selectedPortId);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ports = ports.filter((port) => String(port.id) !== String(selectedPortId));
|
||||||
selectedPortId = null;
|
selectedPortId = null;
|
||||||
|
|
||||||
rerenderPorts();
|
rerenderPorts();
|
||||||
|
resetSidebar();
|
||||||
// TODO: Sidebar zurücksetzen
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* =========================
|
|
||||||
* Laden / Speichern
|
|
||||||
* ========================= */
|
|
||||||
|
|
||||||
function loadPorts() {
|
function loadPorts() {
|
||||||
if (!DEVICE_TYPE_ID) {
|
if (!DEVICE_TYPE_ID) {
|
||||||
console.warn('DEVICE_TYPE_ID nicht gesetzt');
|
console.warn('SVG Editor: DEVICE_TYPE_ID fehlt auf #device-svg');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch(`${API_LOAD_PORTS}&device_type_id=${DEVICE_TYPE_ID}`)
|
fetch(`${API_LOAD_PORTS}&device_type_id=${DEVICE_TYPE_ID}`)
|
||||||
.then(res => res.json())
|
.then((res) => res.json())
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
// TODO: Datenformat validieren
|
if (!Array.isArray(data)) {
|
||||||
ports = data;
|
throw new Error('Antwortformat ungueltig');
|
||||||
|
}
|
||||||
|
|
||||||
|
ports = data
|
||||||
|
.filter((entry) => entry && typeof entry === 'object')
|
||||||
|
.map((entry) => ({
|
||||||
|
id: entry.id,
|
||||||
|
name: String(entry.name || ''),
|
||||||
|
port_type_id: entry.port_type_id ? Number(entry.port_type_id) : null,
|
||||||
|
x: Number(entry.x || 0),
|
||||||
|
y: Number(entry.y || 0),
|
||||||
|
metadata: entry.metadata || null
|
||||||
|
}));
|
||||||
|
|
||||||
rerenderPorts();
|
rerenderPorts();
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch((err) => {
|
||||||
console.error('Fehler beim Laden der Ports', err);
|
console.error('Fehler beim Laden der Ports', err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function savePorts() {
|
function savePorts() {
|
||||||
if (!DEVICE_TYPE_ID) return;
|
if (!DEVICE_TYPE_ID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
fetch(API_SAVE_PORTS, {
|
fetch(API_SAVE_PORTS, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
device_type_id: DEVICE_TYPE_ID,
|
device_type_id: DEVICE_TYPE_ID,
|
||||||
ports: ports
|
ports
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.then(res => res.json())
|
.then((res) => res.json())
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
// TODO: Erfolg / Fehler anzeigen
|
if (data?.error) {
|
||||||
console.log('Ports gespeichert', data);
|
throw new Error(data.error);
|
||||||
})
|
}
|
||||||
.catch(err => {
|
alert('Ports gespeichert');
|
||||||
console.error('Fehler beim Speichern', err);
|
})
|
||||||
});
|
.catch((err) => {
|
||||||
|
alert('Speichern fehlgeschlagen: ' + err.message);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* =========================
|
|
||||||
* Hilfsfunktionen
|
|
||||||
* ========================= */
|
|
||||||
|
|
||||||
function getSvgCoordinates(event) {
|
function getSvgCoordinates(event) {
|
||||||
const pt = svgElement.createSVGPoint();
|
const pt = svgElement.createSVGPoint();
|
||||||
pt.x = event.clientX;
|
pt.x = event.clientX;
|
||||||
pt.y = event.clientY;
|
pt.y = event.clientY;
|
||||||
|
|
||||||
const transformed = pt.matrixTransform(svgElement.getScreenCTM().inverse());
|
const transformed = pt.matrixTransform(svgElement.getScreenCTM().inverse());
|
||||||
return { x: transformed.x, y: transformed.y };
|
return { x: transformed.x, y: transformed.y };
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPortById(id) {
|
function getPortById(id) {
|
||||||
return ports.find(p => p.id === id);
|
return ports.find((port) => String(port.id) === String(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateTempId() {
|
function generateTempId() {
|
||||||
return 'tmp_' + Math.random().toString(36).substr(2, 9);
|
return 'tmp_' + Math.random().toString(36).slice(2, 11);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* =========================
|
document.addEventListener('keydown', (event) => {
|
||||||
* Keyboard Shortcuts
|
if (event.key === 'Delete') {
|
||||||
* ========================= */
|
|
||||||
|
|
||||||
document.addEventListener('keydown', (e) => {
|
|
||||||
if (e.key === 'Delete') {
|
|
||||||
deleteSelectedPort();
|
deleteSelectedPort();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,41 +2,18 @@
|
|||||||
/**
|
/**
|
||||||
* bootstrap.php
|
* bootstrap.php
|
||||||
*
|
*
|
||||||
* Initialisierung der Anwendung
|
* Application initialization.
|
||||||
* - Config laden
|
|
||||||
* - Session starten
|
|
||||||
* - DB-Verbindung über _sql.php
|
|
||||||
* - Helper einbinden
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* =========================
|
|
||||||
* Config laden
|
|
||||||
* ========================= */
|
|
||||||
require_once __DIR__ . '/config.php';
|
require_once __DIR__ . '/config.php';
|
||||||
// TODO: Config-Datei mit DB-Zugang, Pfaden, globalen Settings füllen
|
date_default_timezone_set(defined('APP_TIMEZONE') ? APP_TIMEZONE : 'UTC');
|
||||||
|
|
||||||
/* =========================
|
if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||||
* Session starten
|
session_start();
|
||||||
* ========================= */
|
}
|
||||||
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';
|
require_once __DIR__ . '/lib/_sql.php';
|
||||||
|
|
||||||
// TODO: Host, User, Passwort, DB aus config.php nutzen
|
|
||||||
$sql = new SQL();
|
$sql = new SQL();
|
||||||
|
|
||||||
/* =========================
|
|
||||||
* Helper laden
|
|
||||||
* ========================= */
|
|
||||||
require_once __DIR__ . '/lib/helpers.php';
|
require_once __DIR__ . '/lib/helpers.php';
|
||||||
|
require_once __DIR__ . '/lib/auth.php';
|
||||||
/* =========================
|
|
||||||
* Optional: Fehlerbehandlung
|
|
||||||
* ========================= */
|
|
||||||
// error_reporting(E_ALL);
|
|
||||||
// ini_set('display_errors', 1);
|
|
||||||
|
|||||||
@@ -1,2 +1,19 @@
|
|||||||
<?php
|
<?php
|
||||||
// Zentrale Konfiguration (DB-Zugangsdaten, Pfade, globale Settings)
|
// Zentrale Konfiguration (DB-Zugangsdaten, Pfade, globale Settings)
|
||||||
|
|
||||||
|
define('APP_NAME', 'netwatch');
|
||||||
|
define('APP_ENV', getenv('APP_ENV') ?: 'development');
|
||||||
|
define('APP_TIMEZONE', getenv('APP_TIMEZONE') ?: 'Europe/Berlin');
|
||||||
|
|
||||||
|
define('DB_HOST', getenv('DB_HOST') ?: 'netdoc_db');
|
||||||
|
define('DB_USER', getenv('DB_USER') ?: 'netdoc');
|
||||||
|
define('DB_PASS', getenv('DB_PASS') ?: 'netdoc');
|
||||||
|
define('DB_NAME', getenv('DB_NAME') ?: 'netdoc');
|
||||||
|
|
||||||
|
define('AUTH_REQUIRED', (getenv('AUTH_REQUIRED') ?: '0') === '1');
|
||||||
|
define('ADMIN_PASSWORD_HASH', getenv('ADMIN_PASSWORD_HASH') ?: '');
|
||||||
|
define('LOGIN_PATH', getenv('LOGIN_PATH') ?: '/login.php');
|
||||||
|
|
||||||
|
define('UPLOAD_BASE_DIR', __DIR__ . '/uploads');
|
||||||
|
define('UPLOAD_MAX_FILE_SIZE', (int)(getenv('UPLOAD_MAX_FILE_SIZE') ?: 5 * 1024 * 1024));
|
||||||
|
define('UPLOAD_ALLOWED_CATEGORIES', ['device_types', 'floors', 'racks', 'rooms', 'misc']);
|
||||||
|
|||||||
@@ -1,72 +1,37 @@
|
|||||||
<?php
|
<?php
|
||||||
// Einstiegspunkt der Anwendung, Routing zur jeweiligen Modul-Seite
|
// Einstiegspunkt der Anwendung, Routing zur jeweiligen Modul-Seite
|
||||||
|
|
||||||
|
require_once __DIR__ . '/bootstrap.php';
|
||||||
|
requireAuth();
|
||||||
|
|
||||||
/**
|
|
||||||
* 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';
|
$module = $_GET['module'] ?? 'dashboard';
|
||||||
$action = $_GET['action'] ?? 'list';
|
$action = $_GET['action'] ?? 'list';
|
||||||
|
|
||||||
// Whitelist der Module
|
|
||||||
$validModules = ['dashboard', 'locations', 'buildings', 'rooms', 'device_types', 'devices', 'racks', 'floors', 'floor_infrastructure', 'connections', 'port_types'];
|
$validModules = ['dashboard', 'locations', 'buildings', 'rooms', 'device_types', 'devices', 'racks', 'floors', 'floor_infrastructure', 'connections', 'port_types'];
|
||||||
|
$validActions = ['list', 'edit', 'save', 'ports', 'delete', 'swap'];
|
||||||
|
|
||||||
// Whitelist der Aktionen
|
if (!in_array($module, $validModules, true)) {
|
||||||
$validActions = ['list', 'edit', 'save', 'ports', 'delete'];
|
renderClientError(400, 'Ungueltiges Modul');
|
||||||
|
exit;
|
||||||
// Prüfen auf gültige Werte
|
|
||||||
if (!in_array($module, $validModules)) {
|
|
||||||
renderClientError(400, 'Ungültiges Modul');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!in_array($action, $validActions)) {
|
if (!in_array($action, $validActions, true)) {
|
||||||
// TODO: Fehlerseite anzeigen, nutze renderClientError(...)
|
renderClientError(400, 'Ungueltige Aktion');
|
||||||
die('Ungültige Aktion');
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* =========================
|
if (!in_array($action, ['save', 'delete', 'swap'], true)) {
|
||||||
* Template-Header laden (nur für View-Aktionen)
|
|
||||||
* ========================= */
|
|
||||||
if (!in_array($action, ['save', 'delete'], true)) {
|
|
||||||
require_once __DIR__ . '/templates/header.php';
|
require_once __DIR__ . '/templates/header.php';
|
||||||
}
|
}
|
||||||
|
|
||||||
/* =========================
|
|
||||||
* Modul laden
|
|
||||||
* ========================= */
|
|
||||||
$modulePath = __DIR__ . "/modules/$module/$action.php";
|
$modulePath = __DIR__ . "/modules/$module/$action.php";
|
||||||
|
|
||||||
if (file_exists($modulePath)) {
|
if (file_exists($modulePath)) {
|
||||||
require_once $modulePath;
|
require_once $modulePath;
|
||||||
} else {
|
} else {
|
||||||
// TODO: Fehlerseite oder 404, nutze renderClientError(...)
|
renderClientError(404, 'Die angeforderte Seite existiert nicht.');
|
||||||
if ($action !== 'save') {
|
|
||||||
echo "<p>Die Seite existiert noch nicht.</p>".$modulePath;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* =========================
|
if (!in_array($action, ['save', 'delete', 'swap'], true)) {
|
||||||
* Template-Footer laden (nur für View-Aktionen)
|
|
||||||
* ========================= */
|
|
||||||
if (!in_array($action, ['save', 'delete'], true)) {
|
|
||||||
require_once __DIR__ . '/templates/footer.php';
|
require_once __DIR__ . '/templates/footer.php';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,19 @@ class SQL {
|
|||||||
public $cnt_get = 0;
|
public $cnt_get = 0;
|
||||||
public $cnt_set = 0;
|
public $cnt_set = 0;
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
require_once ('secret.php');
|
if (defined('DB_HOST') && defined('DB_USER') && defined('DB_PASS') && defined('DB_NAME')) {
|
||||||
|
$this->m = [
|
||||||
|
'host' => DB_HOST,
|
||||||
|
'user' => DB_USER,
|
||||||
|
'pass' => DB_PASS,
|
||||||
|
'data' => DB_NAME
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
require_once ('secret.php');
|
||||||
|
$this->m = $_m;
|
||||||
|
}
|
||||||
|
|
||||||
$this->m = $_m;
|
$this->h = new mysqli ( $this->m ['host'], $this->m ['user'], $this->m ['pass'], $this->m ['data'] );
|
||||||
|
|
||||||
$this->h = new mysqli ( $_m ['host'], $_m ['user'], $_m ['pass'], $_m ['data'] );
|
|
||||||
if ($this->h->connect_errno) {
|
if ($this->h->connect_errno) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,82 +2,60 @@
|
|||||||
/**
|
/**
|
||||||
* app/lib/auth.php
|
* app/lib/auth.php
|
||||||
*
|
*
|
||||||
* Single-User-Authentifizierung
|
* Single-user authentication helpers.
|
||||||
* - Login / Logout
|
|
||||||
* - Session-Check
|
|
||||||
* - Optional: Passwortschutz für Admin-Tool
|
|
||||||
*
|
|
||||||
* KEIN Mehrbenutzer-System
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* =========================
|
|
||||||
* Login prüfen
|
|
||||||
* ========================= */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prüft, ob der Benutzer eingeloggt ist
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
function isAuthenticated(): bool
|
function isAuthenticated(): bool
|
||||||
{
|
{
|
||||||
// TODO: Session-Variable definieren, z.B. $_SESSION['auth'] === true
|
if (!defined('AUTH_REQUIRED') || AUTH_REQUIRED === false) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return isset($_SESSION['auth']) && $_SESSION['auth'] === true;
|
return isset($_SESSION['auth']) && $_SESSION['auth'] === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* =========================
|
|
||||||
* Login durchführen
|
|
||||||
* ========================= */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Führt einen Login durch
|
|
||||||
*
|
|
||||||
* @param string $password
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
function login(string $password): bool
|
function login(string $password): bool
|
||||||
{
|
{
|
||||||
// TODO: Passwort aus config.php vergleichen
|
$hash = defined('ADMIN_PASSWORD_HASH') ? trim((string)ADMIN_PASSWORD_HASH) : '';
|
||||||
// TODO: Passwort-Hash verwenden (password_hash / password_verify)
|
if ($hash === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
if (password_verify($password, $hash)) {
|
||||||
if (password_verify($password, ADMIN_PASSWORD_HASH)) {
|
|
||||||
$_SESSION['auth'] = true;
|
$_SESSION['auth'] = true;
|
||||||
|
$_SESSION['auth_at'] = time();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* =========================
|
|
||||||
* Logout
|
|
||||||
* ========================= */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loggt den Benutzer aus
|
|
||||||
*/
|
|
||||||
function logout(): void
|
function logout(): void
|
||||||
{
|
{
|
||||||
// TODO: Session-Variablen löschen
|
unset($_SESSION['auth'], $_SESSION['auth_at']);
|
||||||
// unset($_SESSION['auth']);
|
|
||||||
|
|
||||||
// TODO: Optional komplette Session zerstören
|
if (session_status() === PHP_SESSION_ACTIVE) {
|
||||||
// session_destroy();
|
session_regenerate_id(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* =========================
|
|
||||||
* Zugriff erzwingen
|
|
||||||
* ========================= */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erzwingt Login, sonst Redirect
|
|
||||||
*/
|
|
||||||
function requireAuth(): void
|
function requireAuth(): void
|
||||||
{
|
{
|
||||||
|
if (!defined('AUTH_REQUIRED') || AUTH_REQUIRED === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!isAuthenticated()) {
|
if (!isAuthenticated()) {
|
||||||
// TODO: Redirect auf Login-Seite
|
$isApiRequest = str_starts_with($_SERVER['REQUEST_URI'] ?? '', '/api/');
|
||||||
// header('Location: /login.php');
|
if ($isApiRequest) {
|
||||||
|
http_response_code(401);
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode(['error' => 'Nicht authentifiziert']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$target = defined('LOGIN_PATH') ? LOGIN_PATH : '/login.php';
|
||||||
|
header('Location: ' . $target);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
35
app/modules/buildings/delete.php
Normal file
35
app/modules/buildings/delete.php
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* app/modules/buildings/delete.php
|
||||||
|
*/
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
http_response_code(405);
|
||||||
|
echo json_encode(['error' => 'Methode nicht erlaubt']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$id = (int)($_POST['id'] ?? $_GET['id'] ?? 0);
|
||||||
|
if ($id <= 0) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['error' => 'ID fehlt']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$exists = $sql->single("SELECT id FROM buildings WHERE id = ?", "i", [$id]);
|
||||||
|
if (!$exists) {
|
||||||
|
http_response_code(404);
|
||||||
|
echo json_encode(['error' => 'Gebaeude nicht gefunden']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$rows = $sql->set("DELETE FROM buildings WHERE id = ?", "i", [$id]);
|
||||||
|
if ($rows === false) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => 'Loeschen fehlgeschlagen']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['status' => 'ok', 'success' => true, 'rows' => $rows]);
|
||||||
@@ -47,7 +47,7 @@ $selectedLocationId = $building['location_id'] ?? $prefillLocationId;
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="name">Name <span class="required">*</span></label>
|
<label for="name">Name <span class="required">*</span></label>
|
||||||
<input type="text" id="name" name="name" required
|
<input type="text" id="name" name="name" required
|
||||||
value="<?php echo htmlspecialchars($building['name'] ?? ''); ?>"
|
value="<?php echo htmlspecialchars($building['name'] ?? '); ?>"
|
||||||
placeholder="z.B. Gebäude A, Verwaltungsgebäude">
|
placeholder="z.B. Gebäude A, Verwaltungsgebäude">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ $selectedLocationId = $building['location_id'] ?? $prefillLocationId;
|
|||||||
<option value="">- Wählen -</option>
|
<option value="">- Wählen -</option>
|
||||||
<?php foreach ($locations as $location): ?>
|
<?php foreach ($locations as $location): ?>
|
||||||
<option value="<?php echo $location['id']; ?>"
|
<option value="<?php echo $location['id']; ?>"
|
||||||
<?php echo ((int)$selectedLocationId === (int)$location['id']) ? 'selected' : ''; ?>>
|
<?php echo ((int)$selectedLocationId === (int)$location['id']) ? 'selected' : '; ?>>
|
||||||
<?php echo htmlspecialchars($location['name']); ?>
|
<?php echo htmlspecialchars($location['name']); ?>
|
||||||
</option>
|
</option>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
@@ -67,7 +67,7 @@ $selectedLocationId = $building['location_id'] ?? $prefillLocationId;
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="comment">Beschreibung</label>
|
<label for="comment">Beschreibung</label>
|
||||||
<textarea id="comment" name="comment" rows="3"
|
<textarea id="comment" name="comment" rows="3"
|
||||||
placeholder="Adresse, Besonderheiten, etc."><?php echo htmlspecialchars($building['comment'] ?? ''); ?></textarea>
|
placeholder="Adresse, Besonderheiten, etc."><?php echo htmlspecialchars($building['comment'] ?? '); ?></textarea>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
@@ -172,9 +172,25 @@ $selectedLocationId = $building['location_id'] ?? $prefillLocationId;
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
function confirmDelete(id) {
|
function confirmDelete(id) {
|
||||||
if (confirm('Dieses Gebäude wirklich löschen? Alle Stockwerke werden gelöscht.')) {
|
if (confirm('Dieses Gebaeude wirklich loeschen? Alle Stockwerke werden geloescht.')) {
|
||||||
// TODO: AJAX-Delete implementieren
|
fetch('?module=buildings&action=delete', {
|
||||||
alert('Löschen noch nicht implementiert');
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
|
||||||
|
body: 'id=' + encodeURIComponent(id)
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
if (data && data.status === 'ok') {
|
||||||
|
window.location.href = '?module=buildings&action=list';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
alert((data && data.error) ? data.error : 'Loeschen fehlgeschlagen');
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
alert('Loeschen fehlgeschlagen');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,17 +8,17 @@
|
|||||||
// =========================
|
// =========================
|
||||||
// Filter einlesen
|
// Filter einlesen
|
||||||
// =========================
|
// =========================
|
||||||
$search = trim($_GET['search'] ?? '');
|
$search = trim($_GET['search'] ?? ');
|
||||||
$locationId = (int)($_GET['location_id'] ?? 0);
|
$locationId = (int)($_GET['location_id'] ?? 0);
|
||||||
|
|
||||||
// =========================
|
// =========================
|
||||||
// WHERE-Clause bauen
|
// WHERE-Clause bauen
|
||||||
// =========================
|
// =========================
|
||||||
$where = [];
|
$where = [];
|
||||||
$types = '';
|
$types = ';
|
||||||
$params = [];
|
$params = [];
|
||||||
|
|
||||||
if ($search !== '') {
|
if ($search !== ') {
|
||||||
$where[] = "b.name LIKE ? OR b.comment LIKE ?";
|
$where[] = "b.name LIKE ? OR b.comment LIKE ?";
|
||||||
$types .= "ss";
|
$types .= "ss";
|
||||||
$params[] = "%$search%";
|
$params[] = "%$search%";
|
||||||
@@ -70,7 +70,7 @@ $locations = $sql->get("SELECT id, name FROM locations ORDER BY name", "", []);
|
|||||||
<option value="">- Alle Standorte -</option>
|
<option value="">- Alle Standorte -</option>
|
||||||
<?php foreach ($locations as $loc): ?>
|
<?php foreach ($locations as $loc): ?>
|
||||||
<option value="<?php echo $loc['id']; ?>"
|
<option value="<?php echo $loc['id']; ?>"
|
||||||
<?php echo $loc['id'] === $locationId ? 'selected' : ''; ?>>
|
<?php echo $loc['id'] === $locationId ? 'selected' : '; ?>>
|
||||||
<?php echo htmlspecialchars($loc['name']); ?>
|
<?php echo htmlspecialchars($loc['name']); ?>
|
||||||
</option>
|
</option>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
@@ -112,7 +112,7 @@ $locations = $sql->get("SELECT id, name FROM locations ORDER BY name", "", []);
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<small><?php echo htmlspecialchars($building['comment'] ?? ''); ?></small>
|
<small><?php echo htmlspecialchars($building['comment'] ?? '); ?></small>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="actions">
|
<td class="actions">
|
||||||
@@ -241,9 +241,24 @@ $locations = $sql->get("SELECT id, name FROM locations ORDER BY name", "", []);
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
function confirmDelete(id) {
|
function confirmDelete(id) {
|
||||||
if (confirm('Dieses Gebäude wirklich löschen?')) {
|
if (confirm('Dieses Gebaeude wirklich loeschen?')) {
|
||||||
// TODO: AJAX-Delete implementieren
|
fetch('?module=buildings&action=delete', {
|
||||||
alert('Löschen noch nicht implementiert');
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
|
||||||
|
body: 'id=' + encodeURIComponent(id)
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
if (data && data.status === 'ok') {
|
||||||
|
window.location.reload();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
alert((data && data.error) ? data.error : 'Loeschen fehlgeschlagen');
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
alert('Loeschen fehlgeschlagen');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,68 @@ $endpointOptions = [
|
|||||||
'patchpanel' => [],
|
'patchpanel' => [],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$occupiedByType = [
|
||||||
|
'device' => [],
|
||||||
|
'module' => [],
|
||||||
|
'outlet' => [],
|
||||||
|
'patchpanel' => [],
|
||||||
|
];
|
||||||
|
$occupiedRows = $sql->get(
|
||||||
|
"SELECT id, port_a_type, port_a_id, port_b_type, port_b_id
|
||||||
|
FROM connections
|
||||||
|
WHERE id <> ?",
|
||||||
|
"i",
|
||||||
|
[$connectionId]
|
||||||
|
);
|
||||||
|
foreach ((array)$occupiedRows as $row) {
|
||||||
|
$typeA = $normalizePortType((string)($row['port_a_type'] ?? ''));
|
||||||
|
$idA = (int)($row['port_a_id'] ?? 0);
|
||||||
|
if ($idA > 0 && isset($occupiedByType[$typeA])) {
|
||||||
|
$occupiedByType[$typeA][$idA] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$typeB = $normalizePortType((string)($row['port_b_type'] ?? ''));
|
||||||
|
$idB = (int)($row['port_b_id'] ?? 0);
|
||||||
|
if ($idB > 0 && isset($occupiedByType[$typeB])) {
|
||||||
|
$occupiedByType[$typeB][$idB] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$isEndpointAllowed = static function (string $type, int $id) use ($occupiedByType, $portAType, $portAId, $portBType, $portBId): bool {
|
||||||
|
if ($id <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ($type === $portAType && $id === $portAId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ($type === $portBType && $id === $portBId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return empty($occupiedByType[$type][$id]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Auto-heal: ensure each outlet has at least one selectable port.
|
||||||
|
$outletsWithoutPorts = $sql->get(
|
||||||
|
"SELECT o.id
|
||||||
|
FROM network_outlets o
|
||||||
|
LEFT JOIN network_outlet_ports nop ON nop.outlet_id = o.id
|
||||||
|
GROUP BY o.id
|
||||||
|
HAVING COUNT(nop.id) = 0",
|
||||||
|
"",
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
foreach ((array)$outletsWithoutPorts as $outletRow) {
|
||||||
|
$outletId = (int)($outletRow['id'] ?? 0);
|
||||||
|
if ($outletId <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$sql->set(
|
||||||
|
"INSERT INTO network_outlet_ports (outlet_id, name) VALUES (?, 'Port 1')",
|
||||||
|
"i",
|
||||||
|
[$outletId]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
$devicePorts = $sql->get(
|
$devicePorts = $sql->get(
|
||||||
"SELECT dp.id, dp.name, d.name AS owner_name
|
"SELECT dp.id, dp.name, d.name AS owner_name
|
||||||
FROM device_ports dp
|
FROM device_ports dp
|
||||||
@@ -55,8 +117,12 @@ $devicePorts = $sql->get(
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
foreach ($devicePorts as $row) {
|
foreach ($devicePorts as $row) {
|
||||||
|
$id = (int)$row['id'];
|
||||||
|
if (!$isEndpointAllowed('device', $id)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$endpointOptions['device'][] = [
|
$endpointOptions['device'][] = [
|
||||||
'id' => (int)$row['id'],
|
'id' => $id,
|
||||||
'label' => $row['owner_name'] . ' / ' . $row['name'],
|
'label' => $row['owner_name'] . ' / ' . $row['name'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -78,27 +144,35 @@ $modulePorts = $sql->get(
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
foreach ($modulePorts as $row) {
|
foreach ($modulePorts as $row) {
|
||||||
|
$id = (int)$row['id'];
|
||||||
|
if (!$isEndpointAllowed('module', $id)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$deviceName = trim((string)($row['device_name'] ?? '')) ?: 'Unzugeordnet';
|
$deviceName = trim((string)($row['device_name'] ?? '')) ?: 'Unzugeordnet';
|
||||||
$endpointOptions['module'][] = [
|
$endpointOptions['module'][] = [
|
||||||
'id' => (int)$row['id'],
|
'id' => $id,
|
||||||
'label' => $deviceName . ' / ' . $row['module_name'] . ' / ' . $row['name'],
|
'label' => $deviceName . ' / ' . $row['module_name'] . ' / ' . $row['name'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
$outletPorts = $sql->get(
|
$outletPorts = $sql->get(
|
||||||
"SELECT nop.id, nop.name, no.name AS outlet_name, r.name AS room_name, f.name AS floor_name
|
"SELECT nop.id, nop.name, o.name AS outlet_name, r.name AS room_name, f.name AS floor_name
|
||||||
FROM network_outlet_ports nop
|
FROM network_outlet_ports nop
|
||||||
JOIN network_outlets no ON no.id = nop.outlet_id
|
JOIN network_outlets o ON o.id = nop.outlet_id
|
||||||
LEFT JOIN rooms r ON r.id = no.room_id
|
LEFT JOIN rooms r ON r.id = o.room_id
|
||||||
LEFT JOIN floors f ON f.id = r.floor_id
|
LEFT JOIN floors f ON f.id = r.floor_id
|
||||||
ORDER BY floor_name, room_name, outlet_name, nop.name",
|
ORDER BY floor_name, room_name, outlet_name, nop.name",
|
||||||
"",
|
"",
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
foreach ($outletPorts as $row) {
|
foreach ($outletPorts as $row) {
|
||||||
|
$id = (int)$row['id'];
|
||||||
|
if (!$isEndpointAllowed('outlet', $id)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$parts = array_filter([(string)($row['floor_name'] ?? ''), (string)($row['room_name'] ?? ''), (string)$row['outlet_name'], (string)$row['name']]);
|
$parts = array_filter([(string)($row['floor_name'] ?? ''), (string)($row['room_name'] ?? ''), (string)$row['outlet_name'], (string)$row['name']]);
|
||||||
$endpointOptions['outlet'][] = [
|
$endpointOptions['outlet'][] = [
|
||||||
'id' => (int)$row['id'],
|
'id' => $id,
|
||||||
'label' => implode(' / ', $parts),
|
'label' => implode(' / ', $parts),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -113,9 +187,13 @@ $patchpanelPorts = $sql->get(
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
foreach ($patchpanelPorts as $row) {
|
foreach ($patchpanelPorts as $row) {
|
||||||
|
$id = (int)$row['id'];
|
||||||
|
if (!$isEndpointAllowed('patchpanel', $id)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$parts = array_filter([(string)($row['floor_name'] ?? ''), (string)$row['patchpanel_name'], (string)$row['name']]);
|
$parts = array_filter([(string)($row['floor_name'] ?? ''), (string)$row['patchpanel_name'], (string)$row['name']]);
|
||||||
$endpointOptions['patchpanel'][] = [
|
$endpointOptions['patchpanel'][] = [
|
||||||
'id' => (int)$row['id'],
|
'id' => $id,
|
||||||
'label' => implode(' / ', $parts),
|
'label' => implode(' / ', $parts),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,11 +42,11 @@ $endpointUnionSql = "
|
|||||||
'outlet' AS endpoint_type,
|
'outlet' AS endpoint_type,
|
||||||
nop.id AS endpoint_id,
|
nop.id AS endpoint_id,
|
||||||
nop.name AS port_name,
|
nop.name AS port_name,
|
||||||
CONCAT(no.name, ' / ', IFNULL(r.name, ''), ' / ', IFNULL(f.name, '')) AS owner_name,
|
CONCAT(o.name, ' / ', IFNULL(r.name, ''), ' / ', IFNULL(f.name, '')) AS owner_name,
|
||||||
NULL AS owner_device_id
|
NULL AS owner_device_id
|
||||||
FROM network_outlet_ports nop
|
FROM network_outlet_ports nop
|
||||||
JOIN network_outlets no ON no.id = nop.outlet_id
|
JOIN network_outlets o ON o.id = nop.outlet_id
|
||||||
LEFT JOIN rooms r ON r.id = no.room_id
|
LEFT JOIN rooms r ON r.id = o.room_id
|
||||||
LEFT JOIN floors f ON f.id = r.floor_id
|
LEFT JOIN floors f ON f.id = r.floor_id
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT
|
SELECT
|
||||||
@@ -328,6 +328,7 @@ if ($deviceId > 0) {
|
|||||||
|
|
||||||
<td class="actions">
|
<td class="actions">
|
||||||
<a href="?module=connections&action=edit&id=<?php echo $conn['id']; ?>" class="button button-small">Bearbeiten</a>
|
<a href="?module=connections&action=edit&id=<?php echo $conn['id']; ?>" class="button button-small">Bearbeiten</a>
|
||||||
|
<a href="?module=connections&action=swap&id=<?php echo $conn['id']; ?>" class="button button-small" onclick="return confirm('Von/Nach fuer diese Verbindung vertauschen?');">Von/Nach tauschen</a>
|
||||||
<a href="#" class="button button-small button-danger"
|
<a href="#" class="button button-small button-danger"
|
||||||
data-confirm-delete="true"
|
data-confirm-delete="true"
|
||||||
data-confirm-message="Diese Verbindung wirklich löschen?"
|
data-confirm-message="Diese Verbindung wirklich löschen?"
|
||||||
|
|||||||
@@ -51,6 +51,41 @@ if ($portAId <= 0 || $portBId <= 0) {
|
|||||||
$errors[] = "Beide Ports sind erforderlich";
|
$errors[] = "Beide Ports sind erforderlich";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$otherConnections = $sql->get(
|
||||||
|
"SELECT id, port_a_type, port_a_id, port_b_type, port_b_id
|
||||||
|
FROM connections
|
||||||
|
WHERE id <> ?",
|
||||||
|
"i",
|
||||||
|
[$connId]
|
||||||
|
);
|
||||||
|
|
||||||
|
$isEndpointUsed = static function (string $endpointType, int $endpointId) use ($otherConnections, $normalizePortType): bool {
|
||||||
|
if ($endpointId <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
foreach ((array)$otherConnections as $row) {
|
||||||
|
$typeA = $normalizePortType((string)($row['port_a_type'] ?? ''));
|
||||||
|
$idA = (int)($row['port_a_id'] ?? 0);
|
||||||
|
if ($typeA === $endpointType && $idA === $endpointId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$typeB = $normalizePortType((string)($row['port_b_type'] ?? ''));
|
||||||
|
$idB = (int)($row['port_b_id'] ?? 0);
|
||||||
|
if ($typeB === $endpointType && $idB === $endpointId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
if ($isEndpointUsed($portAType, $portAId)) {
|
||||||
|
$errors[] = "Port an Endpunkt A ist bereits in Verwendung";
|
||||||
|
}
|
||||||
|
if ($isEndpointUsed($portBType, $portBId)) {
|
||||||
|
$errors[] = "Port an Endpunkt B ist bereits in Verwendung";
|
||||||
|
}
|
||||||
|
|
||||||
if (!empty($errors)) {
|
if (!empty($errors)) {
|
||||||
$_SESSION['error'] = implode(', ', $errors);
|
$_SESSION['error'] = implode(', ', $errors);
|
||||||
$redirectUrl = $connId ? "?module=connections&action=edit&id=$connId" : "?module=connections&action=edit";
|
$redirectUrl = $connId ? "?module=connections&action=edit&id=$connId" : "?module=connections&action=edit";
|
||||||
@@ -67,15 +102,36 @@ if ($connId > 0) {
|
|||||||
// UPDATE
|
// UPDATE
|
||||||
$sql->set(
|
$sql->set(
|
||||||
"UPDATE connections SET port_a_type = ?, port_a_id = ?, port_b_type = ?, port_b_id = ?, vlan_config = ?, comment = ? WHERE id = ?",
|
"UPDATE connections SET port_a_type = ?, port_a_id = ?, port_b_type = ?, port_b_id = ?, vlan_config = ?, comment = ? WHERE id = ?",
|
||||||
"siisisi",
|
"sisissi",
|
||||||
[$portAType, $portAId, $portBType, $portBId, $vlanJson, $comment, $connId]
|
[$portAType, $portAId, $portBType, $portBId, $vlanJson, $comment, $connId]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
$connectionTypeId = (int)($sql->single(
|
||||||
|
"SELECT id FROM connection_types ORDER BY id LIMIT 1",
|
||||||
|
"",
|
||||||
|
[]
|
||||||
|
)['id'] ?? 0);
|
||||||
|
|
||||||
|
if ($connectionTypeId <= 0) {
|
||||||
|
$connectionTypeId = (int)$sql->set(
|
||||||
|
"INSERT INTO connection_types (name, medium, duplex, line_style, comment) VALUES (?, ?, ?, ?, ?)",
|
||||||
|
"sssss",
|
||||||
|
['Default', 'copper', 'custom', 'solid', 'Auto-created by connections/save'],
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($connectionTypeId <= 0) {
|
||||||
|
$_SESSION['error'] = "Kein Verbindungstyp verfuegbar";
|
||||||
|
header("Location: ?module=connections&action=edit");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
// INSERT
|
// INSERT
|
||||||
$sql->set(
|
$sql->set(
|
||||||
"INSERT INTO connections (port_a_type, port_a_id, port_b_type, port_b_id, vlan_config, comment) VALUES (?, ?, ?, ?, ?, ?)",
|
"INSERT INTO connections (connection_type_id, port_a_type, port_a_id, port_b_type, port_b_id, vlan_config, comment) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||||
"siisis",
|
"isisiss",
|
||||||
[$portAType, $portAId, $portBType, $portBId, $vlanJson, $comment]
|
[$connectionTypeId, $portAType, $portAId, $portBType, $portBId, $vlanJson, $comment]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
46
app/modules/connections/swap.php
Normal file
46
app/modules/connections/swap.php
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* app/modules/connections/swap.php
|
||||||
|
*
|
||||||
|
* Vertauscht Endpunkt A und Endpunkt B einer Verbindung.
|
||||||
|
*/
|
||||||
|
|
||||||
|
$connectionId = (int)($_GET['id'] ?? 0);
|
||||||
|
|
||||||
|
if ($connectionId <= 0) {
|
||||||
|
$_SESSION['error'] = 'Ungueltige Verbindungs-ID';
|
||||||
|
header('Location: ?module=connections&action=list');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$connection = $sql->single(
|
||||||
|
"SELECT id, port_a_type, port_a_id, port_b_type, port_b_id
|
||||||
|
FROM connections
|
||||||
|
WHERE id = ?",
|
||||||
|
"i",
|
||||||
|
[$connectionId]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!$connection) {
|
||||||
|
$_SESSION['error'] = 'Verbindung nicht gefunden';
|
||||||
|
header('Location: ?module=connections&action=list');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql->set(
|
||||||
|
"UPDATE connections
|
||||||
|
SET port_a_type = ?, port_a_id = ?, port_b_type = ?, port_b_id = ?
|
||||||
|
WHERE id = ?",
|
||||||
|
"sisii",
|
||||||
|
[
|
||||||
|
(string)$connection['port_b_type'],
|
||||||
|
(int)$connection['port_b_id'],
|
||||||
|
(string)$connection['port_a_type'],
|
||||||
|
(int)$connection['port_a_id'],
|
||||||
|
$connectionId
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$_SESSION['success'] = 'Endpunkte wurden vertauscht';
|
||||||
|
header('Location: ?module=connections&action=list');
|
||||||
|
exit;
|
||||||
@@ -34,6 +34,10 @@ $recentDevices = $sql->get(
|
|||||||
<!-- Dashboard / Übersicht -->
|
<!-- Dashboard / Übersicht -->
|
||||||
<div class="dashboard">
|
<div class="dashboard">
|
||||||
<h1>Dashboard</h1>
|
<h1>Dashboard</h1>
|
||||||
|
<div id="dashboard-modules" class="dashboard-modules"></div>
|
||||||
|
<p data-dashboard-stats class="dashboard-inline-status"></p>
|
||||||
|
<p data-dashboard-warnings class="dashboard-inline-status"></p>
|
||||||
|
<p data-dashboard-recent class="dashboard-inline-status"></p>
|
||||||
|
|
||||||
<!-- Statistik-Karten -->
|
<!-- Statistik-Karten -->
|
||||||
<div class="stats-grid">
|
<div class="stats-grid">
|
||||||
@@ -100,6 +104,54 @@ $recentDevices = $sql->get(
|
|||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dashboard-modules {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
margin: 12px 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-tile {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
border: 1px solid #d7d7d7;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #222;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-icon {
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #0c4da2;
|
||||||
|
color: #fff;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-content h3 {
|
||||||
|
margin: 0 0 4px;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-content p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-inline-status {
|
||||||
|
margin: 6px 0;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
.stat-card {
|
.stat-card {
|
||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
|||||||
39
app/modules/device_types/delete.php
Normal file
39
app/modules/device_types/delete.php
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* app/modules/device_types/delete.php
|
||||||
|
*/
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
http_response_code(405);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Methode nicht erlaubt']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = (int)($_POST['id'] ?? $_GET['id'] ?? 0);
|
||||||
|
if ($id <= 0) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'ID fehlt']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$exists = $sql->single("SELECT id FROM device_types WHERE id = ?", "i", [$id]);
|
||||||
|
if (!$exists) {
|
||||||
|
http_response_code(404);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Geraetetyp nicht gefunden']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$rows = $sql->set("DELETE FROM device_types WHERE id = ?", "i", [$id]);
|
||||||
|
if ($rows === false) {
|
||||||
|
http_response_code(409);
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Geraetetyp konnte nicht geloescht werden (wird ggf. noch von Geraeten verwendet)'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'message' => 'Geraetetyp geloescht']);
|
||||||
|
|
||||||
@@ -15,7 +15,6 @@ $deviceTypeId = (int)($_GET['id'] ?? 0);
|
|||||||
$deviceType = null;
|
$deviceType = null;
|
||||||
$ports = [];
|
$ports = [];
|
||||||
|
|
||||||
//TODO port hinzufügen geht nicht
|
|
||||||
|
|
||||||
if ($deviceTypeId > 0) {
|
if ($deviceTypeId > 0) {
|
||||||
$deviceType = $sql->single(
|
$deviceType = $sql->single(
|
||||||
@@ -339,3 +338,4 @@ $pageTitle = $isEdit ? "Gerätetyp bearbeiten: " . htmlspecialchars($deviceType[
|
|||||||
</div>
|
</div>
|
||||||
<script src="/assets/js/device-type-shape-editor.js" defer></script>
|
<script src="/assets/js/device-type-shape-editor.js" defer></script>
|
||||||
<script src="/assets/js/device-type-edit-form.js" defer></script>
|
<script src="/assets/js/device-type-edit-form.js" defer></script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,265 +1,165 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* app/device_types/ports.php
|
* app/modules/device_types/ports.php
|
||||||
*
|
*
|
||||||
* Verwaltung der Ports eines Gerätetyps
|
* Verwaltung der Ports eines Geraetetyps.
|
||||||
* - Port anlegen / bearbeiten / löschen
|
|
||||||
* - Port-Typ (RJ45, SFP, BNC, Custom)
|
|
||||||
* - VLAN / Modus / Medienart
|
|
||||||
* - Übergabe an SVG-Port-Editor
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// TODO: bootstrap laden
|
$deviceTypeId = (int)($_GET['id'] ?? ($_GET['device_type_id'] ?? 0));
|
||||||
// require_once __DIR__ . '/../../bootstrap.php';
|
if ($deviceTypeId <= 0) {
|
||||||
|
renderClientError(400, 'device_type_id fehlt');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Auth erzwingen
|
$deviceType = $sql->single(
|
||||||
// requireAuth();
|
"SELECT id, name, image_path, image_type FROM device_types WHERE id = ?",
|
||||||
|
'i',
|
||||||
|
[$deviceTypeId]
|
||||||
|
);
|
||||||
|
|
||||||
// =========================
|
if (!$deviceType) {
|
||||||
// Kontext bestimmen
|
renderClientError(404, 'Geraetetyp nicht gefunden');
|
||||||
// =========================
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: device_type_id aus GET lesen
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
// $deviceTypeId = (int)($_GET['id'] ?? 0);
|
$formAction = $_POST['form_action'] ?? '';
|
||||||
|
|
||||||
// TODO: Gerätetyp laden
|
if ($formAction === 'add_port') {
|
||||||
// $deviceType = null;
|
$name = trim((string)($_POST['name'] ?? ''));
|
||||||
|
$portTypeId = (int)($_POST['port_type_id'] ?? 0);
|
||||||
|
$x = (int)($_POST['x'] ?? 0);
|
||||||
|
$y = (int)($_POST['y'] ?? 0);
|
||||||
|
|
||||||
// TODO: Ports dieses Gerätetyps laden
|
if ($name !== '') {
|
||||||
// $ports = [];
|
if ($portTypeId > 0) {
|
||||||
|
$sql->set(
|
||||||
|
"INSERT INTO device_type_ports (device_type_id, name, port_type_id, x, y) VALUES (?, ?, ?, ?, ?)",
|
||||||
|
"isiii",
|
||||||
|
[$deviceTypeId, $name, $portTypeId, $x, $y]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$sql->set(
|
||||||
|
"INSERT INTO device_type_ports (device_type_id, name, port_type_id, x, y) VALUES (?, ?, NULL, ?, ?)",
|
||||||
|
"isii",
|
||||||
|
[$deviceTypeId, $name, $x, $y]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$_SESSION['success'] = 'Port hinzugefuegt';
|
||||||
|
} else {
|
||||||
|
$_SESSION['error'] = 'Portname darf nicht leer sein';
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Location: ?module=device_types&action=ports&id=' . $deviceTypeId);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($formAction === 'delete_port') {
|
||||||
|
$portId = (int)($_POST['port_id'] ?? 0);
|
||||||
|
if ($portId > 0) {
|
||||||
|
$sql->set(
|
||||||
|
"DELETE FROM device_type_ports WHERE id = ? AND device_type_id = ?",
|
||||||
|
"ii",
|
||||||
|
[$portId, $deviceTypeId]
|
||||||
|
);
|
||||||
|
$_SESSION['success'] = 'Port geloescht';
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Location: ?module=device_types&action=ports&id=' . $deviceTypeId);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$ports = $sql->get(
|
||||||
|
"SELECT dtp.id, dtp.name, dtp.port_type_id, dtp.x, dtp.y, pt.name AS port_type_name
|
||||||
|
FROM device_type_ports dtp
|
||||||
|
LEFT JOIN port_types pt ON pt.id = dtp.port_type_id
|
||||||
|
WHERE dtp.device_type_id = ?
|
||||||
|
ORDER BY dtp.id ASC",
|
||||||
|
'i',
|
||||||
|
[$deviceTypeId]
|
||||||
|
);
|
||||||
|
|
||||||
|
$portTypes = $sql->get("SELECT id, name FROM port_types ORDER BY name", '', []);
|
||||||
|
$svgPath = trim((string)($deviceType['image_path'] ?? ''));
|
||||||
|
$svgUrl = $svgPath !== '' ? '/' . ltrim($svgPath, '/\\') : '';
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<h2>Ports – Gerätetyp</h2>
|
<div class="device-type-ports">
|
||||||
|
<h2>Ports: <?php echo htmlspecialchars((string)$deviceType['name']); ?></h2>
|
||||||
|
|
||||||
<!-- =========================
|
<div class="breadcrumb">
|
||||||
Zurück / Kontext
|
<a href="?module=device_types&action=list">Geraetetypen</a>
|
||||||
========================= -->
|
->
|
||||||
|
<a href="?module=device_types&action=edit&id=<?php echo (int)$deviceType['id']; ?>"><?php echo htmlspecialchars((string)$deviceType['name']); ?></a>
|
||||||
|
-> Ports
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="breadcrumb">
|
<div class="toolbar">
|
||||||
<a href="/?page=device_types/list">Gerätetypen</a>
|
<a class="button" href="?module=device_types&action=edit&id=<?php echo (int)$deviceType['id']; ?>">Zurueck zum Geraetetyp</a>
|
||||||
→
|
</div>
|
||||||
<a href="/?page=device_types/edit&id=<?= $deviceTypeId ?>">
|
|
||||||
<!-- TODO: Gerätetyp-Name -->
|
<form method="post" class="port-form">
|
||||||
Gerätetyp
|
<input type="hidden" name="form_action" value="add_port">
|
||||||
</a>
|
<div>
|
||||||
→
|
<label for="name">Portname</label>
|
||||||
Ports
|
<input id="name" name="name" required placeholder="z. B. Gi1/0/1">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="port_type_id">Port-Typ</label>
|
||||||
|
<select id="port_type_id" name="port_type_id">
|
||||||
|
<option value="0">- keiner -</option>
|
||||||
|
<?php foreach ($portTypes as $portType): ?>
|
||||||
|
<option value="<?php echo (int)$portType['id']; ?>"><?php echo htmlspecialchars((string)$portType['name']); ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="x">X</label>
|
||||||
|
<input id="x" name="x" type="number" value="0">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="y">Y</label>
|
||||||
|
<input id="y" name="y" type="number" value="0">
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="button button-primary">Port hinzufuegen</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<table class="port-list">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Typ</th>
|
||||||
|
<th>X</th>
|
||||||
|
<th>Y</th>
|
||||||
|
<th>Aktionen</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($ports as $port): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?php echo (int)$port['id']; ?></td>
|
||||||
|
<td><?php echo htmlspecialchars((string)$port['name']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars((string)($port['port_type_name'] ?? '-')); ?></td>
|
||||||
|
<td><?php echo (int)$port['x']; ?></td>
|
||||||
|
<td><?php echo (int)$port['y']; ?></td>
|
||||||
|
<td>
|
||||||
|
<form method="post" onsubmit="return confirm('Port wirklich loeschen?');" style="display:inline;">
|
||||||
|
<input type="hidden" name="form_action" value="delete_port">
|
||||||
|
<input type="hidden" name="port_id" value="<?php echo (int)$port['id']; ?>">
|
||||||
|
<button type="submit" class="button button-small button-danger">Loeschen</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<?php if ($svgUrl !== '' && ($deviceType['image_type'] ?? '') === 'svg'): ?>
|
||||||
|
<section class="svg-port-editor-section">
|
||||||
|
<h3>SVG Vorschau</h3>
|
||||||
|
<img src="<?php echo htmlspecialchars($svgUrl); ?>" alt="Geraetetyp SVG" style="max-width:100%; height:auto; border:1px solid #ddd;">
|
||||||
|
</section>
|
||||||
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- =========================
|
|
||||||
Toolbar
|
|
||||||
========================= -->
|
|
||||||
|
|
||||||
<div class="toolbar">
|
|
||||||
<button id="add-port">
|
|
||||||
+ Port hinzufügen
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- TODO: Port-Typen verwalten -->
|
|
||||||
<!-- TODO: Import / Export -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form id="port-form" class="port-form" aria-hidden="true">
|
|
||||||
<div>
|
|
||||||
<label for="port-name">Portname</label>
|
|
||||||
<input id="port-name" name="name" required placeholder="z. B. Gi1/0/1">
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="port-type">Port-Typ</label>
|
|
||||||
<input id="port-type" name="type" required placeholder="RJ45, SFP …">
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="port-medium">Medium</label>
|
|
||||||
<input id="port-medium" name="medium" placeholder="Kupfer, LWL …">
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="port-mode">Modus</label>
|
|
||||||
<input id="port-mode" name="mode" placeholder="Access, Trunk …">
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="port-vlan">VLAN</label>
|
|
||||||
<input id="port-vlan" name="vlan" placeholder="10, 20-30 …">
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="button button-primary">Port hinzufügen</button>
|
|
||||||
<button type="button" class="button" id="cancel-port">Abbrechen</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<!-- =========================
|
|
||||||
Port-Liste
|
|
||||||
========================= -->
|
|
||||||
|
|
||||||
<table class="port-list">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>#</th>
|
|
||||||
<th>Name</th>
|
|
||||||
<th>Typ</th>
|
|
||||||
<th>Medium</th>
|
|
||||||
<th>Modus</th>
|
|
||||||
<th>VLAN</th>
|
|
||||||
<th>Aktionen</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="port-list-body">
|
|
||||||
|
|
||||||
<?php /* foreach ($ports as $port): */ ?>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<!-- TODO: Port-Nummer -->
|
|
||||||
1
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<!-- TODO: Port-Name -->
|
|
||||||
Port 1
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<!-- TODO: Port-Typ (RJ45, SFP, ...) -->
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<!-- TODO: Medium (Kupfer, LWL, BNC, Custom) -->
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<!-- TODO: Modus (Access, Trunk, Custom) -->
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<!-- TODO: VLANs -->
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button>
|
|
||||||
Bearbeiten
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button>
|
|
||||||
Löschen
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<?php /* endforeach; */ ?>
|
|
||||||
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<!-- =========================
|
|
||||||
SVG-Port-Positionierung
|
|
||||||
========================= -->
|
|
||||||
|
|
||||||
<section class="svg-port-editor-section">
|
|
||||||
<h3>Port-Positionen</h3>
|
|
||||||
|
|
||||||
<p class="hint">
|
|
||||||
Ports per Drag & Drop auf dem Gerät platzieren.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="svg-editor-container">
|
|
||||||
<svg
|
|
||||||
id="device-type-svg"
|
|
||||||
viewBox="0 0 800 400"
|
|
||||||
width="100%"
|
|
||||||
height="400"
|
|
||||||
>
|
|
||||||
<!-- TODO: SVG des Gerätetyps laden -->
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- =========================
|
|
||||||
JS-Konfiguration
|
|
||||||
========================= -->
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.port-form {
|
|
||||||
display: none;
|
|
||||||
margin: 20px 0;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 15px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 6px;
|
|
||||||
background: #f9f9f9;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
|
||||||
grid-auto-rows: minmax(50px, auto);
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.port-form.visible {
|
|
||||||
display: grid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.port-form div {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.port-form label {
|
|
||||||
font-size: 0.85rem;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.port-form input {
|
|
||||||
padding: 6px 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid #bbb;
|
|
||||||
font-family: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.port-form button {
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.port-form .button-primary {
|
|
||||||
justify-self: flex-start;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const addPortButton = document.getElementById('add-port');
|
|
||||||
const portForm = document.getElementById('port-form');
|
|
||||||
const portListBody = document.getElementById('port-list-body');
|
|
||||||
const cancelPortButton = document.getElementById('cancel-port');
|
|
||||||
let portCounter = portListBody.querySelectorAll('tr').length + 1;
|
|
||||||
|
|
||||||
function showPortForm(show = true) {
|
|
||||||
portForm.classList.toggle('visible', show);
|
|
||||||
portForm.setAttribute('aria-hidden', show ? 'false' : 'true');
|
|
||||||
if (show) {
|
|
||||||
portForm.querySelector('input').focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addPortButton.addEventListener('click', (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
showPortForm(portForm.getAttribute('aria-hidden') === 'true');
|
|
||||||
});
|
|
||||||
|
|
||||||
cancelPortButton.addEventListener('click', () => {
|
|
||||||
showPortForm(false);
|
|
||||||
portForm.reset();
|
|
||||||
});
|
|
||||||
|
|
||||||
portForm.addEventListener('submit', (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
const data = new FormData(portForm);
|
|
||||||
const row = document.createElement('tr');
|
|
||||||
row.innerHTML = `
|
|
||||||
<td>${portCounter++}</td>
|
|
||||||
<td>${data.get('name') || '-'}</td>
|
|
||||||
<td>${data.get('type') || '-'}</td>
|
|
||||||
<td>${data.get('medium') || '-'}</td>
|
|
||||||
<td>${data.get('mode') || '-'}</td>
|
|
||||||
<td>${data.get('vlan') || '-'}</td>
|
|
||||||
<td>
|
|
||||||
<button type="button">Bearbeiten</button>
|
|
||||||
<button type="button">Löschen</button>
|
|
||||||
</td>
|
|
||||||
`;
|
|
||||||
portListBody.appendChild(row);
|
|
||||||
showPortForm(false);
|
|
||||||
portForm.reset();
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: Replace this mock logic with real AJAX once ports are
|
|
||||||
* persisted on the backend.
|
|
||||||
*/
|
|
||||||
</script>
|
|
||||||
|
|||||||
31
app/modules/floor_infrastructure/delete.php
Normal file
31
app/modules/floor_infrastructure/delete.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* app/modules/floor_infrastructure/delete.php
|
||||||
|
*
|
||||||
|
* Loescht Patchpanels oder Wandbuchsen.
|
||||||
|
*/
|
||||||
|
|
||||||
|
$type = strtolower(trim((string)($_GET['type'] ?? '')));
|
||||||
|
$id = (int)($_GET['id'] ?? 0);
|
||||||
|
|
||||||
|
if ($id <= 0 || !in_array($type, ['patchpanel', 'outlet'], true)) {
|
||||||
|
header('Location: ?module=floor_infrastructure&action=list');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($type === 'patchpanel') {
|
||||||
|
$sql->set(
|
||||||
|
"DELETE FROM floor_patchpanels WHERE id = ?",
|
||||||
|
"i",
|
||||||
|
[$id]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$sql->set(
|
||||||
|
"DELETE FROM network_outlets WHERE id = ?",
|
||||||
|
"i",
|
||||||
|
[$id]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Location: ?module=floor_infrastructure&action=list');
|
||||||
|
exit;
|
||||||
@@ -293,11 +293,8 @@ $mapOutlets = $sql->get(
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php
|
|
||||||
//TODO drag an drop auf der stockwerkskarte für die patchfelder und wandbuchsen. buchsen haben eine einheitliche größe, und sind quadratisch, patchfelder sind auch für sich einheitlich, sind rechteckig und breiter als hoch
|
|
||||||
//TODO style in css files einsortieren
|
|
||||||
?>
|
|
||||||
<script src="/assets/js/floor-infrastructure-edit.js" defer></script>
|
<script src="/assets/js/floor-infrastructure-edit.js" defer></script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -180,6 +180,7 @@ if ($editorFloor) {
|
|||||||
<td><?php echo (int)$panel['port_count']; ?></td>
|
<td><?php echo (int)$panel['port_count']; ?></td>
|
||||||
<td class="actions">
|
<td class="actions">
|
||||||
<a href="?module=floor_infrastructure&action=edit&type=patchpanel&id=<?php echo (int)$panel['id']; ?>" class="button button-small">Bearbeiten</a>
|
<a href="?module=floor_infrastructure&action=edit&type=patchpanel&id=<?php echo (int)$panel['id']; ?>" class="button button-small">Bearbeiten</a>
|
||||||
|
<a href="?module=floor_infrastructure&action=delete&type=patchpanel&id=<?php echo (int)$panel['id']; ?>" class="button button-small button-danger" onclick="return confirm('Patchpanel wirklich loeschen?');">Loeschen</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
@@ -225,6 +226,7 @@ if ($editorFloor) {
|
|||||||
<td><?php echo htmlspecialchars((string)($outlet['comment'] ?? '')); ?></td>
|
<td><?php echo htmlspecialchars((string)($outlet['comment'] ?? '')); ?></td>
|
||||||
<td class="actions">
|
<td class="actions">
|
||||||
<a href="?module=floor_infrastructure&action=edit&type=outlet&id=<?php echo (int)$outlet['id']; ?>" class="button button-small">Bearbeiten</a>
|
<a href="?module=floor_infrastructure&action=edit&type=outlet&id=<?php echo (int)$outlet['id']; ?>" class="button button-small">Bearbeiten</a>
|
||||||
|
<a href="?module=floor_infrastructure&action=delete&type=outlet&id=<?php echo (int)$outlet['id']; ?>" class="button button-small button-danger" onclick="return confirm('Wandbuchse wirklich loeschen?');">Loeschen</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ if ($type === 'patchpanel') {
|
|||||||
$portCount = (int)($_POST['port_count'] ?? 0);
|
$portCount = (int)($_POST['port_count'] ?? 0);
|
||||||
$comment = trim($_POST['comment'] ?? '');
|
$comment = trim($_POST['comment'] ?? '');
|
||||||
|
|
||||||
|
$panelId = $id;
|
||||||
|
|
||||||
if ($id > 0) {
|
if ($id > 0) {
|
||||||
$sql->set(
|
$sql->set(
|
||||||
"UPDATE floor_patchpanels SET name = ?, floor_id = ?, pos_x = ?, pos_y = ?, width = ?, height = ?, port_count = ?, comment = ? WHERE id = ?",
|
"UPDATE floor_patchpanels SET name = ?, floor_id = ?, pos_x = ?, pos_y = ?, width = ?, height = ?, port_count = ?, comment = ? WHERE id = ?",
|
||||||
@@ -28,18 +30,38 @@ if ($type === 'patchpanel') {
|
|||||||
[$name, $floorId, $posX, $posY, $width, $height, $portCount, $comment, $id]
|
[$name, $floorId, $posX, $posY, $width, $height, $portCount, $comment, $id]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$sql->set(
|
$panelId = (int)$sql->set(
|
||||||
"INSERT INTO floor_patchpanels (name, floor_id, pos_x, pos_y, width, height, port_count, comment) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
"INSERT INTO floor_patchpanels (name, floor_id, pos_x, pos_y, width, height, port_count, comment) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||||
"siiiiiss",
|
"siiiiiss",
|
||||||
[$name, $floorId, $posX, $posY, $width, $height, $portCount, $comment]
|
[$name, $floorId, $posX, $posY, $width, $height, $portCount, $comment],
|
||||||
|
true
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($panelId > 0 && $portCount > 0) {
|
||||||
|
$existingCount = (int)($sql->single(
|
||||||
|
"SELECT COUNT(*) AS cnt FROM floor_patchpanel_ports WHERE patchpanel_id = ?",
|
||||||
|
"i",
|
||||||
|
[$panelId]
|
||||||
|
)['cnt'] ?? 0);
|
||||||
|
|
||||||
|
if ($existingCount < $portCount) {
|
||||||
|
for ($i = $existingCount + 1; $i <= $portCount; $i++) {
|
||||||
|
$sql->set(
|
||||||
|
"INSERT INTO floor_patchpanel_ports (patchpanel_id, name) VALUES (?, ?)",
|
||||||
|
"is",
|
||||||
|
[$panelId, 'Port ' . $i]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} elseif ($type === 'outlet') {
|
} elseif ($type === 'outlet') {
|
||||||
$name = trim($_POST['name'] ?? '');
|
$name = trim($_POST['name'] ?? '');
|
||||||
$roomId = (int)($_POST['room_id'] ?? 0);
|
$roomId = (int)($_POST['room_id'] ?? 0);
|
||||||
$x = (int)($_POST['x'] ?? 0);
|
$x = (int)($_POST['x'] ?? 0);
|
||||||
$y = (int)($_POST['y'] ?? 0);
|
$y = (int)($_POST['y'] ?? 0);
|
||||||
$comment = trim($_POST['comment'] ?? '');
|
$comment = trim($_POST['comment'] ?? '');
|
||||||
|
$outletId = $id;
|
||||||
|
|
||||||
if ($id > 0) {
|
if ($id > 0) {
|
||||||
$sql->set(
|
$sql->set(
|
||||||
@@ -48,12 +70,29 @@ if ($type === 'patchpanel') {
|
|||||||
[$name, $roomId, $x, $y, $comment, $id]
|
[$name, $roomId, $x, $y, $comment, $id]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$sql->set(
|
$outletId = (int)$sql->set(
|
||||||
"INSERT INTO network_outlets (name, room_id, x, y, comment) VALUES (?, ?, ?, ?, ?)",
|
"INSERT INTO network_outlets (name, room_id, x, y, comment) VALUES (?, ?, ?, ?, ?)",
|
||||||
"siiis",
|
"siiis",
|
||||||
[$name, $roomId, $x, $y, $comment]
|
[$name, $roomId, $x, $y, $comment],
|
||||||
|
true
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($outletId > 0) {
|
||||||
|
$existingPortCount = (int)($sql->single(
|
||||||
|
"SELECT COUNT(*) AS cnt FROM network_outlet_ports WHERE outlet_id = ?",
|
||||||
|
"i",
|
||||||
|
[$outletId]
|
||||||
|
)['cnt'] ?? 0);
|
||||||
|
|
||||||
|
if ($existingPortCount === 0) {
|
||||||
|
$sql->set(
|
||||||
|
"INSERT INTO network_outlet_ports (outlet_id, name) VALUES (?, 'Port 1')",
|
||||||
|
"i",
|
||||||
|
[$outletId]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
header('Location: ?module=floor_infrastructure&action=list');
|
header('Location: ?module=floor_infrastructure&action=list');
|
||||||
|
|||||||
36
app/modules/floors/delete.php
Normal file
36
app/modules/floors/delete.php
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* app/modules/floors/delete.php
|
||||||
|
*/
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
http_response_code(405);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Methode nicht erlaubt']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = (int)($_POST['id'] ?? $_GET['id'] ?? 0);
|
||||||
|
if ($id <= 0) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'ID fehlt']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$exists = $sql->single("SELECT id FROM floors WHERE id = ?", "i", [$id]);
|
||||||
|
if (!$exists) {
|
||||||
|
http_response_code(404);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Stockwerk nicht gefunden']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$rows = $sql->set("DELETE FROM floors WHERE id = ?", "i", [$id]);
|
||||||
|
if ($rows === false) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Loeschen fehlgeschlagen']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'message' => 'Stockwerk geloescht']);
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
// =========================
|
// =========================
|
||||||
// Filter einlesen
|
// Filter einlesen
|
||||||
// =========================
|
// =========================
|
||||||
$search = trim($_GET['search'] ?? '');
|
$search = trim($_GET['search'] ?? ');
|
||||||
|
|
||||||
// =========================
|
// =========================
|
||||||
// Floors laden
|
// Floors laden
|
||||||
@@ -19,7 +19,7 @@ $whereClause = "";
|
|||||||
$types = "";
|
$types = "";
|
||||||
$params = [];
|
$params = [];
|
||||||
|
|
||||||
if ($search !== '') {
|
if ($search !== ') {
|
||||||
$whereClause = "WHERE f.name LIKE ? OR f.comment LIKE ?";
|
$whereClause = "WHERE f.name LIKE ? OR f.comment LIKE ?";
|
||||||
$types = "ss";
|
$types = "ss";
|
||||||
$params = ["%$search%", "%$search%"];
|
$params = ["%$search%", "%$search%"];
|
||||||
@@ -105,7 +105,7 @@ $buildings = $sql->get("SELECT id, name FROM buildings ORDER BY name", "", []);
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<small><?php echo htmlspecialchars($floor['comment'] ?? ''); ?></small>
|
<small><?php echo htmlspecialchars($floor['comment'] ?? '); ?></small>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="actions">
|
<td class="actions">
|
||||||
@@ -233,9 +233,24 @@ $buildings = $sql->get("SELECT id, name FROM buildings ORDER BY name", "", []);
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
function confirmDelete(id) {
|
function confirmDelete(id) {
|
||||||
if (confirm('Dieses Stockwerk wirklich löschen? Alle Räume und Racks werden gelöscht.')) {
|
if (confirm('Dieses Stockwerk wirklich loeschen? Alle Raeume und Racks werden geloescht.')) {
|
||||||
// TODO: AJAX-Delete implementieren
|
fetch('?module=floors&action=delete', {
|
||||||
alert('Löschen noch nicht implementiert');
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
|
||||||
|
body: 'id=' + encodeURIComponent(id)
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
if (data && data.success) {
|
||||||
|
window.location.reload();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
alert((data && data.message) ? data.message : 'Loeschen fehlgeschlagen');
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
alert('Loeschen fehlgeschlagen');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -42,14 +42,14 @@ $pageTitle = $isEdit ? "Standort bearbeiten: " . htmlspecialchars($location['nam
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="name">Name <span class="required">*</span></label>
|
<label for="name">Name <span class="required">*</span></label>
|
||||||
<input type="text" id="name" name="name" required
|
<input type="text" id="name" name="name" required
|
||||||
value="<?php echo htmlspecialchars($location['name'] ?? ''); ?>"
|
value="<?php echo htmlspecialchars($location['name'] ?? '); ?>"
|
||||||
placeholder="z.B. Hauptgebäude, Campus A, Außenstelle Berlin">
|
placeholder="z.B. Hauptgebäude, Campus A, Außenstelle Berlin">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="comment">Beschreibung</label>
|
<label for="comment">Beschreibung</label>
|
||||||
<textarea id="comment" name="comment" rows="3"
|
<textarea id="comment" name="comment" rows="3"
|
||||||
placeholder="Adresse, Kontaktinformationen, Besonderheiten"><?php echo htmlspecialchars($location['comment'] ?? ''); ?></textarea>
|
placeholder="Adresse, Kontaktinformationen, Besonderheiten"><?php echo htmlspecialchars($location['comment'] ?? '); ?></textarea>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
@@ -153,9 +153,24 @@ $pageTitle = $isEdit ? "Standort bearbeiten: " . htmlspecialchars($location['nam
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
function confirmDelete(id) {
|
function confirmDelete(id) {
|
||||||
if (confirm('Diesen Standort wirklich löschen? Alle Gebäude werden gelöscht.')) {
|
if (confirm('Diesen Standort wirklich loeschen? Alle Gebaeude werden geloescht.')) {
|
||||||
// TODO: AJAX-Delete implementieren
|
fetch('?module=locations&action=delete', {
|
||||||
alert('Löschen noch nicht implementiert');
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
|
||||||
|
body: 'id=' + encodeURIComponent(id)
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
if (data && data.success) {
|
||||||
|
window.location.href = '?module=locations&action=list';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
alert((data && data.message) ? data.message : 'Loeschen fehlgeschlagen');
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
alert('Loeschen fehlgeschlagen');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
36
app/modules/racks/delete.php
Normal file
36
app/modules/racks/delete.php
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* app/modules/racks/delete.php
|
||||||
|
*/
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
http_response_code(405);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Methode nicht erlaubt']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = (int)($_POST['id'] ?? $_GET['id'] ?? 0);
|
||||||
|
if ($id <= 0) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'ID fehlt']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$exists = $sql->single("SELECT id FROM racks WHERE id = ?", "i", [$id]);
|
||||||
|
if (!$exists) {
|
||||||
|
http_response_code(404);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Rack nicht gefunden']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$rows = $sql->set("DELETE FROM racks WHERE id = ?", "i", [$id]);
|
||||||
|
if ($rows === false) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Loeschen fehlgeschlagen']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'message' => 'Rack geloescht']);
|
||||||
|
|
||||||
@@ -1,16 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* app/modules/racks/edit.php
|
* app/modules/racks/edit.php
|
||||||
*
|
|
||||||
* Rack anlegen oder bearbeiten
|
|
||||||
* - Name, Beschreibung
|
|
||||||
* - Zugehöriges Stockwerk (Floor)
|
|
||||||
* - Höhe in Höheneinheiten (HE)
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// =========================
|
|
||||||
// Kontext bestimmen
|
|
||||||
// =========================
|
|
||||||
$rackId = (int)($_GET['id'] ?? 0);
|
$rackId = (int)($_GET['id'] ?? 0);
|
||||||
$rack = null;
|
$rack = null;
|
||||||
|
|
||||||
@@ -23,83 +15,61 @@ if ($rackId > 0) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$isEdit = !empty($rack);
|
$isEdit = !empty($rack);
|
||||||
$pageTitle = $isEdit ? "Rack bearbeiten: " . htmlspecialchars($rack['name']) : "Neues Rack";
|
$pageTitle = $isEdit ? 'Rack bearbeiten: ' . htmlspecialchars((string)$rack['name']) : 'Neues Rack';
|
||||||
|
$floors = $sql->get("SELECT id, name FROM floors ORDER BY name", '', []);
|
||||||
// =========================
|
|
||||||
// Floors laden
|
|
||||||
// =========================
|
|
||||||
$floors = $sql->get("SELECT id, name FROM floors ORDER BY name", "", []);
|
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="rack-edit">
|
<div class="rack-edit">
|
||||||
<h1><?php echo $pageTitle; ?></h1>
|
<h1><?php echo $pageTitle; ?></h1>
|
||||||
|
|
||||||
<form method="post" action="?module=racks&action=save" class="edit-form">
|
<form method="post" action="?module=racks&action=save" class="edit-form">
|
||||||
|
|
||||||
<?php if ($isEdit): ?>
|
<?php if ($isEdit): ?>
|
||||||
<input type="hidden" name="id" value="<?php echo $rackId; ?>">
|
<input type="hidden" name="id" value="<?php echo (int)$rackId; ?>">
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<!-- =========================
|
|
||||||
Basisdaten
|
|
||||||
========================= -->
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Allgemein</legend>
|
<legend>Allgemein</legend>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="name">Name <span class="required">*</span></label>
|
<label for="name">Name <span class="required">*</span></label>
|
||||||
<input type="text" id="name" name="name" required
|
<input type="text" id="name" name="name" required value="<?php echo htmlspecialchars((string)($rack['name'] ?? '')); ?>" placeholder="z.B. Rack A1">
|
||||||
value="<?php echo htmlspecialchars($rack['name'] ?? ''); ?>"
|
|
||||||
placeholder="z.B. Rack A1">
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="comment">Beschreibung</label>
|
<label for="comment">Beschreibung</label>
|
||||||
<textarea id="comment" name="comment" rows="3"
|
<textarea id="comment" name="comment" rows="3" placeholder="z.B. Standort, Besonderheiten"><?php echo htmlspecialchars((string)($rack['comment'] ?? '')); ?></textarea>
|
||||||
placeholder="z.B. Standort, Besonderheiten"><?php echo htmlspecialchars($rack['comment'] ?? ''); ?></textarea>
|
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<!-- =========================
|
|
||||||
Standort & Höhe
|
|
||||||
========================= -->
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Standort & Größe</legend>
|
<legend>Standort und Groesse</legend>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="floor_id">Stockwerk <span class="required">*</span></label>
|
<label for="floor_id">Stockwerk <span class="required">*</span></label>
|
||||||
<select id="floor_id" name="floor_id" required>
|
<select id="floor_id" name="floor_id" required>
|
||||||
<option value="">- Wählen -</option>
|
<option value="">- Waehlen -</option>
|
||||||
<?php foreach ($floors as $floor): ?>
|
<?php foreach ($floors as $floor): ?>
|
||||||
<option value="<?php echo $floor['id']; ?>"
|
<option value="<?php echo (int)$floor['id']; ?>" <?php echo ((int)($rack['floor_id'] ?? 0) === (int)$floor['id']) ? 'selected' : ''; ?>>
|
||||||
<?php echo ($rack['floor_id'] ?? 0) == $floor['id'] ? 'selected' : ''; ?>>
|
<?php echo htmlspecialchars((string)$floor['name']); ?>
|
||||||
<?php echo htmlspecialchars($floor['name']); ?>
|
</option>
|
||||||
</option>
|
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="height_he">Höhe in Höheneinheiten (HE) <span class="required">*</span></label>
|
<label for="height_he">Hoehe in Hoeheneinheiten (HE) <span class="required">*</span></label>
|
||||||
<input type="number" id="height_he" name="height_he" required min="1" max="100"
|
<input type="number" id="height_he" name="height_he" required min="1" max="100" value="<?php echo (int)($rack['height_he'] ?? 42); ?>">
|
||||||
value="<?php echo htmlspecialchars($rack['height_he'] ?? '42'); ?>"
|
<small>Standard: 42 HE</small>
|
||||||
placeholder="z.B. 42">
|
|
||||||
<small>Standard: 42 HE (ca. 2 Meter)</small>
|
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<!-- =========================
|
|
||||||
Aktionen
|
|
||||||
========================= -->
|
|
||||||
<fieldset class="form-actions">
|
<fieldset class="form-actions">
|
||||||
<button type="submit" class="button button-primary">Speichern</button>
|
<button type="submit" class="button button-primary">Speichern</button>
|
||||||
<a href="?module=racks&action=list" class="button">Abbrechen</a>
|
<a href="?module=racks&action=list" class="button">Abbrechen</a>
|
||||||
<?php if ($isEdit): ?>
|
<?php if ($isEdit): ?>
|
||||||
<a href="#" class="button button-danger" onclick="confirmDelete(<?php echo $rackId; ?>)">Löschen</a>
|
<a href="#" class="button button-danger" onclick="return confirmDelete(<?php echo (int)$rackId; ?>)">Loeschen</a>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -197,60 +167,25 @@ $floors = $sql->get("SELECT id, name FROM floors ORDER BY name", "", []);
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
function confirmDelete(id) {
|
function confirmDelete(id) {
|
||||||
if (confirm('Dieses Rack wirklich löschen? Alle Geräte werden aus dem Rack entfernt.')) {
|
if (!confirm('Dieses Rack wirklich loeschen? Alle Geraete werden aus dem Rack entfernt.')) {
|
||||||
// TODO: AJAX-Delete implementieren
|
return false;
|
||||||
alert('Löschen noch nicht implementiert');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fetch('?module=racks&action=delete', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
|
||||||
|
body: 'id=' + encodeURIComponent(id)
|
||||||
|
})
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => {
|
||||||
|
if (data && data.success) {
|
||||||
|
window.location.href = '?module=racks&action=list';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
alert((data && data.message) ? data.message : 'Loeschen fehlgeschlagen');
|
||||||
|
})
|
||||||
|
.catch(() => alert('Loeschen fehlgeschlagen'));
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- =========================
|
|
||||||
Rack-SVG / Gerätepositionen
|
|
||||||
========================= -->
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<legend>Rack-Layout</legend>
|
|
||||||
|
|
||||||
<div class="svg-editor-container">
|
|
||||||
<svg
|
|
||||||
id="rack-svg"
|
|
||||||
viewBox="0 0 200 1000"
|
|
||||||
width="100%"
|
|
||||||
height="600"
|
|
||||||
>
|
|
||||||
<!-- TODO: Rack-SVG laden -->
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="hint">
|
|
||||||
Geräte per Drag & Drop im Rack positionieren.
|
|
||||||
</p>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<!-- =========================
|
|
||||||
Aktionen
|
|
||||||
========================= -->
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<button type="submit">Speichern</button>
|
|
||||||
<button type="button" onclick="history.back()">Abbrechen</button>
|
|
||||||
<!-- TODO: Löschen, falls edit -->
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<!-- =========================
|
|
||||||
JS-Konfiguration
|
|
||||||
========================= -->
|
|
||||||
|
|
||||||
<script>
|
|
||||||
/**
|
|
||||||
* Konfiguration für Rack-SVG-Editor
|
|
||||||
*/
|
|
||||||
|
|
||||||
// TODO: Rack-ID aus PHP setzen
|
|
||||||
// window.RACK_ID = <?= (int)$rackId ?>;
|
|
||||||
|
|
||||||
// TODO: Gerätepositionen an JS übergeben
|
|
||||||
// window.RACK_DEVICES = <?= json_encode($rackDevices ?? []) ?>;
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -2,44 +2,30 @@
|
|||||||
/**
|
/**
|
||||||
* app/modules/racks/list.php
|
* app/modules/racks/list.php
|
||||||
*
|
*
|
||||||
* Übersicht aller Racks
|
* Uebersicht aller Racks.
|
||||||
* - Anzeigen, Bearbeiten, Löschen
|
|
||||||
* - Zugehöriges Stockwerk anzeigen
|
|
||||||
* - Gerätecount
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// =========================
|
$search = trim((string)($_GET['search'] ?? ''));
|
||||||
// Filter einlesen
|
|
||||||
// =========================
|
|
||||||
$search = trim($_GET['search'] ?? '');
|
|
||||||
$floorId = (int)($_GET['floor_id'] ?? 0);
|
$floorId = (int)($_GET['floor_id'] ?? 0);
|
||||||
|
|
||||||
//TODO racks beim editieren auf der stockwerkkarte platzieren und verschieben können
|
|
||||||
|
|
||||||
// =========================
|
|
||||||
// WHERE-Clause bauen
|
|
||||||
// =========================
|
|
||||||
$where = [];
|
$where = [];
|
||||||
$types = '';
|
$types = '';
|
||||||
$params = [];
|
$params = [];
|
||||||
|
|
||||||
if ($search !== '') {
|
if ($search !== '') {
|
||||||
$where[] = "r.name LIKE ?";
|
$where[] = 'r.name LIKE ?';
|
||||||
$types .= "s";
|
$types .= 's';
|
||||||
$params[] = "%$search%";
|
$params[] = "%$search%";
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($floorId > 0) {
|
if ($floorId > 0) {
|
||||||
$where[] = "r.floor_id = ?";
|
$where[] = 'r.floor_id = ?';
|
||||||
$types .= "i";
|
$types .= 'i';
|
||||||
$params[] = $floorId;
|
$params[] = $floorId;
|
||||||
}
|
}
|
||||||
|
|
||||||
$whereSql = $where ? "WHERE " . implode(" AND ", $where) : "";
|
$whereSql = $where ? ('WHERE ' . implode(' AND ', $where)) : '';
|
||||||
|
|
||||||
// =========================
|
|
||||||
// Racks laden
|
|
||||||
// =========================
|
|
||||||
$racks = $sql->get(
|
$racks = $sql->get(
|
||||||
"SELECT r.*, f.name AS floor_name, COUNT(d.id) AS device_count
|
"SELECT r.*, f.name AS floor_name, COUNT(d.id) AS device_count
|
||||||
FROM racks r
|
FROM racks r
|
||||||
@@ -52,212 +38,111 @@ $racks = $sql->get(
|
|||||||
$params
|
$params
|
||||||
);
|
);
|
||||||
|
|
||||||
// =========================
|
$floors = $sql->get('SELECT id, name FROM floors ORDER BY name', '', []);
|
||||||
// Filter-Daten laden
|
|
||||||
// =========================
|
|
||||||
$floors = $sql->get("SELECT id, name FROM floors ORDER BY name", "", []);
|
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="racks-container">
|
<div class="racks-container">
|
||||||
<h1>Racks</h1>
|
<h1>Racks</h1>
|
||||||
|
|
||||||
<!-- =========================
|
|
||||||
Toolbar
|
|
||||||
========================= -->
|
|
||||||
<div class="filter-form">
|
<div class="filter-form">
|
||||||
<form method="GET" style="display: flex; gap: 10px; flex-wrap: wrap; align-items: center;">
|
<form method="GET" style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;">
|
||||||
<input type="hidden" name="module" value="racks">
|
<input type="hidden" name="module" value="racks">
|
||||||
<input type="hidden" name="action" value="list">
|
<input type="hidden" name="action" value="list">
|
||||||
|
|
||||||
<input type="text" name="search" placeholder="Suche nach Name…"
|
<input type="text" name="search" placeholder="Suche nach Name..." value="<?php echo htmlspecialchars($search); ?>" class="search-input">
|
||||||
value="<?php echo htmlspecialchars($search); ?>" class="search-input">
|
|
||||||
|
|
||||||
<select name="floor_id">
|
<select name="floor_id">
|
||||||
<option value="">- Alle Stockwerke -</option>
|
<option value="">- Alle Stockwerke -</option>
|
||||||
<?php foreach ($floors as $floor): ?>
|
<?php foreach ($floors as $floor): ?>
|
||||||
<option value="<?php echo $floor['id']; ?>"
|
<option value="<?php echo (int)$floor['id']; ?>" <?php echo ((int)$floor['id'] === $floorId) ? 'selected' : ''; ?>>
|
||||||
<?php echo $floor['id'] === $floorId ? 'selected' : ''; ?>>
|
<?php echo htmlspecialchars((string)$floor['name']); ?>
|
||||||
<?php echo htmlspecialchars($floor['name']); ?>
|
</option>
|
||||||
</option>
|
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<button type="submit" class="button">Filter</button>
|
<button type="submit" class="button">Filter</button>
|
||||||
<a href="?module=racks&action=list" class="button">Reset</a>
|
<a href="?module=racks&action=list" class="button">Reset</a>
|
||||||
<a href="?module=racks&action=edit" class="button button-primary" style="margin-left: auto;">+ Neues Rack</a>
|
<a href="?module=racks&action=edit" class="button button-primary" style="margin-left:auto;">+ Neues Rack</a>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- =========================
|
|
||||||
Racks-Tabelle
|
|
||||||
========================= -->
|
|
||||||
<?php if (!empty($racks)): ?>
|
<?php if (!empty($racks)): ?>
|
||||||
<table class="rack-list">
|
<table class="rack-list">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Stockwerk</th>
|
<th>Stockwerk</th>
|
||||||
<th>Höhe (HE)</th>
|
<th>Hoehe (HE)</th>
|
||||||
<th>Geräte</th>
|
<th>Geraete</th>
|
||||||
<th>Beschreibung</th>
|
<th>Beschreibung</th>
|
||||||
<th>Aktionen</th>
|
<th>Aktionen</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<?php foreach ($racks as $rack): ?>
|
<?php foreach ($racks as $rack): ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td><strong><?php echo htmlspecialchars((string)$rack['name']); ?></strong></td>
|
||||||
<strong><?php echo htmlspecialchars($rack['name']); ?></strong>
|
<td><?php echo htmlspecialchars((string)($rack['floor_name'] ?? '-')); ?></td>
|
||||||
</td>
|
<td><?php echo (int)$rack['height_he']; ?> HE</td>
|
||||||
|
<td><?php echo (int)$rack['device_count']; ?></td>
|
||||||
<td>
|
<td><small><?php echo htmlspecialchars((string)($rack['comment'] ?? '')); ?></small></td>
|
||||||
<?php echo htmlspecialchars($rack['floor_name'] ?? '—'); ?>
|
<td class="actions">
|
||||||
</td>
|
<a href="?module=racks&action=edit&id=<?php echo (int)$rack['id']; ?>" class="button button-small">Bearbeiten</a>
|
||||||
|
<a href="#" class="button button-small button-danger" onclick="return confirmDelete(<?php echo (int)$rack['id']; ?>)">Loeschen</a>
|
||||||
<td>
|
</td>
|
||||||
<?php echo $rack['height_he']; ?> HE
|
</tr>
|
||||||
</td>
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
<td>
|
</table>
|
||||||
<?php echo $rack['device_count']; ?>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td>
|
|
||||||
<small><?php echo htmlspecialchars($rack['comment'] ?? ''); ?></small>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td class="actions">
|
|
||||||
<a href="?module=racks&action=edit&id=<?php echo $rack['id']; ?>" class="button button-small">Bearbeiten</a>
|
|
||||||
<a href="#" class="button button-small button-danger" onclick="confirmDelete(<?php echo $rack['id']; ?>)">Löschen</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<div class="empty-state">
|
<div class="empty-state">
|
||||||
<p>Keine Racks gefunden.</p>
|
<p>Keine Racks gefunden.</p>
|
||||||
<p>
|
<p><a href="?module=racks&action=edit" class="button button-primary">Erstes Rack anlegen</a></p>
|
||||||
<a href="?module=racks&action=edit" class="button button-primary">
|
</div>
|
||||||
Erstes Rack anlegen
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.racks-container {
|
.racks-container { padding: 20px; max-width: 1000px; margin: 0 auto; }
|
||||||
padding: 20px;
|
.filter-form { margin: 20px 0; }
|
||||||
max-width: 1000px;
|
.filter-form input, .filter-form select { padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; }
|
||||||
margin: 0 auto;
|
.search-input { flex: 1; min-width: 250px; }
|
||||||
}
|
.rack-list { width: 100%; border-collapse: collapse; margin: 15px 0; }
|
||||||
|
.rack-list th { background: #f5f5f5; padding: 12px; text-align: left; border-bottom: 2px solid #ddd; font-weight: bold; }
|
||||||
.filter-form {
|
.rack-list td { padding: 12px; border-bottom: 1px solid #ddd; }
|
||||||
margin: 20px 0;
|
.rack-list tr:hover { background: #f9f9f9; }
|
||||||
}
|
.actions { white-space: nowrap; }
|
||||||
|
.button { display: inline-block; padding: 8px 12px; background: #007bff; color: #fff; text-decoration: none; border-radius: 4px; border: none; cursor: pointer; font-size: 0.9em; }
|
||||||
.filter-form form {
|
.button:hover { background: #0056b3; }
|
||||||
display: flex;
|
.button-primary { background: #28a745; }
|
||||||
gap: 10px;
|
.button-primary:hover { background: #218838; }
|
||||||
flex-wrap: wrap;
|
.button-small { padding: 4px 8px; font-size: 0.85em; }
|
||||||
align-items: center;
|
.button-danger { background: #dc3545; }
|
||||||
}
|
.button-danger:hover { background: #c82333; }
|
||||||
|
.empty-state { text-align: center; padding: 40px 20px; background: #f9f9f9; border: 1px solid #eee; border-radius: 8px; }
|
||||||
.filter-form input,
|
|
||||||
.filter-form select {
|
|
||||||
padding: 8px 12px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-input {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 250px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rack-list {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
margin: 15px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rack-list th {
|
|
||||||
background: #f5f5f5;
|
|
||||||
padding: 12px;
|
|
||||||
text-align: left;
|
|
||||||
border-bottom: 2px solid #ddd;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rack-list td {
|
|
||||||
padding: 12px;
|
|
||||||
border-bottom: 1px solid #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rack-list tr:hover {
|
|
||||||
background: #f9f9f9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 8px 12px;
|
|
||||||
background: #007bff;
|
|
||||||
color: white;
|
|
||||||
text-decoration: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button:hover {
|
|
||||||
background: #0056b3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-primary {
|
|
||||||
background: #28a745;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-primary:hover {
|
|
||||||
background: #218838;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-small {
|
|
||||||
padding: 4px 8px;
|
|
||||||
font-size: 0.85em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-danger {
|
|
||||||
background: #dc3545;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-danger:hover {
|
|
||||||
background: #c82333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-state {
|
|
||||||
text-align: center;
|
|
||||||
padding: 40px 20px;
|
|
||||||
background: #f9f9f9;
|
|
||||||
border: 1px solid #eee;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function confirmDelete(id) {
|
function confirmDelete(id) {
|
||||||
if (confirm('Dieses Rack wirklich löschen?')) {
|
if (!confirm('Dieses Rack wirklich loeschen?')) {
|
||||||
// TODO: AJAX-Delete implementieren
|
return false;
|
||||||
alert('Löschen noch nicht implementiert');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fetch('?module=racks&action=delete', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
|
||||||
|
body: 'id=' + encodeURIComponent(id)
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
if (data && data.success) {
|
||||||
|
window.location.reload();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
alert((data && data.message) ? data.message : 'Loeschen fehlgeschlagen');
|
||||||
|
})
|
||||||
|
.catch(() => alert('Loeschen fehlgeschlagen'));
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</div>
|
|
||||||
<?php /* endif; */ ?>
|
|
||||||
|
|||||||
@@ -1,19 +1,32 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* footer.php
|
* footer.php
|
||||||
*
|
|
||||||
* HTML-Footer, Scripts, evtl. Modale oder Notifications
|
|
||||||
* Wird am Ende jeder Seite eingebunden
|
|
||||||
*/
|
*/
|
||||||
?>
|
?>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<p>© <?php echo date('Y'); ?> Troy Grunt - NDT</p>
|
<p>© <?php echo date('Y'); ?> Troy Grunt - NDT</p>
|
||||||
|
<p class="footer-meta">
|
||||||
<!-- TODO: Optional: Statusanzeige, Debug-Info, Session-Hinweis -->
|
Umgebung: <?php echo defined('APP_ENV') ? htmlspecialchars(APP_ENV) : 'unknown'; ?>
|
||||||
|
| Session: <?php echo session_id() !== '' ? 'aktiv' : 'inaktiv'; ?>
|
||||||
|
</p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<!-- TODO: evtl. JS für modale Fenster oder Flash Messages -->
|
<?php if (!empty($_SESSION['success']) || !empty($_SESSION['error'])): ?>
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
const success = <?php echo json_encode($_SESSION['success'] ?? '', JSON_UNESCAPED_UNICODE); ?>;
|
||||||
|
const error = <?php echo json_encode($_SESSION['error'] ?? '', JSON_UNESCAPED_UNICODE); ?>;
|
||||||
|
if (success) {
|
||||||
|
alert(success);
|
||||||
|
}
|
||||||
|
if (error) {
|
||||||
|
alert(error);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
<?php unset($_SESSION['success'], $_SESSION['error']); ?>
|
||||||
|
<?php endif; ?>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -10,7 +10,10 @@
|
|||||||
<html lang="de">
|
<html lang="de">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="description" content="Netwatch - Netzwerk-Dokumentation und Verkabelungsverwaltung">
|
||||||
<title>Netzwerk-Dokumentation</title>
|
<title>Netzwerk-Dokumentation</title>
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/assets/icons/favicon.svg">
|
||||||
|
|
||||||
<!-- CSS -->
|
<!-- CSS -->
|
||||||
<link rel="stylesheet" href="/assets/css/app.css">
|
<link rel="stylesheet" href="/assets/css/app.css">
|
||||||
@@ -21,7 +24,6 @@
|
|||||||
<script src="/assets/js/svg-editor.js" defer></script>
|
<script src="/assets/js/svg-editor.js" defer></script>
|
||||||
<script src="/assets/js/network-view.js" defer></script>
|
<script src="/assets/js/network-view.js" defer></script>
|
||||||
|
|
||||||
<!-- TODO: Meta-Tags, Favicon -->
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header class="app-header">
|
<header class="app-header">
|
||||||
|
|||||||
@@ -2,25 +2,18 @@
|
|||||||
/**
|
/**
|
||||||
* layout.php
|
* layout.php
|
||||||
*
|
*
|
||||||
* Grundlayout: Header + Content + Footer
|
* Basislayout fuer 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'; ?>
|
<?php include __DIR__ . '/header.php'; ?>
|
||||||
|
|
||||||
<div class="content-wrapper">
|
<div class="content-wrapper">
|
||||||
<!-- TODO: Dynamischen Content hier einfügen -->
|
|
||||||
<?php
|
<?php
|
||||||
if (isset($content)) {
|
if (isset($content)) {
|
||||||
echo $content;
|
echo $content;
|
||||||
} else {
|
} else {
|
||||||
echo "<p>Inhalt fehlt</p>";
|
echo '<p>Kein Inhalt uebergeben.</p>';
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
11
init.sql
11
init.sql
@@ -44,9 +44,9 @@ INSERT INTO `buildings` (`id`, `location_id`, `name`, `comment`) VALUES
|
|||||||
CREATE TABLE `connections` (
|
CREATE TABLE `connections` (
|
||||||
`id` int(11) NOT NULL,
|
`id` int(11) NOT NULL,
|
||||||
`connection_type_id` int(11) NOT NULL,
|
`connection_type_id` int(11) NOT NULL,
|
||||||
`port_a_type` enum('device','module','outlet') NOT NULL,
|
`port_a_type` enum('device','module','outlet','patchpanel') NOT NULL,
|
||||||
`port_a_id` int(11) NOT NULL,
|
`port_a_id` int(11) NOT NULL,
|
||||||
`port_b_type` enum('device','module','outlet') NOT NULL,
|
`port_b_type` enum('device','module','outlet','patchpanel') NOT NULL,
|
||||||
`port_b_id` int(11) NOT NULL,
|
`port_b_id` int(11) NOT NULL,
|
||||||
`vlan_config` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`vlan_config`)),
|
`vlan_config` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`vlan_config`)),
|
||||||
`mode` varchar(50) DEFAULT NULL,
|
`mode` varchar(50) DEFAULT NULL,
|
||||||
@@ -70,6 +70,13 @@ CREATE TABLE `connection_types` (
|
|||||||
`comment` text DEFAULT NULL
|
`comment` text DEFAULT NULL
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Daten fuer Tabelle `connection_types`
|
||||||
|
--
|
||||||
|
|
||||||
|
INSERT INTO `connection_types` (`id`, `name`, `medium`, `duplex`, `max_speed`, `color`, `line_style`, `comment`) VALUES
|
||||||
|
(1, 'Default', 'copper', 'custom', NULL, NULL, 'solid', 'Auto-created default type');
|
||||||
|
|
||||||
-- --------------------------------------------------------
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
--
|
--
|
||||||
|
|||||||
Reference in New Issue
Block a user