382 lines
12 KiB
PHP
382 lines
12 KiB
PHP
<?php
|
|
/**
|
|
* app/api/connections.php
|
|
*/
|
|
|
|
require_once __DIR__ . '/../bootstrap.php';
|
|
requireAuth();
|
|
|
|
header('Content-Type: application/json');
|
|
|
|
$action = $_GET['action'] ?? 'load';
|
|
|
|
switch ($action) {
|
|
case 'load':
|
|
loadConnections($sql);
|
|
break;
|
|
case 'save':
|
|
saveConnection($sql);
|
|
break;
|
|
case 'delete':
|
|
deleteConnection($sql);
|
|
break;
|
|
default:
|
|
jsonError('Unbekannte Aktion', 400);
|
|
}
|
|
|
|
function jsonError(string $message, int $status = 400): void
|
|
{
|
|
http_response_code($status);
|
|
echo json_encode(['error' => $message]);
|
|
exit;
|
|
}
|
|
|
|
function normalizeEndpointType(string $type): ?string
|
|
{
|
|
$map = [
|
|
'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;
|
|
}
|
|
|
|
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 isTopologyPairAllowed(string $typeA, string $typeB): bool
|
|
{
|
|
$allowed = ['device' => true, 'module' => true, 'outlet' => true, 'patchpanel' => true];
|
|
if (!isset($allowed[$typeA]) || !isset($allowed[$typeB])) {
|
|
return false;
|
|
}
|
|
if ($typeA === 'patchpanel' || $typeB === 'patchpanel') {
|
|
return ($typeA === 'patchpanel' && in_array($typeB, ['patchpanel', 'outlet'], true))
|
|
|| ($typeB === 'patchpanel' && in_array($typeA, ['patchpanel', 'outlet'], true));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function buildEndpointUsageMap($sql, int $excludeConnectionId = 0): array
|
|
{
|
|
$usage = [
|
|
'device' => [],
|
|
'module' => [],
|
|
'outlet' => [],
|
|
'patchpanel' => [],
|
|
];
|
|
|
|
$rows = $sql->get(
|
|
"SELECT id, port_a_type, port_a_id, port_b_type, port_b_id
|
|
FROM connections
|
|
WHERE id <> ?",
|
|
'i',
|
|
[$excludeConnectionId]
|
|
);
|
|
|
|
$track = static function (string $endpointType, int $endpointId, string $otherType) use (&$usage): void {
|
|
if ($endpointId <= 0 || !isset($usage[$endpointType])) {
|
|
return;
|
|
}
|
|
if (!isset($usage[$endpointType][$endpointId])) {
|
|
$usage[$endpointType][$endpointId] = [
|
|
'total' => 0,
|
|
'fixed' => 0,
|
|
'patch' => 0,
|
|
];
|
|
}
|
|
|
|
$usage[$endpointType][$endpointId]['total']++;
|
|
if (in_array($endpointType, ['outlet', 'patchpanel'], true)) {
|
|
if (in_array($otherType, ['outlet', 'patchpanel'], true)) {
|
|
$usage[$endpointType][$endpointId]['fixed']++;
|
|
} elseif (in_array($otherType, ['device', 'module'], true)) {
|
|
$usage[$endpointType][$endpointId]['patch']++;
|
|
}
|
|
}
|
|
};
|
|
|
|
foreach ((array)$rows as $row) {
|
|
$typeA = normalizeEndpointType((string)($row['port_a_type'] ?? ''));
|
|
$typeB = normalizeEndpointType((string)($row['port_b_type'] ?? ''));
|
|
$idA = (int)($row['port_a_id'] ?? 0);
|
|
$idB = (int)($row['port_b_id'] ?? 0);
|
|
if ($typeA === null || $typeB === null) {
|
|
continue;
|
|
}
|
|
$track($typeA, $idA, $typeB);
|
|
$track($typeB, $idB, $typeA);
|
|
}
|
|
|
|
return $usage;
|
|
}
|
|
|
|
function validateEndpointCapacity(array $usage, string $endpointType, int $endpointId, string $otherType, string $label): ?string
|
|
{
|
|
if ($endpointId <= 0) {
|
|
return null;
|
|
}
|
|
|
|
$stats = $usage[$endpointType][$endpointId] ?? ['total' => 0, 'fixed' => 0, 'patch' => 0];
|
|
if ((int)$stats['total'] <= 0) {
|
|
return null;
|
|
}
|
|
|
|
if (in_array($endpointType, ['outlet', 'patchpanel'], true)) {
|
|
if ((int)$stats['total'] >= 2) {
|
|
return $label . ' hat bereits die maximale Anzahl von 2 Verbindungen';
|
|
}
|
|
if (in_array($otherType, ['outlet', 'patchpanel'], true) && (int)$stats['fixed'] >= 1) {
|
|
return $label . ' hat bereits eine feste Verdrahtung';
|
|
}
|
|
if (in_array($otherType, ['device', 'module'], true) && (int)$stats['patch'] >= 1) {
|
|
return $label . ' hat bereits ein Patchkabel';
|
|
}
|
|
return null;
|
|
}
|
|
|
|
return $label . ' ist bereits in Verwendung';
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
$devices = $sql->get(
|
|
"SELECT d.id, d.name, d.device_type_id, d.rack_id, 0 AS pos_x, 0 AS pos_y
|
|
FROM devices d
|
|
LEFT JOIN racks r ON r.id = d.rack_id
|
|
LEFT JOIN floors f ON f.id = r.floor_id
|
|
LEFT JOIN buildings b ON b.id = f.building_id" . $where,
|
|
$bindType,
|
|
$bindValues
|
|
);
|
|
|
|
$ports = $sql->get(
|
|
"SELECT dp.id, dp.device_id, dp.name, dp.port_type_id
|
|
FROM device_ports dp
|
|
JOIN devices d ON d.id = dp.device_id
|
|
LEFT JOIN racks r ON r.id = d.rack_id
|
|
LEFT JOIN floors f ON f.id = r.floor_id
|
|
LEFT JOIN buildings b ON b.id = f.building_id" . $where,
|
|
$bindType,
|
|
$bindValues
|
|
);
|
|
|
|
$connections = $sql->get(
|
|
"SELECT id, connection_type_id, port_a_type, port_a_id, port_b_type, port_b_id, vlan_config, mode, comment
|
|
FROM connections",
|
|
'',
|
|
[]
|
|
);
|
|
|
|
echo json_encode([
|
|
'devices' => $devices ?: [],
|
|
'ports' => $ports ?: [],
|
|
'connections' => $connections ?: []
|
|
]);
|
|
}
|
|
|
|
function resolveConnectionTypeId($sql, array $data): int
|
|
{
|
|
if (!empty($data['connection_type_id'])) {
|
|
$requestedId = (int)$data['connection_type_id'];
|
|
$exists = $sql->single('SELECT id FROM connection_types WHERE id = ?', 'i', [$requestedId]);
|
|
if (!$exists) {
|
|
jsonError('connection_type_id existiert nicht', 400);
|
|
}
|
|
return $requestedId;
|
|
}
|
|
|
|
$defaultType = $sql->single('SELECT id FROM connection_types ORDER BY id ASC LIMIT 1');
|
|
if (!$defaultType) {
|
|
jsonError('Kein Verbindungstyp vorhanden', 400);
|
|
}
|
|
|
|
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 (!isTopologyPairAllowed($portAType, $portBType)) {
|
|
jsonError('Patchpanel-Ports duerfen nur mit Patchpanel-Ports oder Netzwerkdosen-Ports verbunden werden', 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;
|
|
|
|
$connectionId = !empty($data['id']) ? (int)$data['id'] : 0;
|
|
$usage = buildEndpointUsageMap($sql, $connectionId);
|
|
$capacityErrorA = validateEndpointCapacity($usage, $portAType, $portAId, $portBType, 'Port an Endpunkt A');
|
|
if ($capacityErrorA !== null) {
|
|
jsonError($capacityErrorA, 409);
|
|
}
|
|
$capacityErrorB = validateEndpointCapacity($usage, $portBType, $portBId, $portAType, 'Port an Endpunkt B');
|
|
if ($capacityErrorB !== null) {
|
|
jsonError($capacityErrorB, 409);
|
|
}
|
|
|
|
if ($connectionId > 0) {
|
|
$id = $connectionId;
|
|
$existing = $sql->single('SELECT id FROM connections WHERE id = ?', 'i', [$connectionId]);
|
|
if (!$existing) {
|
|
jsonError('Verbindung existiert nicht', 404);
|
|
}
|
|
|
|
$rows = $sql->set(
|
|
'UPDATE connections
|
|
SET connection_type_id = ?, port_a_type = ?, port_a_id = ?, port_b_type = ?, port_b_id = ?, vlan_config = ?, mode = ?, comment = ?
|
|
WHERE id = ?',
|
|
'isisisssi',
|
|
[$connectionTypeId, $portAType, $portAId, $portBType, $portBId, $vlanConfig, $mode, $comment, $id]
|
|
);
|
|
|
|
if ($rows === false) {
|
|
jsonError('Update fehlgeschlagen', 500);
|
|
}
|
|
|
|
echo json_encode(['status' => 'updated', 'rows' => $rows]);
|
|
return;
|
|
}
|
|
|
|
$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)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
|
|
'isisisss',
|
|
[$connectionTypeId, $portAType, $portAId, $portBType, $portBId, $vlanConfig, $mode, $comment],
|
|
true
|
|
);
|
|
|
|
if ($id === false) {
|
|
jsonError('Insert fehlgeschlagen', 500);
|
|
}
|
|
|
|
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]);
|
|
}
|