415 lines
11 KiB
PHP
415 lines
11 KiB
PHP
<?php
|
|
/**
|
|
* modules/devices/list.php
|
|
* Vollstaendige Geraeteuebersicht mit Filter
|
|
*/
|
|
|
|
// =========================
|
|
// Filter / Suche einlesen
|
|
// =========================
|
|
$search = trim($_GET['search'] ?? '');
|
|
$typeId = (int)($_GET['type_id'] ?? 0);
|
|
$locationId = (int)($_GET['location_id'] ?? 0);
|
|
$floorId = (int)($_GET['floor_id'] ?? 0);
|
|
$rackId = (int)($_GET['rack_id'] ?? 0);
|
|
|
|
// =========================
|
|
// WHERE-Clause dynamisch bauen
|
|
// =========================
|
|
$where = [];
|
|
$types = '';
|
|
$params = [];
|
|
|
|
if ($search !== '') {
|
|
$where[] = "(d.name LIKE ? OR d.serial_number LIKE ? OR dt.name LIKE ?)";
|
|
$types .= 'sss';
|
|
$params[] = "%$search%";
|
|
$params[] = "%$search%";
|
|
$params[] = "%$search%";
|
|
}
|
|
|
|
if ($typeId > 0) {
|
|
$where[] = 'd.device_type_id = ?';
|
|
$types .= 'i';
|
|
$params[] = $typeId;
|
|
}
|
|
|
|
if ($floorId > 0) {
|
|
$where[] = 'f.id = ?';
|
|
$types .= 'i';
|
|
$params[] = $floorId;
|
|
}
|
|
|
|
if ($rackId > 0) {
|
|
$where[] = 'd.rack_id = ?';
|
|
$types .= 'i';
|
|
$params[] = $rackId;
|
|
}
|
|
|
|
$whereSql = $where ? 'WHERE ' . implode(' AND ', $where) : '';
|
|
|
|
// =========================
|
|
// Geraete laden
|
|
// =========================
|
|
$devices = $sql->get(
|
|
"
|
|
SELECT
|
|
d.id,
|
|
d.name,
|
|
d.serial_number,
|
|
d.rack_position_he,
|
|
d.rack_height_he,
|
|
d.web_config_url,
|
|
dt.name AS device_type,
|
|
dt.image_path,
|
|
f.name AS floor_name,
|
|
r.name AS rack_name,
|
|
(
|
|
SELECT COUNT(*)
|
|
FROM device_ports dp
|
|
WHERE dp.device_id = d.id
|
|
) AS port_count,
|
|
(
|
|
SELECT COUNT(*)
|
|
FROM device_ports dp
|
|
WHERE dp.device_id = d.id
|
|
AND dp.status = 'active'
|
|
) AS active_port_count,
|
|
(
|
|
SELECT COUNT(*)
|
|
FROM device_ports dp
|
|
WHERE dp.device_id = d.id
|
|
AND dp.status = 'disabled'
|
|
) AS disabled_port_count,
|
|
(
|
|
SELECT COUNT(*)
|
|
FROM device_ports dp
|
|
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' OR c.port_a_type = 'device_ports') 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' OR c.port_b_type = 'device_ports') 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
|
|
JOIN device_types dt ON dt.id = d.device_type_id
|
|
LEFT JOIN racks r ON r.id = d.rack_id
|
|
LEFT JOIN floors f ON f.id = r.floor_id
|
|
$whereSql
|
|
ORDER BY f.name, r.name, d.rack_position_he, d.name
|
|
",
|
|
$types,
|
|
$params
|
|
);
|
|
|
|
// =========================
|
|
// Filter-Daten laden
|
|
// =========================
|
|
$deviceTypes = $sql->get('SELECT id, name FROM device_types 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', '', []);
|
|
?>
|
|
|
|
<div class="devices-container">
|
|
<h1>Geraete</h1>
|
|
|
|
<form method="get" class="filter-form">
|
|
<input type="hidden" name="module" value="devices">
|
|
<input type="hidden" name="action" value="list">
|
|
|
|
<input type="text" name="search" placeholder="Suche nach Name oder Seriennummer..."
|
|
value="<?php echo htmlspecialchars($search); ?>" class="search-input">
|
|
|
|
<select name="type_id">
|
|
<option value="">- Alle Typen -</option>
|
|
<?php foreach ($deviceTypes as $t): ?>
|
|
<option value="<?php echo $t['id']; ?>" <?php echo $t['id'] === $typeId ? 'selected' : ''; ?>>
|
|
<?php echo htmlspecialchars($t['name']); ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
|
|
<select name="floor_id">
|
|
<option value="">- Alle Stockwerke -</option>
|
|
<?php foreach ($floors as $f): ?>
|
|
<option value="<?php echo $f['id']; ?>" <?php echo $f['id'] === $floorId ? 'selected' : ''; ?>>
|
|
<?php echo htmlspecialchars($f['name']); ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
|
|
<select name="rack_id">
|
|
<option value="">- Alle Racks -</option>
|
|
<?php foreach ($racks as $r): ?>
|
|
<option value="<?php echo $r['id']; ?>" <?php echo $r['id'] === $rackId ? 'selected' : ''; ?>>
|
|
<?php echo htmlspecialchars($r['name']); ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
|
|
<button type="submit" class="button">Filter</button>
|
|
|
|
<a href="?module=devices&action=list" class="button">Reset</a>
|
|
|
|
<a href="?module=devices&action=edit" class="button button-primary" style="margin-left: auto;">
|
|
+ Neues Geraet
|
|
</a>
|
|
</form>
|
|
|
|
<?php if (!empty($devices)): ?>
|
|
<div class="device-stats">
|
|
<p>Gefundene Geraete: <strong><?php echo count($devices); ?></strong></p>
|
|
</div>
|
|
|
|
<table class="device-list">
|
|
<thead>
|
|
<tr>
|
|
<th>Name</th>
|
|
<th>Typ</th>
|
|
<th>Stockwerk</th>
|
|
<th>Rack</th>
|
|
<th>Position (HE)</th>
|
|
<th>Ports</th>
|
|
<th>Seriennummer</th>
|
|
<th>Webconfig</th>
|
|
<th>Aktionen</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($devices as $d): ?>
|
|
<tr>
|
|
<td>
|
|
<strong><?php echo htmlspecialchars($d['name']); ?></strong>
|
|
</td>
|
|
|
|
<td>
|
|
<?php echo htmlspecialchars($d['device_type']); ?>
|
|
</td>
|
|
|
|
<td>
|
|
<?php echo htmlspecialchars($d['floor_name'] ?? '-'); ?>
|
|
</td>
|
|
|
|
<td>
|
|
<?php echo htmlspecialchars($d['rack_name'] ?? '-'); ?>
|
|
</td>
|
|
|
|
<td>
|
|
<?php
|
|
if ($d['rack_position_he']) {
|
|
echo (int)$d['rack_position_he'];
|
|
if ($d['rack_height_he']) {
|
|
echo '-' . ((int)$d['rack_position_he'] + (int)$d['rack_height_he'] - 1);
|
|
}
|
|
} else {
|
|
echo '-';
|
|
}
|
|
?>
|
|
</td>
|
|
|
|
<td>
|
|
<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>
|
|
<?php if (!empty($d['web_config_url'])): ?>
|
|
<a href="<?php echo htmlspecialchars($d['web_config_url']); ?>" target="_blank" rel="noopener noreferrer" class="button button-small">
|
|
Webconfig
|
|
</a>
|
|
<?php else: ?>
|
|
-
|
|
<?php endif; ?>
|
|
</td>
|
|
|
|
<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=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>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
|
|
<?php else: ?>
|
|
<div class="empty-state">
|
|
<p>Keine Geraete gefunden.</p>
|
|
<p>
|
|
<a href="?module=devices&action=edit" class="button button-primary">
|
|
Erstes Geraet anlegen
|
|
</a>
|
|
</p>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<style>
|
|
.devices-container {
|
|
padding: 20px;
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.filter-form {
|
|
display: flex;
|
|
gap: 10px;
|
|
margin: 20px 0;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
}
|
|
|
|
.filter-form input,
|
|
.filter-form select {
|
|
padding: 8px 12px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
font-family: inherit;
|
|
}
|
|
|
|
.search-input {
|
|
flex: 1;
|
|
min-width: 250px;
|
|
}
|
|
|
|
.device-stats {
|
|
background: #f0f0f0;
|
|
padding: 10px 15px;
|
|
border-radius: 4px;
|
|
margin: 15px 0;
|
|
}
|
|
|
|
.device-list {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin: 15px 0;
|
|
}
|
|
|
|
.device-list th {
|
|
background: #f5f5f5;
|
|
padding: 12px;
|
|
text-align: left;
|
|
border-bottom: 2px solid #ddd;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.device-list td {
|
|
padding: 12px;
|
|
border-bottom: 1px solid #ddd;
|
|
}
|
|
|
|
.device-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>
|
|
|
|
<script>
|
|
function confirmDelete(link, id, connectionCount, portCount, moduleCount) {
|
|
if (!confirm('Dieses Geraet wirklich loeschen?')) {
|
|
return false;
|
|
}
|
|
|
|
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;
|
|
}
|
|
</script>
|