Files
netwatch/app/modules/connections/list.php
2026-02-16 09:35:42 +01:00

388 lines
14 KiB
PHP

<?php
/**
* app/modules/connections/list.php
*
* Übersicht der Netzwerkverbindungen
* - Tabellarische Liste aller Verbindungen
* - Filter nach Geräten, VLANs, Status
* - Später: Visuelle Netzwerk-Topologie
*/
// =========================
// Filter einlesen
// =========================
$search = trim($_GET['search'] ?? '');
$deviceId = (int)($_GET['device_id'] ?? 0);
// Einheitliche Endpunkt-Aufloesung fuer polymorphe Port-Typen.
$endpointUnionSql = "
SELECT
'device' AS endpoint_type,
dp.id AS endpoint_id,
dp.name AS port_name,
d.name AS owner_name,
d.id AS owner_device_id
FROM device_ports dp
JOIN devices d ON d.id = dp.device_id
UNION ALL
SELECT
'module' AS endpoint_type,
mp.id AS endpoint_id,
mp.name AS port_name,
CONCAT(IFNULL(MIN(d.name), 'Unzugeordnet'), ' / ', m.name) AS owner_name,
MIN(d.id) AS owner_device_id
FROM module_ports mp
JOIN modules m ON m.id = mp.module_id
LEFT JOIN device_port_modules dpm ON dpm.module_id = m.id
LEFT JOIN device_ports dp ON dp.id = dpm.device_port_id
LEFT JOIN devices d ON d.id = dp.device_id
GROUP BY mp.id, mp.name, m.name
UNION ALL
SELECT
'outlet' AS endpoint_type,
nop.id AS endpoint_id,
nop.name AS port_name,
CONCAT(no.name, ' / ', IFNULL(r.name, ''), ' / ', IFNULL(f.name, '')) AS owner_name,
NULL AS owner_device_id
FROM network_outlet_ports nop
JOIN network_outlets no ON no.id = nop.outlet_id
LEFT JOIN rooms r ON r.id = no.room_id
LEFT JOIN floors f ON f.id = r.floor_id
UNION ALL
SELECT
'floor_patchpanel' AS endpoint_type,
fpp.id AS endpoint_id,
fpp.name AS port_name,
CONCAT(fp.name, ' / ', IFNULL(f.name, '')) AS owner_name,
NULL AS owner_device_id
FROM floor_patchpanel_ports fpp
JOIN floor_patchpanels fp ON fp.id = fpp.patchpanel_id
LEFT JOIN floors f ON f.id = fp.floor_id
";
// =========================
// WHERE-Clause bauen
// =========================
$where = [];
$types = '';
$params = [];
if ($search !== '') {
$where[] = "(e1.owner_name LIKE ? OR e2.owner_name LIKE ? OR e1.port_name LIKE ? OR e2.port_name LIKE ?)";
$types .= "ssss";
$params[] = "%$search%";
$params[] = "%$search%";
$params[] = "%$search%";
$params[] = "%$search%";
}
if ($deviceId > 0) {
$where[] = "(e1.owner_device_id = ? OR e2.owner_device_id = ?)";
$types .= "ii";
$params[] = $deviceId;
$params[] = $deviceId;
}
$whereSql = $where ? "WHERE " . implode(" AND ", $where) : "";
// =========================
// Verbindungen laden
// =========================
$connections = $sql->get(
"SELECT
c.id,
c.port_a_type, c.port_a_id, c.port_b_type, c.port_b_id,
e1.owner_name AS endpoint_a_name,
e2.owner_name AS endpoint_b_name,
e1.port_name AS port_a_name,
e2.port_name AS port_b_name,
c.vlan_config,
c.comment
FROM connections c
LEFT JOIN ($endpointUnionSql) e1
ON c.port_a_id = e1.endpoint_id
AND (
c.port_a_type = e1.endpoint_type
OR (c.port_a_type = 'device_ports' AND e1.endpoint_type = 'device')
OR (c.port_a_type = 'module_ports' AND e1.endpoint_type = 'module')
OR (c.port_a_type = 'network_outlet_ports' AND e1.endpoint_type = 'outlet')
OR (c.port_a_type = 'floor_patchpanel_ports' AND e1.endpoint_type = 'floor_patchpanel')
OR (c.port_a_type = 'patchpanel' AND e1.endpoint_type = 'floor_patchpanel')
)
LEFT JOIN ($endpointUnionSql) e2
ON c.port_b_id = e2.endpoint_id
AND (
c.port_b_type = e2.endpoint_type
OR (c.port_b_type = 'device_ports' AND e2.endpoint_type = 'device')
OR (c.port_b_type = 'module_ports' AND e2.endpoint_type = 'module')
OR (c.port_b_type = 'network_outlet_ports' AND e2.endpoint_type = 'outlet')
OR (c.port_b_type = 'floor_patchpanel_ports' AND e2.endpoint_type = 'floor_patchpanel')
OR (c.port_b_type = 'patchpanel' AND e2.endpoint_type = 'floor_patchpanel')
)
$whereSql
ORDER BY e1.owner_name, e2.owner_name",
$types,
$params
);
// =========================
// Filter-Daten
// =========================
$devices = $sql->get("SELECT id, name FROM devices ORDER BY name", "", []);
$selectedDevice = null;
$selectedDevicePorts = [];
$selectedDeviceVlans = [];
if ($deviceId > 0) {
$selectedDevice = $sql->single(
"SELECT d.id, d.name, dt.name AS type_name
FROM devices d
LEFT JOIN device_types dt ON d.device_type_id = dt.id
WHERE d.id = ?",
"i",
[$deviceId]
);
if ($selectedDevice) {
$selectedDevice['port_count'] = (int)($sql->single(
"SELECT COUNT(*) AS cnt
FROM (
SELECT dp.id
FROM device_ports dp
WHERE dp.device_id = ?
UNION ALL
SELECT DISTINCT mp.id
FROM module_ports mp
JOIN modules m ON m.id = mp.module_id
JOIN device_port_modules dpm ON dpm.module_id = m.id
JOIN device_ports dp ON dp.id = dpm.device_port_id
WHERE dp.device_id = ?
) p",
"ii",
[$deviceId, $deviceId]
)['cnt'] ?? 0);
$selectedDevice['connection_count'] = (int)($sql->single(
"SELECT COUNT(DISTINCT c.id) AS cnt
FROM connections c
LEFT JOIN ($endpointUnionSql) e1
ON c.port_a_id = e1.endpoint_id
AND (
c.port_a_type = e1.endpoint_type
OR (c.port_a_type = 'device_ports' AND e1.endpoint_type = 'device')
OR (c.port_a_type = 'module_ports' AND e1.endpoint_type = 'module')
OR (c.port_a_type = 'network_outlet_ports' AND e1.endpoint_type = 'outlet')
OR (c.port_a_type = 'floor_patchpanel_ports' AND e1.endpoint_type = 'floor_patchpanel')
OR (c.port_a_type = 'patchpanel' AND e1.endpoint_type = 'floor_patchpanel')
)
LEFT JOIN ($endpointUnionSql) e2
ON c.port_b_id = e2.endpoint_id
AND (
c.port_b_type = e2.endpoint_type
OR (c.port_b_type = 'device_ports' AND e2.endpoint_type = 'device')
OR (c.port_b_type = 'module_ports' AND e2.endpoint_type = 'module')
OR (c.port_b_type = 'network_outlet_ports' AND e2.endpoint_type = 'outlet')
OR (c.port_b_type = 'floor_patchpanel_ports' AND e2.endpoint_type = 'floor_patchpanel')
OR (c.port_b_type = 'patchpanel' AND e2.endpoint_type = 'floor_patchpanel')
)
WHERE e1.owner_device_id = ? OR e2.owner_device_id = ?",
"ii",
[$deviceId, $deviceId]
)['cnt'] ?? 0);
$selectedDevicePorts = $sql->get(
"SELECT name, vlan_config
FROM (
SELECT dp.name, dp.vlan_config, dp.id AS sort_id
FROM device_ports dp
WHERE dp.device_id = ?
UNION ALL
SELECT DISTINCT CONCAT(m.name, ' / ', mp.name) AS name, NULL AS vlan_config, (1000000 + mp.id) AS sort_id
FROM module_ports mp
JOIN modules m ON m.id = mp.module_id
JOIN device_port_modules dpm ON dpm.module_id = m.id
JOIN device_ports dp ON dp.id = dpm.device_port_id
WHERE dp.device_id = ?
) p
ORDER BY sort_id
LIMIT 12",
"ii",
[$deviceId, $deviceId]
);
foreach ($selectedDevicePorts as $port) {
if (empty($port['vlan_config'])) {
continue;
}
$vlans = json_decode($port['vlan_config'], true);
foreach ((array)$vlans as $vlan) {
$vlan = trim((string)$vlan);
if ($vlan !== '') {
$selectedDeviceVlans[$vlan] = true;
}
}
}
$selectedDeviceVlans = array_keys($selectedDeviceVlans);
natcasesort($selectedDeviceVlans);
}
}
?>
<div class="connections-container">
<h1>Netzwerkverbindungen</h1>
<!-- =========================
Filter-Toolbar
========================= -->
<div class="filter-form">
<form method="GET">
<input type="hidden" name="module" value="connections">
<input type="hidden" name="action" value="list">
<input type="text" name="search" placeholder="Suche nach Gerät oder Port…"
value="<?php echo htmlspecialchars($search); ?>" class="search-input">
<select name="device_id">
<option value="">- Alle Geräte -</option>
<?php foreach ($devices as $device): ?>
<option value="<?php echo $device['id']; ?>"
<?php echo $device['id'] === $deviceId ? 'selected' : ''; ?>>
<?php echo htmlspecialchars($device['name']); ?>
</option>
<?php endforeach; ?>
</select>
<button type="submit" class="button">Filter</button>
<a href="?module=connections&action=list" class="button">Reset</a>
<a href="?module=connections&action=save" class="button button-primary">+ Neue Verbindung</a>
</form>
</div>
<!-- =========================
Verbindungs-Tabelle
========================= -->
<?php if (!empty($connections)): ?>
<table class="connections-list">
<thead>
<tr>
<th>Von (Gerät → Port)</th>
<th>Nach (Gerät → Port)</th>
<th>VLANs</th>
<th>Beschreibung</th>
<th>Status</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
<?php foreach ($connections as $conn): ?>
<?php
$comment = trim($conn['comment'] ?? '');
$hasMissingInfo = empty($conn['endpoint_a_name']) || empty($conn['endpoint_b_name'])
|| empty($conn['port_a_name']) || empty($conn['port_b_name']);
$commentLower = mb_strtolower($comment, 'UTF-8');
$warningFromComment = preg_match('/warn|achtung|critical/', $commentLower);
$hasWarning = $hasMissingInfo || $warningFromComment;
?>
<tr>
<td>
<strong><?php echo htmlspecialchars($conn['endpoint_a_name'] ?? 'N/A'); ?></strong><br>
<small><?php echo htmlspecialchars($conn['port_a_name'] ?? '—'); ?></small>
</td>
<td>
<strong><?php echo htmlspecialchars($conn['endpoint_b_name'] ?? 'N/A'); ?></strong><br>
<small><?php echo htmlspecialchars($conn['port_b_name'] ?? '—'); ?></small>
</td>
<td>
<small>
<?php
if ($conn['vlan_config']) {
$vlan = json_decode($conn['vlan_config'], true);
echo htmlspecialchars(implode(', ', (array)$vlan));
} else {
echo '—';
}
?>
</small>
</td>
<td>
<small><?php echo htmlspecialchars($conn['comment'] ?? ''); ?></small>
</td>
<td class="status-cell">
<?php if ($hasWarning): ?>
<span class="status-badge status-badge-warning" title="Unvollständige oder kritische Verbindung">
⚠️ Warnung
</span>
<?php else: ?>
<span class="status-badge status-badge-ok" title="Verbindung vollständig">
✔️ OK
</span>
<?php endif; ?>
</td>
<td class="actions">
<a href="?module=connections&action=edit&id=<?php echo $conn['id']; ?>" class="button button-small">Bearbeiten</a>
<a href="#" class="button button-small button-danger"
data-confirm-delete="true"
data-confirm-message="Diese Verbindung wirklich löschen?"
data-confirm-feedback="Löschen noch nicht implementiert">
Löschen
</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php else: ?>
<div class="empty-state">
<p>Keine Verbindungen gefunden.</p>
<p>
<a href="?module=connections&action=save" class="button button-primary">
Erste Verbindung anlegen
</a>
</p>
</div>
<?php endif; ?>
</div>
<!-- =========================
Sidebar / Details
========================= -->
<aside class="sidebar">
<?php if ($selectedDevice): ?>
<h3>Ausgewähltes Gerät</h3>
<p><strong><?php echo htmlspecialchars($selectedDevice['name']); ?></strong></p>
<p>Typ: <?php echo htmlspecialchars($selectedDevice['type_name'] ?? '—'); ?></p>
<p>Ports: <?php echo (int)$selectedDevice['port_count']; ?></p>
<p>Verbindungen: <?php echo (int)$selectedDevice['connection_count']; ?></p>
<p>
VLANs:
<?php if (!empty($selectedDeviceVlans)): ?>
<?php echo htmlspecialchars(implode(', ', $selectedDeviceVlans)); ?>
<?php else: ?>
<?php endif; ?>
</p>
<?php if (!empty($selectedDevicePorts)): ?>
<h4>Ports (max. 12)</h4>
<ul>
<?php foreach ($selectedDevicePorts as $port): ?>
<li><?php echo htmlspecialchars($port['name']); ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<?php else: ?>
<p><em>Bitte ein Gerät im Filter auswählen.</em></p>
<?php endif; ?>
<!-- TODO: Verbindung bearbeiten / löschen -->
</aside>