feat: complete package 2 delete flows and package 3 port management
This commit is contained in:
@@ -9,15 +9,15 @@
|
|||||||
- [x] Validierungsfehler anzeigen
|
- [x] Validierungsfehler anzeigen
|
||||||
|
|
||||||
### Package 2: Delete-Funktionen (1h)
|
### Package 2: Delete-Funktionen (1h)
|
||||||
- [ ] DELETE-Endpoints für alle Module
|
- [x] DELETE-Endpoints für alle Module
|
||||||
- [ ] AJAX-Bestätigung vor Löschen
|
- [x] AJAX-Bestätigung vor Löschen
|
||||||
- [ ] Kaskadierendes Löschen prüfen (z.B. Floor → Racks)
|
- [x] Kaskadierendes Löschen prüfen (z.B. Floor → Racks)
|
||||||
|
|
||||||
### Package 3: Port-Management (2-3h)
|
### Package 3: Port-Management (2-3h)
|
||||||
- [ ] Ports zu Device-Types verwalten
|
- [x] Ports zu Device-Types verwalten
|
||||||
- [ ] Ports zu Devices anzeigen
|
- [x] Ports zu Devices anzeigen
|
||||||
- [ ] Port-Status (aktiv/inaktiv)
|
- [x] Port-Status (aktiv/inaktiv)
|
||||||
- [ ] VLAN-Zuordnung zu Ports
|
- [x] VLAN-Zuordnung zu Ports
|
||||||
|
|
||||||
### Package 4: SVG-Editor für Floorplans (4-5h)
|
### Package 4: SVG-Editor für Floorplans (4-5h)
|
||||||
- [ ] Interaktiver SVG-Editor für Rooms
|
- [ ] Interaktiver SVG-Editor für Rooms
|
||||||
|
|||||||
@@ -2,12 +2,19 @@
|
|||||||
/**
|
/**
|
||||||
* app/modules/connections/delete.php
|
* app/modules/connections/delete.php
|
||||||
*
|
*
|
||||||
* Loescht eine Verbindung und leitet zur Liste zurueck.
|
* Loescht eine Verbindung (AJAX-POST bevorzugt, GET-Fallback fuer Redirects).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$connectionId = (int)($_GET['id'] ?? $_POST['id'] ?? 0);
|
$isPost = ($_SERVER['REQUEST_METHOD'] ?? '') === 'POST';
|
||||||
|
$connectionId = (int)($_POST['id'] ?? $_GET['id'] ?? 0);
|
||||||
|
|
||||||
if ($connectionId <= 0) {
|
if ($connectionId <= 0) {
|
||||||
|
if ($isPost) {
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Ungueltige Verbindungs-ID']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
$_SESSION['error'] = 'Ungueltige Verbindungs-ID';
|
$_SESSION['error'] = 'Ungueltige Verbindungs-ID';
|
||||||
header('Location: ?module=connections&action=list');
|
header('Location: ?module=connections&action=list');
|
||||||
exit;
|
exit;
|
||||||
@@ -20,6 +27,12 @@ $connection = $sql->single(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!$connection) {
|
if (!$connection) {
|
||||||
|
if ($isPost) {
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
http_response_code(404);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Verbindung nicht gefunden']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
$_SESSION['error'] = 'Verbindung nicht gefunden';
|
$_SESSION['error'] = 'Verbindung nicht gefunden';
|
||||||
header('Location: ?module=connections&action=list');
|
header('Location: ?module=connections&action=list');
|
||||||
exit;
|
exit;
|
||||||
@@ -31,6 +44,17 @@ $rows = $sql->set(
|
|||||||
[$connectionId]
|
[$connectionId]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if ($isPost) {
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
if ($rows > 0) {
|
||||||
|
echo json_encode(['success' => true, 'message' => 'Verbindung geloescht']);
|
||||||
|
} else {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Verbindung konnte nicht geloescht werden']);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
if ($rows > 0) {
|
if ($rows > 0) {
|
||||||
$_SESSION['success'] = 'Verbindung geloescht';
|
$_SESSION['success'] = 'Verbindung geloescht';
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -335,11 +335,12 @@ $buildListUrl = static function (array $extra = []) use ($search, $deviceId): st
|
|||||||
<a href="<?php echo htmlspecialchars($buildListUrl(['connection_id' => $connId])); ?>" class="button button-small">Details</a>
|
<a href="<?php echo htmlspecialchars($buildListUrl(['connection_id' => $connId])); ?>" class="button button-small">Details</a>
|
||||||
<a href="?module=connections&action=edit&id=<?php echo $connId; ?>" class="button button-small">Bearbeiten</a>
|
<a href="?module=connections&action=edit&id=<?php echo $connId; ?>" class="button button-small">Bearbeiten</a>
|
||||||
<a href="?module=connections&action=swap&id=<?php echo $connId; ?>" class="button button-small" onclick="return confirm('Von/Nach fuer diese Verbindung vertauschen?');">Von/Nach tauschen</a>
|
<a href="?module=connections&action=swap&id=<?php echo $connId; ?>" class="button button-small" onclick="return confirm('Von/Nach fuer diese Verbindung vertauschen?');">Von/Nach tauschen</a>
|
||||||
<a href="?module=connections&action=delete&id=<?php echo $connId; ?>" class="button button-small button-danger"
|
<button
|
||||||
data-confirm-delete="true"
|
type="button"
|
||||||
data-confirm-message="Diese Verbindung wirklich loeschen?">
|
class="button button-small button-danger js-connection-delete"
|
||||||
|
data-connection-id="<?php echo $connId; ?>">
|
||||||
Loeschen
|
Loeschen
|
||||||
</a>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
@@ -382,7 +383,12 @@ $buildListUrl = static function (array $extra = []) use ($search, $deviceId): st
|
|||||||
<div class="sidebar-actions">
|
<div class="sidebar-actions">
|
||||||
<a href="?module=connections&action=edit&id=<?php echo $selectedConnId; ?>" class="button button-small">Bearbeiten</a>
|
<a href="?module=connections&action=edit&id=<?php echo $selectedConnId; ?>" class="button button-small">Bearbeiten</a>
|
||||||
<a href="?module=connections&action=swap&id=<?php echo $selectedConnId; ?>" class="button button-small" onclick="return confirm('Von/Nach fuer diese Verbindung vertauschen?');">Tauschen</a>
|
<a href="?module=connections&action=swap&id=<?php echo $selectedConnId; ?>" class="button button-small" onclick="return confirm('Von/Nach fuer diese Verbindung vertauschen?');">Tauschen</a>
|
||||||
<a href="?module=connections&action=delete&id=<?php echo $selectedConnId; ?>" class="button button-small button-danger" data-confirm-delete="true" data-confirm-message="Diese Verbindung wirklich loeschen?">Loeschen</a>
|
<button
|
||||||
|
type="button"
|
||||||
|
class="button button-small button-danger js-connection-delete"
|
||||||
|
data-connection-id="<?php echo $selectedConnId; ?>">
|
||||||
|
Loeschen
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<p><em>Keine Verbindung ausgewaehlt.</em></p>
|
<p><em>Keine Verbindung ausgewaehlt.</em></p>
|
||||||
@@ -474,3 +480,35 @@ $buildListUrl = static function (array $extra = []) use ($search, $deviceId): st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
document.querySelectorAll('.js-connection-delete').forEach((button) => {
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
const id = Number(button.dataset.connectionId || '0');
|
||||||
|
if (id <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!confirm('Diese Verbindung wirklich loeschen?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch('?module=connections&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.reload();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
alert((data && data.message) ? data.message : 'Loeschen fehlgeschlagen');
|
||||||
|
})
|
||||||
|
.catch(() => alert('Loeschen fehlgeschlagen'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -3,12 +3,20 @@
|
|||||||
* app/modules/devices/delete.php
|
* app/modules/devices/delete.php
|
||||||
*
|
*
|
||||||
* Loescht ein Geraet. Bei Abhaengigkeiten ist force=1 erforderlich.
|
* Loescht ein Geraet. Bei Abhaengigkeiten ist force=1 erforderlich.
|
||||||
|
* Unterstuetzt GET-Redirects und AJAX-POST.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$deviceId = (int)($_GET['id'] ?? 0);
|
$isPost = ($_SERVER['REQUEST_METHOD'] ?? '') === 'POST';
|
||||||
$forceDelete = (int)($_GET['force'] ?? 0) === 1;
|
$deviceId = (int)($_POST['id'] ?? $_GET['id'] ?? 0);
|
||||||
|
$forceDelete = (int)($_POST['force'] ?? $_GET['force'] ?? 0) === 1;
|
||||||
|
|
||||||
if ($deviceId <= 0) {
|
if ($deviceId <= 0) {
|
||||||
|
if ($isPost) {
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Ungueltige Geraete-ID']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
$_SESSION['error'] = "Ungueltige Geraete-ID";
|
$_SESSION['error'] = "Ungueltige Geraete-ID";
|
||||||
header('Location: ?module=devices&action=list');
|
header('Location: ?module=devices&action=list');
|
||||||
exit;
|
exit;
|
||||||
@@ -21,6 +29,12 @@ $device = $sql->single(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!$device) {
|
if (!$device) {
|
||||||
|
if ($isPost) {
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
http_response_code(404);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Geraet nicht gefunden']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
$_SESSION['error'] = "Geraet nicht gefunden";
|
$_SESSION['error'] = "Geraet nicht gefunden";
|
||||||
header('Location: ?module=devices&action=list');
|
header('Location: ?module=devices&action=list');
|
||||||
exit;
|
exit;
|
||||||
@@ -70,7 +84,23 @@ if ($hasDependencies && !$forceDelete) {
|
|||||||
$parts[] = $moduleCount . ' Port-Module';
|
$parts[] = $moduleCount . ' Port-Module';
|
||||||
}
|
}
|
||||||
|
|
||||||
$_SESSION['error'] = "Geraet hat abhaengige Daten (" . implode(', ', $parts) . "). Loeschen bitte bestaetigen.";
|
$dependencyMessage = "Geraet hat abhaengige Daten (" . implode(', ', $parts) . "). Loeschen bitte bestaetigen.";
|
||||||
|
if ($isPost) {
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
http_response_code(409);
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'requires_force' => true,
|
||||||
|
'message' => $dependencyMessage,
|
||||||
|
'dependencies' => [
|
||||||
|
'connections' => $connectionCount,
|
||||||
|
'ports' => $portCount,
|
||||||
|
'modules' => $moduleCount
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$_SESSION['error'] = $dependencyMessage;
|
||||||
header('Location: ?module=devices&action=edit&id=' . urlencode((string)$deviceId));
|
header('Location: ?module=devices&action=edit&id=' . urlencode((string)$deviceId));
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
@@ -91,8 +121,19 @@ $deleted = $sql->set(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if ($deleted > 0) {
|
if ($deleted > 0) {
|
||||||
|
if ($isPost) {
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
echo json_encode(['success' => true, 'message' => "Geraet geloescht: " . $device['name']]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
$_SESSION['success'] = "Geraet geloescht: " . $device['name'];
|
$_SESSION['success'] = "Geraet geloescht: " . $device['name'];
|
||||||
} else {
|
} else {
|
||||||
|
if ($isPost) {
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Geraet konnte nicht geloescht werden']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
$_SESSION['error'] = "Geraet konnte nicht geloescht werden";
|
$_SESSION['error'] = "Geraet konnte nicht geloescht werden";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,19 @@ if ($isEdit) {
|
|||||||
// =========================
|
// =========================
|
||||||
$deviceTypes = $sql->get("SELECT id, name, category FROM device_types ORDER BY name", "", []);
|
$deviceTypes = $sql->get("SELECT id, name, category FROM device_types ORDER BY name", "", []);
|
||||||
$racks = $sql->get("SELECT id, name FROM racks ORDER BY name", "", []);
|
$racks = $sql->get("SELECT id, name FROM racks ORDER BY name", "", []);
|
||||||
|
$devicePorts = [];
|
||||||
|
|
||||||
|
if ($isEdit) {
|
||||||
|
$devicePorts = $sql->get(
|
||||||
|
"SELECT dp.id, dp.name, dp.status, dp.mode, dp.vlan_config, pt.name AS port_type_name
|
||||||
|
FROM device_ports dp
|
||||||
|
LEFT JOIN port_types pt ON pt.id = dp.port_type_id
|
||||||
|
WHERE dp.device_id = ?
|
||||||
|
ORDER BY dp.id",
|
||||||
|
"i",
|
||||||
|
[$deviceId]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|
||||||
@@ -160,6 +173,67 @@ $racks = $sql->get("SELECT id, name FROM racks ORDER BY name", "", []);
|
|||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>Ports</legend>
|
||||||
|
<?php if ($isEdit): ?>
|
||||||
|
<?php if (!empty($devicePorts)): ?>
|
||||||
|
<p><small>Portstatus und VLAN-Zuordnung koennen hier direkt gepflegt werden (VLANs kommagetrennt, z. B. 10,20,30).</small></p>
|
||||||
|
<table class="device-port-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Port-Typ</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Modus</th>
|
||||||
|
<th>VLANs</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($devicePorts as $port): ?>
|
||||||
|
<?php
|
||||||
|
$vlanValue = '';
|
||||||
|
if (!empty($port['vlan_config'])) {
|
||||||
|
$decodedVlans = json_decode((string)$port['vlan_config'], true);
|
||||||
|
if (is_array($decodedVlans)) {
|
||||||
|
$vlanValue = implode(', ', array_map('strval', $decodedVlans));
|
||||||
|
} else {
|
||||||
|
$vlanValue = (string)$port['vlan_config'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$statusValue = (string)($port['status'] ?? 'active');
|
||||||
|
if (!in_array($statusValue, ['active', 'disabled'], true)) {
|
||||||
|
$statusValue = 'active';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<input type="text" name="device_ports[<?php echo (int)$port['id']; ?>][name]" value="<?php echo htmlspecialchars((string)$port['name']); ?>">
|
||||||
|
</td>
|
||||||
|
<td><?php echo htmlspecialchars((string)($port['port_type_name'] ?? '-')); ?></td>
|
||||||
|
<td>
|
||||||
|
<select name="device_ports[<?php echo (int)$port['id']; ?>][status]">
|
||||||
|
<option value="active" <?php echo $statusValue === 'active' ? 'selected' : ''; ?>>aktiv</option>
|
||||||
|
<option value="disabled" <?php echo $statusValue === 'disabled' ? 'selected' : ''; ?>>inaktiv</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" name="device_ports[<?php echo (int)$port['id']; ?>][mode]" value="<?php echo htmlspecialchars((string)($port['mode'] ?? '')); ?>" placeholder="z. B. access/trunk">
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" name="device_ports[<?php echo (int)$port['id']; ?>][vlan_config]" value="<?php echo htmlspecialchars($vlanValue); ?>" placeholder="10,20,30">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<?php else: ?>
|
||||||
|
<p><small>Zu diesem Geraet sind aktuell keine Ports vorhanden.</small></p>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php else: ?>
|
||||||
|
<p><small>Ports werden nach dem ersten Speichern automatisch aus dem Geraetetyp erzeugt und koennen dann hier gepflegt werden.</small></p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
<!-- =========================
|
<!-- =========================
|
||||||
Aktionen
|
Aktionen
|
||||||
========================= -->
|
========================= -->
|
||||||
@@ -264,34 +338,64 @@ $racks = $sql->get("SELECT id, name FROM racks ORDER BY name", "", []);
|
|||||||
.button:hover {
|
.button:hover {
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.device-port-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-port-table th,
|
||||||
|
.device-port-table td {
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
padding: 8px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-port-table input,
|
||||||
|
.device-port-table select {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function confirmDelete(link, id, connectionCount, portCount, moduleCount) {
|
function confirmDelete(link, id, connectionCount, portCount, moduleCount) {
|
||||||
if (confirm('Dieses Gerät wirklich löschen?')) {
|
if (!confirm('Dieses Geraet wirklich loeschen?')) {
|
||||||
const hasDependencies = (connectionCount > 0) || (portCount > 0) || (moduleCount > 0);
|
return false;
|
||||||
if (hasDependencies) {
|
|
||||||
const details = [];
|
|
||||||
if (connectionCount > 0) {
|
|
||||||
details.push(connectionCount + ' Verbindungen');
|
|
||||||
}
|
|
||||||
if (portCount > 0) {
|
|
||||||
details.push(portCount + ' Ports');
|
|
||||||
}
|
|
||||||
if (moduleCount > 0) {
|
|
||||||
details.push(moduleCount + ' Port-Module');
|
|
||||||
}
|
|
||||||
const dependencyMessage = 'Es gibt abhängige Daten (' + details.join(', ') + '). Diese auch löschen?';
|
|
||||||
if (!confirm(dependencyMessage)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
window.location.href = (link && link.href ? link.href : ('?module=devices&action=delete&id=' + encodeURIComponent(id))) + '&force=1';
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const requestDelete = (forceDelete) => {
|
||||||
|
const body = ['id=' + encodeURIComponent(id)];
|
||||||
|
if (forceDelete) {
|
||||||
|
body.push('force=1');
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch('?module=devices&action=delete', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
|
||||||
|
body: body.join('&')
|
||||||
|
})
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => {
|
||||||
|
if (data && data.success) {
|
||||||
|
window.location.href = '?module=devices&action=list';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data && data.requires_force) {
|
||||||
|
if (confirm(data.message || 'Es gibt abhaengige Daten. Trotzdem loeschen?')) {
|
||||||
|
requestDelete(true);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
alert((data && data.message) ? data.message : 'Loeschen fehlgeschlagen');
|
||||||
|
})
|
||||||
|
.catch(() => alert('Loeschen fehlgeschlagen'));
|
||||||
|
};
|
||||||
|
|
||||||
|
requestDelete(false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* modules/devices/list.php
|
* modules/devices/list.php
|
||||||
* Vollständige Geräteübersicht mit Filter
|
* Vollstaendige Geraeteuebersicht mit Filter
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// =========================
|
// =========================
|
||||||
@@ -22,69 +22,88 @@ $params = [];
|
|||||||
|
|
||||||
if ($search !== '') {
|
if ($search !== '') {
|
||||||
$where[] = "(d.name LIKE ? OR d.serial_number LIKE ? OR dt.name LIKE ?)";
|
$where[] = "(d.name LIKE ? OR d.serial_number LIKE ? OR dt.name LIKE ?)";
|
||||||
$types .= "sss";
|
$types .= 'sss';
|
||||||
$params[] = "%$search%";
|
$params[] = "%$search%";
|
||||||
$params[] = "%$search%";
|
$params[] = "%$search%";
|
||||||
$params[] = "%$search%";
|
$params[] = "%$search%";
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($typeId > 0) {
|
if ($typeId > 0) {
|
||||||
$where[] = "d.device_type_id = ?";
|
$where[] = 'd.device_type_id = ?';
|
||||||
$types .= "i";
|
$types .= 'i';
|
||||||
$params[] = $typeId;
|
$params[] = $typeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($floorId > 0) {
|
if ($floorId > 0) {
|
||||||
$where[] = "f.id = ?";
|
$where[] = 'f.id = ?';
|
||||||
$types .= "i";
|
$types .= 'i';
|
||||||
$params[] = $floorId;
|
$params[] = $floorId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($rackId > 0) {
|
if ($rackId > 0) {
|
||||||
$where[] = "d.rack_id = ?";
|
$where[] = 'd.rack_id = ?';
|
||||||
$types .= "i";
|
$types .= 'i';
|
||||||
$params[] = $rackId;
|
$params[] = $rackId;
|
||||||
}
|
}
|
||||||
|
|
||||||
$whereSql = $where ? 'WHERE ' . implode(' AND ', $where) : '';
|
$whereSql = $where ? 'WHERE ' . implode(' AND ', $where) : '';
|
||||||
|
|
||||||
// =========================
|
// =========================
|
||||||
// Geräte laden
|
// Geraete laden
|
||||||
// =========================
|
// =========================
|
||||||
$devices = $sql->get(
|
$devices = $sql->get(
|
||||||
"
|
"
|
||||||
SELECT
|
SELECT
|
||||||
d.id,
|
d.id,
|
||||||
d.name,
|
d.name,
|
||||||
d.serial_number,
|
d.serial_number,
|
||||||
d.rack_position_he,
|
d.rack_position_he,
|
||||||
d.rack_height_he,
|
d.rack_height_he,
|
||||||
d.web_config_url,
|
d.web_config_url,
|
||||||
dt.name AS device_type,
|
dt.name AS device_type,
|
||||||
dt.image_path,
|
dt.image_path,
|
||||||
f.name AS floor_name,
|
f.name AS floor_name,
|
||||||
r.name AS rack_name,
|
r.name AS rack_name,
|
||||||
(
|
(
|
||||||
SELECT COUNT(*)
|
SELECT COUNT(*)
|
||||||
FROM device_ports dp
|
FROM device_ports dp
|
||||||
WHERE dp.device_id = d.id
|
WHERE dp.device_id = d.id
|
||||||
) AS port_count,
|
) AS port_count,
|
||||||
(
|
(
|
||||||
SELECT COUNT(*)
|
SELECT COUNT(*)
|
||||||
FROM device_port_modules dpm
|
FROM device_ports dp
|
||||||
JOIN device_ports dp2 ON dp2.id = dpm.device_port_id
|
WHERE dp.device_id = d.id
|
||||||
WHERE dp2.device_id = d.id
|
AND dp.status = 'active'
|
||||||
) AS module_count,
|
) AS active_port_count,
|
||||||
(
|
(
|
||||||
SELECT COUNT(*)
|
SELECT COUNT(*)
|
||||||
FROM connections c
|
FROM device_ports dp
|
||||||
WHERE (c.port_a_type = 'device' AND c.port_a_id IN (
|
WHERE dp.device_id = d.id
|
||||||
SELECT dp3.id FROM device_ports dp3 WHERE dp3.device_id = d.id
|
AND dp.status = 'disabled'
|
||||||
))
|
) AS disabled_port_count,
|
||||||
OR (c.port_b_type = 'device' AND c.port_b_id IN (
|
(
|
||||||
SELECT dp4.id FROM device_ports dp4 WHERE dp4.device_id = d.id
|
SELECT COUNT(*)
|
||||||
))
|
FROM device_ports dp
|
||||||
) AS connection_count
|
WHERE dp.device_id = d.id
|
||||||
|
AND dp.vlan_config IS NOT NULL
|
||||||
|
AND dp.vlan_config <> '[]'
|
||||||
|
) AS vlan_port_count,
|
||||||
|
(
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM device_port_modules dpm
|
||||||
|
JOIN device_ports dp2 ON dp2.id = dpm.device_port_id
|
||||||
|
WHERE dp2.device_id = d.id
|
||||||
|
) AS module_count,
|
||||||
|
(
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM connections c
|
||||||
|
WHERE (c.port_a_type = 'device' AND c.port_a_id IN (
|
||||||
|
SELECT dp3.id FROM device_ports dp3 WHERE dp3.device_id = d.id
|
||||||
|
))
|
||||||
|
OR (c.port_b_type = 'device' AND c.port_b_id IN (
|
||||||
|
SELECT dp4.id FROM device_ports dp4 WHERE dp4.device_id = d.id
|
||||||
|
))
|
||||||
|
) AS connection_count
|
||||||
FROM devices d
|
FROM devices d
|
||||||
JOIN device_types dt ON dt.id = d.device_type_id
|
JOIN device_types dt ON dt.id = d.device_type_id
|
||||||
LEFT JOIN racks r ON r.id = d.rack_id
|
LEFT JOIN racks r ON r.id = d.rack_id
|
||||||
@@ -99,22 +118,19 @@ $whereSql = $where ? 'WHERE ' . implode(' AND ', $where) : '';
|
|||||||
// =========================
|
// =========================
|
||||||
// Filter-Daten laden
|
// Filter-Daten laden
|
||||||
// =========================
|
// =========================
|
||||||
$deviceTypes = $sql->get("SELECT id, name FROM device_types ORDER BY name", "", []);
|
$deviceTypes = $sql->get('SELECT id, name FROM device_types ORDER BY name', '', []);
|
||||||
$floors = $sql->get("SELECT id, name FROM floors ORDER BY name", "", []);
|
$floors = $sql->get('SELECT id, name FROM floors ORDER BY name', '', []);
|
||||||
$racks = $sql->get("SELECT id, name FROM racks ORDER BY name", "", []);
|
$racks = $sql->get('SELECT id, name FROM racks ORDER BY name', '', []);
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="devices-container">
|
<div class="devices-container">
|
||||||
<h1>Geräte</h1>
|
<h1>Geraete</h1>
|
||||||
|
|
||||||
<!-- =========================
|
|
||||||
Filter-Toolbar
|
|
||||||
========================= -->
|
|
||||||
<form method="get" class="filter-form">
|
<form method="get" class="filter-form">
|
||||||
<input type="hidden" name="module" value="devices">
|
<input type="hidden" name="module" value="devices">
|
||||||
<input type="hidden" name="action" value="list">
|
<input type="hidden" name="action" value="list">
|
||||||
|
|
||||||
<input type="text" name="search" placeholder="Suche nach Name oder Seriennummer…"
|
<input type="text" name="search" placeholder="Suche nach Name oder Seriennummer..."
|
||||||
value="<?php echo htmlspecialchars($search); ?>" class="search-input">
|
value="<?php echo htmlspecialchars($search); ?>" class="search-input">
|
||||||
|
|
||||||
<select name="type_id">
|
<select name="type_id">
|
||||||
@@ -149,16 +165,13 @@ $racks = $sql->get("SELECT id, name FROM racks ORDER BY name", "", []);
|
|||||||
<a href="?module=devices&action=list" class="button">Reset</a>
|
<a href="?module=devices&action=list" class="button">Reset</a>
|
||||||
|
|
||||||
<a href="?module=devices&action=edit" class="button button-primary" style="margin-left: auto;">
|
<a href="?module=devices&action=edit" class="button button-primary" style="margin-left: auto;">
|
||||||
+ Neues Gerät
|
+ Neues Geraet
|
||||||
</a>
|
</a>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<!-- =========================
|
|
||||||
Geräte-Liste
|
|
||||||
========================= -->
|
|
||||||
<?php if (!empty($devices)): ?>
|
<?php if (!empty($devices)): ?>
|
||||||
<div class="device-stats">
|
<div class="device-stats">
|
||||||
<p>Gefundene Geräte: <strong><?php echo count($devices); ?></strong></p>
|
<p>Gefundene Geraete: <strong><?php echo count($devices); ?></strong></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table class="device-list">
|
<table class="device-list">
|
||||||
@@ -169,6 +182,7 @@ $racks = $sql->get("SELECT id, name FROM racks ORDER BY name", "", []);
|
|||||||
<th>Stockwerk</th>
|
<th>Stockwerk</th>
|
||||||
<th>Rack</th>
|
<th>Rack</th>
|
||||||
<th>Position (HE)</th>
|
<th>Position (HE)</th>
|
||||||
|
<th>Ports</th>
|
||||||
<th>Seriennummer</th>
|
<th>Seriennummer</th>
|
||||||
<th>Webconfig</th>
|
<th>Webconfig</th>
|
||||||
<th>Aktionen</th>
|
<th>Aktionen</th>
|
||||||
@@ -186,28 +200,37 @@ $racks = $sql->get("SELECT id, name FROM racks ORDER BY name", "", []);
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<?php echo htmlspecialchars($d['floor_name'] ?? '—'); ?>
|
<?php echo htmlspecialchars($d['floor_name'] ?? '-'); ?>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<?php echo htmlspecialchars($d['rack_name'] ?? '—'); ?>
|
<?php echo htmlspecialchars($d['rack_name'] ?? '-'); ?>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<?php
|
<?php
|
||||||
if ($d['rack_position_he']) {
|
if ($d['rack_position_he']) {
|
||||||
echo $d['rack_position_he'];
|
echo (int)$d['rack_position_he'];
|
||||||
if ($d['rack_height_he']) {
|
if ($d['rack_height_he']) {
|
||||||
echo "–" . ($d['rack_position_he'] + $d['rack_height_he'] - 1);
|
echo '-' . ((int)$d['rack_position_he'] + (int)$d['rack_height_he'] - 1);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
echo "—";
|
echo '-';
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<small><?php echo htmlspecialchars($d['serial_number'] ?? '—'); ?></small>
|
<small>
|
||||||
|
<?php echo (int)$d['port_count']; ?> gesamt<br>
|
||||||
|
<?php echo (int)$d['active_port_count']; ?> aktiv /
|
||||||
|
<?php echo (int)$d['disabled_port_count']; ?> inaktiv<br>
|
||||||
|
VLAN gesetzt: <?php echo (int)$d['vlan_port_count']; ?>
|
||||||
|
</small>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<small><?php echo htmlspecialchars($d['serial_number'] ?? '-'); ?></small>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
@@ -216,13 +239,13 @@ $racks = $sql->get("SELECT id, name FROM racks ORDER BY name", "", []);
|
|||||||
Webconfig
|
Webconfig
|
||||||
</a>
|
</a>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
—
|
-
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="actions">
|
<td class="actions">
|
||||||
<a href="?module=devices&action=edit&id=<?php echo $d['id']; ?>" class="button button-small">Bearbeiten</a>
|
<a href="?module=devices&action=edit&id=<?php echo $d['id']; ?>" class="button button-small">Bearbeiten</a>
|
||||||
<a href="?module=devices&action=delete&id=<?php echo (int)$d['id']; ?>" class="button button-small button-danger" onclick="return confirmDelete(this, <?php echo (int)$d['id']; ?>, <?php echo (int)$d['connection_count']; ?>, <?php echo (int)$d['port_count']; ?>, <?php echo (int)$d['module_count']; ?>)">Löschen</a>
|
<a href="?module=devices&action=delete&id=<?php echo (int)$d['id']; ?>" class="button button-small button-danger" onclick="return confirmDelete(this, <?php echo (int)$d['id']; ?>, <?php echo (int)$d['connection_count']; ?>, <?php echo (int)$d['port_count']; ?>, <?php echo (int)$d['module_count']; ?>)">Loeschen</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
@@ -231,10 +254,10 @@ $racks = $sql->get("SELECT id, name FROM racks ORDER BY name", "", []);
|
|||||||
|
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<div class="empty-state">
|
<div class="empty-state">
|
||||||
<p>Keine Geräte gefunden.</p>
|
<p>Keine Geraete gefunden.</p>
|
||||||
<p>
|
<p>
|
||||||
<a href="?module=devices&action=edit" class="button button-primary">
|
<a href="?module=devices&action=edit" class="button button-primary">
|
||||||
Erstes Gerät anlegen
|
Erstes Geraet anlegen
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -351,33 +374,41 @@ $racks = $sql->get("SELECT id, name FROM racks ORDER BY name", "", []);
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
function confirmDelete(link, id, connectionCount, portCount, moduleCount) {
|
function confirmDelete(link, id, connectionCount, portCount, moduleCount) {
|
||||||
if (confirm('Dieses Gerät wirklich löschen?')) {
|
if (!confirm('Dieses Geraet wirklich loeschen?')) {
|
||||||
const hasDependencies = (connectionCount > 0) || (portCount > 0) || (moduleCount > 0);
|
return false;
|
||||||
if (hasDependencies) {
|
|
||||||
const details = [];
|
|
||||||
if (connectionCount > 0) {
|
|
||||||
details.push(connectionCount + ' Verbindungen');
|
|
||||||
}
|
|
||||||
if (portCount > 0) {
|
|
||||||
details.push(portCount + ' Ports');
|
|
||||||
}
|
|
||||||
if (moduleCount > 0) {
|
|
||||||
details.push(moduleCount + ' Port-Module');
|
|
||||||
}
|
|
||||||
const dependencyMessage = 'Es gibt abhängige Daten (' + details.join(', ') + '). Diese auch löschen?';
|
|
||||||
if (!confirm(dependencyMessage)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
window.location.href = (link && link.href ? link.href : ('?module=devices&action=delete&id=' + encodeURIComponent(id))) + '&force=1';
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const requestDelete = (forceDelete) => {
|
||||||
|
const body = ['id=' + encodeURIComponent(id)];
|
||||||
|
if (forceDelete) {
|
||||||
|
body.push('force=1');
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch('?module=devices&action=delete', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
|
||||||
|
body: body.join('&')
|
||||||
|
})
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => {
|
||||||
|
if (data && data.success) {
|
||||||
|
window.location.reload();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data && data.requires_force) {
|
||||||
|
if (confirm(data.message || 'Es gibt abhaengige Daten. Trotzdem loeschen?')) {
|
||||||
|
requestDelete(true);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
alert((data && data.message) ? data.message : 'Loeschen fehlgeschlagen');
|
||||||
|
})
|
||||||
|
.catch(() => alert('Loeschen fehlgeschlagen'));
|
||||||
|
};
|
||||||
|
|
||||||
|
requestDelete(false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ $rackHeightHe = (int)($_POST['rack_height_he'] ?? 1);
|
|||||||
$serialNumber = trim($_POST['serial_number'] ?? '');
|
$serialNumber = trim($_POST['serial_number'] ?? '');
|
||||||
$comment = trim($_POST['comment'] ?? '');
|
$comment = trim($_POST['comment'] ?? '');
|
||||||
$webConfigUrl = trim($_POST['web_config_url'] ?? '');
|
$webConfigUrl = trim($_POST['web_config_url'] ?? '');
|
||||||
|
$devicePortRows = is_array($_POST['device_ports'] ?? null) ? $_POST['device_ports'] : [];
|
||||||
if ($webConfigUrl === '') {
|
if ($webConfigUrl === '') {
|
||||||
$webConfigUrl = null;
|
$webConfigUrl = null;
|
||||||
}
|
}
|
||||||
@@ -88,6 +89,9 @@ if ($isNewDevice) {
|
|||||||
if ($isNewDevice && $deviceId > 0) {
|
if ($isNewDevice && $deviceId > 0) {
|
||||||
copyDevicePortsFromType($sql, $deviceId, $deviceTypeId);
|
copyDevicePortsFromType($sql, $deviceId, $deviceTypeId);
|
||||||
}
|
}
|
||||||
|
if ($deviceId > 0 && !$isNewDevice && !empty($devicePortRows)) {
|
||||||
|
syncDevicePorts($sql, $deviceId, $devicePortRows);
|
||||||
|
}
|
||||||
|
|
||||||
$_SESSION['success'] = "Gerät gespeichert";
|
$_SESSION['success'] = "Gerät gespeichert";
|
||||||
|
|
||||||
@@ -139,3 +143,105 @@ function copyDevicePortsFromType($sql, $deviceId, $deviceTypeId)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function syncDevicePorts($sql, $deviceId, array $rows)
|
||||||
|
{
|
||||||
|
$ports = $sql->get(
|
||||||
|
"SELECT id, name FROM device_ports WHERE device_id = ?",
|
||||||
|
"i",
|
||||||
|
[$deviceId]
|
||||||
|
);
|
||||||
|
if (empty($ports)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$allowedIds = [];
|
||||||
|
$currentNames = [];
|
||||||
|
foreach ($ports as $port) {
|
||||||
|
$portId = (int)($port['id'] ?? 0);
|
||||||
|
if ($portId <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$allowedIds[$portId] = true;
|
||||||
|
$currentNames[$portId] = (string)($port['name'] ?? '');
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($rows as $portIdRaw => $row) {
|
||||||
|
$portId = (int)$portIdRaw;
|
||||||
|
if ($portId <= 0 || !isset($allowedIds[$portId]) || !is_array($row)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = trim((string)($row['name'] ?? ''));
|
||||||
|
if ($name === '') {
|
||||||
|
$name = $currentNames[$portId] ?? ('Port ' . $portId);
|
||||||
|
}
|
||||||
|
|
||||||
|
$status = trim((string)($row['status'] ?? 'active'));
|
||||||
|
if (!in_array($status, ['active', 'disabled'], true)) {
|
||||||
|
$status = 'active';
|
||||||
|
}
|
||||||
|
|
||||||
|
$mode = trim((string)($row['mode'] ?? ''));
|
||||||
|
$mode = $mode !== '' ? $mode : null;
|
||||||
|
$vlanJson = normalizeVlanConfig($row['vlan_config'] ?? '');
|
||||||
|
|
||||||
|
if ($mode !== null && $vlanJson !== null) {
|
||||||
|
$sql->set(
|
||||||
|
"UPDATE device_ports
|
||||||
|
SET name = ?, status = ?, mode = ?, vlan_config = ?
|
||||||
|
WHERE id = ? AND device_id = ?",
|
||||||
|
"ssssii",
|
||||||
|
[$name, $status, $mode, $vlanJson, $portId, $deviceId]
|
||||||
|
);
|
||||||
|
} elseif ($mode !== null) {
|
||||||
|
$sql->set(
|
||||||
|
"UPDATE device_ports
|
||||||
|
SET name = ?, status = ?, mode = ?, vlan_config = NULL
|
||||||
|
WHERE id = ? AND device_id = ?",
|
||||||
|
"sssii",
|
||||||
|
[$name, $status, $mode, $portId, $deviceId]
|
||||||
|
);
|
||||||
|
} elseif ($vlanJson !== null) {
|
||||||
|
$sql->set(
|
||||||
|
"UPDATE device_ports
|
||||||
|
SET name = ?, status = ?, mode = NULL, vlan_config = ?
|
||||||
|
WHERE id = ? AND device_id = ?",
|
||||||
|
"sssii",
|
||||||
|
[$name, $status, $vlanJson, $portId, $deviceId]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$sql->set(
|
||||||
|
"UPDATE device_ports
|
||||||
|
SET name = ?, status = ?, mode = NULL, vlan_config = NULL
|
||||||
|
WHERE id = ? AND device_id = ?",
|
||||||
|
"ssii",
|
||||||
|
[$name, $status, $portId, $deviceId]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeVlanConfig($raw)
|
||||||
|
{
|
||||||
|
$value = trim((string)$raw);
|
||||||
|
if ($value === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$parts = preg_split('/[\s,;]+/', $value);
|
||||||
|
$normalized = [];
|
||||||
|
foreach ((array)$parts as $part) {
|
||||||
|
$entry = trim((string)$part);
|
||||||
|
if ($entry === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$normalized[$entry] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($normalized)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return json_encode(array_keys($normalized), JSON_UNESCAPED_UNICODE);
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,30 +2,48 @@
|
|||||||
/**
|
/**
|
||||||
* app/modules/floor_infrastructure/delete.php
|
* app/modules/floor_infrastructure/delete.php
|
||||||
*
|
*
|
||||||
* Loescht Patchpanels oder Wandbuchsen.
|
* Loescht Patchpanels oder Wandbuchsen (AJAX-POST bevorzugt).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$type = strtolower(trim((string)($_GET['type'] ?? '')));
|
$isPost = ($_SERVER['REQUEST_METHOD'] ?? '') === 'POST';
|
||||||
$id = (int)($_GET['id'] ?? 0);
|
$type = strtolower(trim((string)($_POST['type'] ?? $_GET['type'] ?? '')));
|
||||||
|
$id = (int)($_POST['id'] ?? $_GET['id'] ?? 0);
|
||||||
|
|
||||||
if ($id <= 0 || !in_array($type, ['patchpanel', 'outlet'], true)) {
|
if ($id <= 0 || !in_array($type, ['patchpanel', 'outlet'], true)) {
|
||||||
|
if ($isPost) {
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Ungueltige Anfrage']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
header('Location: ?module=floor_infrastructure&action=list');
|
header('Location: ?module=floor_infrastructure&action=list');
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($type === 'patchpanel') {
|
if ($type === 'patchpanel') {
|
||||||
$sql->set(
|
$rows = $sql->set(
|
||||||
"DELETE FROM floor_patchpanels WHERE id = ?",
|
"DELETE FROM floor_patchpanels WHERE id = ?",
|
||||||
"i",
|
"i",
|
||||||
[$id]
|
[$id]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$sql->set(
|
$rows = $sql->set(
|
||||||
"DELETE FROM network_outlets WHERE id = ?",
|
"DELETE FROM network_outlets WHERE id = ?",
|
||||||
"i",
|
"i",
|
||||||
[$id]
|
[$id]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($isPost) {
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
if ($rows > 0) {
|
||||||
|
echo json_encode(['success' => true, 'message' => 'Infrastrukturobjekt geloescht']);
|
||||||
|
} else {
|
||||||
|
http_response_code(404);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Infrastrukturobjekt nicht gefunden oder bereits geloescht']);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
header('Location: ?module=floor_infrastructure&action=list');
|
header('Location: ?module=floor_infrastructure&action=list');
|
||||||
exit;
|
exit;
|
||||||
|
|||||||
@@ -180,7 +180,14 @@ 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>
|
<button
|
||||||
|
type="button"
|
||||||
|
class="button button-small button-danger js-floor-infra-delete"
|
||||||
|
data-delete-id="<?php echo (int)$panel['id']; ?>"
|
||||||
|
data-delete-type="patchpanel"
|
||||||
|
data-delete-label="<?php echo htmlspecialchars((string)$panel['name'], ENT_QUOTES, 'UTF-8'); ?>">
|
||||||
|
Loeschen
|
||||||
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
@@ -226,7 +233,14 @@ 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>
|
<button
|
||||||
|
type="button"
|
||||||
|
class="button button-small button-danger js-floor-infra-delete"
|
||||||
|
data-delete-id="<?php echo (int)$outlet['id']; ?>"
|
||||||
|
data-delete-type="outlet"
|
||||||
|
data-delete-label="<?php echo htmlspecialchars((string)$outlet['name'], ENT_QUOTES, 'UTF-8'); ?>">
|
||||||
|
Loeschen
|
||||||
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
@@ -237,3 +251,38 @@ if ($editorFloor) {
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
document.querySelectorAll('.js-floor-infra-delete').forEach((button) => {
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
const id = Number(button.dataset.deleteId || '0');
|
||||||
|
const type = (button.dataset.deleteType || '').trim();
|
||||||
|
const label = button.dataset.deleteLabel || 'Objekt';
|
||||||
|
if (id <= 0 || (type !== 'patchpanel' && type !== 'outlet')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const entityLabel = type === 'patchpanel' ? 'Patchpanel' : 'Wandbuchse';
|
||||||
|
if (!confirm(entityLabel + ' "' + label + '" wirklich loeschen?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch('?module=floor_infrastructure&action=delete', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
|
||||||
|
body: 'id=' + encodeURIComponent(id) + '&type=' + encodeURIComponent(type)
|
||||||
|
})
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => {
|
||||||
|
if (data && data.success) {
|
||||||
|
window.location.reload();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
alert((data && data.message) ? data.message : 'Loeschen fehlgeschlagen');
|
||||||
|
})
|
||||||
|
.catch(() => alert('Loeschen fehlgeschlagen'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -25,6 +25,47 @@ if (!$exists) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$forceDelete = (int)($_POST['force'] ?? $_GET['force'] ?? 0) === 1;
|
||||||
|
$dependencyCounts = $sql->single(
|
||||||
|
"SELECT
|
||||||
|
(SELECT COUNT(*) FROM rooms WHERE floor_id = ?) AS room_count,
|
||||||
|
(SELECT COUNT(*) FROM racks WHERE floor_id = ?) AS rack_count,
|
||||||
|
(SELECT COUNT(*) FROM floor_patchpanels WHERE floor_id = ?) AS patchpanel_count",
|
||||||
|
"iii",
|
||||||
|
[$id, $id, $id]
|
||||||
|
);
|
||||||
|
|
||||||
|
$roomCount = (int)($dependencyCounts['room_count'] ?? 0);
|
||||||
|
$rackCount = (int)($dependencyCounts['rack_count'] ?? 0);
|
||||||
|
$patchpanelCount = (int)($dependencyCounts['patchpanel_count'] ?? 0);
|
||||||
|
$hasDependencies = $roomCount > 0 || $rackCount > 0 || $patchpanelCount > 0;
|
||||||
|
|
||||||
|
if ($hasDependencies && !$forceDelete) {
|
||||||
|
$parts = [];
|
||||||
|
if ($roomCount > 0) {
|
||||||
|
$parts[] = $roomCount . ' Raeume';
|
||||||
|
}
|
||||||
|
if ($rackCount > 0) {
|
||||||
|
$parts[] = $rackCount . ' Racks';
|
||||||
|
}
|
||||||
|
if ($patchpanelCount > 0) {
|
||||||
|
$parts[] = $patchpanelCount . ' Patchpanels';
|
||||||
|
}
|
||||||
|
|
||||||
|
http_response_code(409);
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'requires_force' => true,
|
||||||
|
'message' => 'Beim Loeschen werden abhaengige Daten entfernt (' . implode(', ', $parts) . '). Fortfahren?',
|
||||||
|
'dependencies' => [
|
||||||
|
'rooms' => $roomCount,
|
||||||
|
'racks' => $rackCount,
|
||||||
|
'patchpanels' => $patchpanelCount
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
$rows = $sql->set("DELETE FROM floors WHERE id = ?", "i", [$id]);
|
$rows = $sql->set("DELETE FROM floors WHERE id = ?", "i", [$id]);
|
||||||
if ($rows === false) {
|
if ($rows === false) {
|
||||||
http_response_code(500);
|
http_response_code(500);
|
||||||
@@ -32,5 +73,13 @@ if ($rows === false) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
echo json_encode(['success' => true, 'message' => 'Stockwerk geloescht']);
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Stockwerk geloescht',
|
||||||
|
'dependencies' => [
|
||||||
|
'rooms' => $roomCount,
|
||||||
|
'racks' => $rackCount,
|
||||||
|
'patchpanels' => $patchpanelCount
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -233,11 +233,20 @@ $buildings = $sql->get("SELECT id, name FROM buildings ORDER BY name", "", []);
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
function confirmDelete(id) {
|
function confirmDelete(id) {
|
||||||
if (confirm('Dieses Stockwerk wirklich loeschen? Alle Raeume und Racks werden geloescht.')) {
|
if (!confirm('Dieses Stockwerk wirklich loeschen?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestDelete = (forceDelete) => {
|
||||||
|
const body = ['id=' + encodeURIComponent(id)];
|
||||||
|
if (forceDelete) {
|
||||||
|
body.push('force=1');
|
||||||
|
}
|
||||||
|
|
||||||
fetch('?module=floors&action=delete', {
|
fetch('?module=floors&action=delete', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
|
||||||
body: 'id=' + encodeURIComponent(id)
|
body: body.join('&')
|
||||||
})
|
})
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
@@ -245,12 +254,22 @@ function confirmDelete(id) {
|
|||||||
window.location.reload();
|
window.location.reload();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data && data.requires_force) {
|
||||||
|
if (confirm(data.message || 'Abhaengige Daten ebenfalls loeschen?')) {
|
||||||
|
requestDelete(true);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
alert((data && data.message) ? data.message : 'Loeschen fehlgeschlagen');
|
alert((data && data.message) ? data.message : 'Loeschen fehlgeschlagen');
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
alert('Loeschen fehlgeschlagen');
|
alert('Loeschen fehlgeschlagen');
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
|
requestDelete(false);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,16 @@
|
|||||||
|
|
||||||
header('Content-Type: application/json; charset=utf-8');
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
|
||||||
|
if ($method !== 'POST') {
|
||||||
|
http_response_code(405);
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Methode nicht erlaubt'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
$locationId = (int)($_POST['id'] ?? $_GET['id'] ?? 0);
|
$locationId = (int)($_POST['id'] ?? $_GET['id'] ?? 0);
|
||||||
|
|
||||||
if ($locationId <= 0) {
|
if ($locationId <= 0) {
|
||||||
|
|||||||
84
app/modules/port_types/delete.php
Normal file
84
app/modules/port_types/delete.php
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* app/modules/port_types/delete.php
|
||||||
|
*
|
||||||
|
* Loescht einen Porttyp per AJAX (POST).
|
||||||
|
*/
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
$portTypeId = (int)($_POST['id'] ?? 0);
|
||||||
|
if ($portTypeId <= 0) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Ungueltige Porttyp-ID']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$portType = $sql->single(
|
||||||
|
"SELECT id, name FROM port_types WHERE id = ?",
|
||||||
|
"i",
|
||||||
|
[$portTypeId]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!$portType) {
|
||||||
|
http_response_code(404);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Porttyp nicht gefunden']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$usage = $sql->single(
|
||||||
|
"SELECT
|
||||||
|
(SELECT COUNT(*) FROM device_type_ports WHERE port_type_id = ?) AS device_type_ports_count,
|
||||||
|
(SELECT COUNT(*) FROM device_ports WHERE port_type_id = ?) AS device_ports_count,
|
||||||
|
(SELECT COUNT(*) FROM module_ports WHERE port_type_id = ?) AS module_ports_count,
|
||||||
|
(SELECT COUNT(*) FROM network_outlet_ports WHERE port_type_id = ?) AS outlet_ports_count,
|
||||||
|
(SELECT COUNT(*) FROM floor_patchpanel_ports WHERE port_type_id = ?) AS patchpanel_ports_count",
|
||||||
|
"iiiii",
|
||||||
|
[$portTypeId, $portTypeId, $portTypeId, $portTypeId, $portTypeId]
|
||||||
|
);
|
||||||
|
|
||||||
|
$references = [
|
||||||
|
'Geraetetyp-Ports' => (int)($usage['device_type_ports_count'] ?? 0),
|
||||||
|
'Geraete-Ports' => (int)($usage['device_ports_count'] ?? 0),
|
||||||
|
'Modul-Ports' => (int)($usage['module_ports_count'] ?? 0),
|
||||||
|
'Netzwerkdosen-Ports' => (int)($usage['outlet_ports_count'] ?? 0),
|
||||||
|
'Patchpanel-Ports' => (int)($usage['patchpanel_ports_count'] ?? 0),
|
||||||
|
];
|
||||||
|
|
||||||
|
$inUse = array_filter($references, static fn ($count) => $count > 0);
|
||||||
|
if (!empty($inUse)) {
|
||||||
|
$parts = [];
|
||||||
|
foreach ($inUse as $label => $count) {
|
||||||
|
$parts[] = $label . ': ' . $count;
|
||||||
|
}
|
||||||
|
http_response_code(409);
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Porttyp wird noch verwendet (' . implode(', ', $parts) . ')'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$deleted = $sql->set(
|
||||||
|
"DELETE FROM port_types WHERE id = ?",
|
||||||
|
"i",
|
||||||
|
[$portTypeId]
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($deleted <= 0) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Porttyp konnte nicht geloescht werden']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Porttyp geloescht: ' . (string)$portType['name']
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
@@ -69,6 +69,13 @@ $portTypes = $sql->get(
|
|||||||
<td><small><?php echo htmlspecialchars($pt['comment'] ?? ''); ?></small></td>
|
<td><small><?php echo htmlspecialchars($pt['comment'] ?? ''); ?></small></td>
|
||||||
<td class="actions">
|
<td class="actions">
|
||||||
<a href="?module=port_types&action=edit&id=<?php echo (int)$pt['id']; ?>" class="button button-small">Bearbeiten</a>
|
<a href="?module=port_types&action=edit&id=<?php echo (int)$pt['id']; ?>" class="button button-small">Bearbeiten</a>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="button button-small button-danger js-port-type-delete"
|
||||||
|
data-port-type-id="<?php echo (int)$pt['id']; ?>"
|
||||||
|
data-port-type-name="<?php echo htmlspecialchars((string)$pt['name'], ENT_QUOTES, 'UTF-8'); ?>">
|
||||||
|
Loeschen
|
||||||
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
@@ -171,3 +178,36 @@ $portTypes = $sql->get(
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
document.querySelectorAll('.js-port-type-delete').forEach((button) => {
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
const id = Number(button.dataset.portTypeId || '0');
|
||||||
|
const name = button.dataset.portTypeName || 'Porttyp';
|
||||||
|
if (id <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!confirm('Porttyp "' + name + '" wirklich loeschen?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch('?module=port_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.reload();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
alert((data && data.message) ? data.message : 'Loeschen fehlgeschlagen');
|
||||||
|
})
|
||||||
|
.catch(() => alert('Loeschen fehlgeschlagen'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user