adasd
This commit is contained in:
@@ -6,7 +6,6 @@ use App\Repository\KeaHostMetadataRepository;
|
|||||||
$module = modules()->get('kea');
|
$module = modules()->get('kea');
|
||||||
$fallback = $module['db_defaults'] ?? [];
|
$fallback = $module['db_defaults'] ?? [];
|
||||||
|
|
||||||
$pdo = modules()->modulePdo('kea', $fallback);
|
|
||||||
$settings = modules()->settings('kea');
|
$settings = modules()->settings('kea');
|
||||||
$metadataFallback = is_array($module['metadata_db_defaults'] ?? null) ? $module['metadata_db_defaults'] : [];
|
$metadataFallback = is_array($module['metadata_db_defaults'] ?? null) ? $module['metadata_db_defaults'] : [];
|
||||||
$metadataConfig = is_array($settings['metadata_db'] ?? null)
|
$metadataConfig = is_array($settings['metadata_db'] ?? null)
|
||||||
@@ -15,11 +14,18 @@ $metadataConfig = is_array($settings['metadata_db'] ?? null)
|
|||||||
$metadataRepo = null;
|
$metadataRepo = null;
|
||||||
$hosts = [];
|
$hosts = [];
|
||||||
$error = null;
|
$error = null;
|
||||||
|
$warnings = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
$pdo = modules()->modulePdo('kea', $fallback);
|
||||||
if (!empty($metadataConfig['driver']) && !empty($metadataConfig['dbname'])) {
|
if (!empty($metadataConfig['driver']) && !empty($metadataConfig['dbname'])) {
|
||||||
|
try {
|
||||||
$metadataRepo = new KeaHostMetadataRepository(Database::createFromArray($metadataConfig));
|
$metadataRepo = new KeaHostMetadataRepository(Database::createFromArray($metadataConfig));
|
||||||
$metadataRepo->ensureSchema();
|
$metadataRepo->ensureSchema();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$warnings[] = 'Nexus DHCP Zusatzdatenbank nicht verfuegbar: ' . $e->getMessage();
|
||||||
|
$metadataRepo = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$repo = new KeaHostRepository($pdo, $metadataRepo);
|
$repo = new KeaHostRepository($pdo, $metadataRepo);
|
||||||
@@ -28,4 +34,4 @@ try {
|
|||||||
$error = "Datenbankfehler: " . $e->getMessage();
|
$error = "Datenbankfehler: " . $e->getMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
module_tpl('kea', 'dashboard', compact('hosts', 'error'));
|
module_tpl('kea', 'dashboard', compact('hosts', 'error', 'warnings'));
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
/**
|
/**
|
||||||
* @var array $hosts Die Liste der KEA-Hosts.
|
* @var array $hosts Die Liste der KEA-Hosts.
|
||||||
* @var string|null $error Eine Fehlermeldung, falls vorhanden.
|
* @var string|null $error Eine Fehlermeldung, falls vorhanden.
|
||||||
|
* @var array $warnings Hinweise, falls Zusatzdaten nicht geladen werden konnten.
|
||||||
*/
|
*/
|
||||||
?>
|
?>
|
||||||
<div class="px-4 py-6 sm:px-0">
|
<div class="px-4 py-6 sm:px-0">
|
||||||
@@ -19,6 +20,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php foreach (($warnings ?? []) as $warning): ?>
|
||||||
|
<div class="bg-yellow-900 border-l-4 border-yellow-500 text-yellow-100 p-4 mb-6" role="alert">
|
||||||
|
<p><?= e((string)$warning) ?></p>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
|
||||||
<div class="bg-gray-800 shadow overflow-hidden sm:rounded-lg border border-gray-700">
|
<div class="bg-gray-800 shadow overflow-hidden sm:rounded-lg border border-gray-700">
|
||||||
<div class="px-4 py-5 sm:px-6 border-b border-gray-700">
|
<div class="px-4 py-5 sm:px-6 border-b border-gray-700">
|
||||||
<h3 class="text-lg leading-6 font-medium text-gray-200">
|
<h3 class="text-lg leading-6 font-medium text-gray-200">
|
||||||
@@ -32,6 +39,7 @@
|
|||||||
<table class="min-w-full divide-y divide-gray-700">
|
<table class="min-w-full divide-y divide-gray-700">
|
||||||
<thead class="bg-gray-900">
|
<thead class="bg-gray-900">
|
||||||
<tr>
|
<tr>
|
||||||
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">Quelle</th>
|
||||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">Hostname</th>
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">Hostname</th>
|
||||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">IP Adresse</th>
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">IP Adresse</th>
|
||||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">MAC Adresse</th>
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">MAC Adresse</th>
|
||||||
@@ -45,11 +53,16 @@
|
|||||||
<tbody class="bg-gray-800 divide-y divide-gray-700">
|
<tbody class="bg-gray-800 divide-y divide-gray-700">
|
||||||
<?php if (empty($hosts)): ?>
|
<?php if (empty($hosts)): ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="6" class="px-6 py-4 text-center text-sm text-gray-500">Keine Hosts gefunden.</td>
|
<td colspan="7" class="px-6 py-4 text-center text-sm text-gray-500">
|
||||||
|
Keine Reservierungen oder aktiven Leases gefunden.
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<?php foreach ($hosts as $host): ?>
|
<?php foreach ($hosts as $host): ?>
|
||||||
<tr class="hover:bg-gray-750 transition-colors">
|
<tr class="hover:bg-gray-750 transition-colors">
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-300">
|
||||||
|
<?= ($host['source'] ?? '') === 'lease' ? 'Lease' : 'Reservierung' ?>
|
||||||
|
</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-white">
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-white">
|
||||||
<?= e($host['hostname'] ?: 'Unbekannt') ?>
|
<?= e($host['hostname'] ?: 'Unbekannt') ?>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -22,21 +22,42 @@ final class KeaHostRepository
|
|||||||
public function findAll(int $limit = 50): array
|
public function findAll(int $limit = 50): array
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
// 'dhcp_identifier' ist in KEA i.d.R. die MAC-Adresse (bei type=1)
|
$hasReservations = $this->tableExists('hosts');
|
||||||
$stmt = $this->pdo->prepare(
|
$hasLeases = $this->tableExists('lease4');
|
||||||
'SELECT host_id, dhcp_identifier, ipv4_address, hostname, user_context
|
if (!$hasReservations && !$hasLeases) {
|
||||||
FROM hosts
|
throw new \RuntimeException(
|
||||||
ORDER BY host_id DESC
|
'Im aktuellen KEA-DB-Schema wurden weder hosts noch lease4 gefunden. Bitte Datenbank, Schema/Search-Path und Benutzerrechte pruefen.'
|
||||||
LIMIT :limit'
|
|
||||||
);
|
);
|
||||||
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
}
|
||||||
$stmt->execute();
|
|
||||||
|
|
||||||
return $this->withMetadata($stmt->fetchAll(PDO::FETCH_ASSOC));
|
$hosts = [];
|
||||||
|
if ($hasReservations) {
|
||||||
|
foreach ($this->findReservations($limit) as $host) {
|
||||||
|
$hosts[] = $host;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($hasLeases) {
|
||||||
|
foreach ($this->findLeases($limit) as $lease) {
|
||||||
|
$hosts[] = $lease;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
usort($hosts, static function (array $a, array $b): int {
|
||||||
|
$aTime = (string)($a['sort_time'] ?? '');
|
||||||
|
$bTime = (string)($b['sort_time'] ?? '');
|
||||||
|
if ($aTime !== '' || $bTime !== '') {
|
||||||
|
return strcmp($bTime, $aTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int)($b['sort_id'] ?? 0) <=> (int)($a['sort_id'] ?? 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
return array_slice($this->withMetadata($hosts), 0, $limit);
|
||||||
} catch (\PDOException $e) {
|
} catch (\PDOException $e) {
|
||||||
if ($this->isMissingTable($e)) {
|
if ($this->isMissingTable($e)) {
|
||||||
throw new \RuntimeException(
|
throw new \RuntimeException(
|
||||||
'KEA schema not initialized. Enable APP_DB_AUTO_INIT or run kea-admin db-init pgsql.',
|
'KEA schema not initialized or expected tables are missing. Expected hosts and/or lease4.',
|
||||||
0,
|
0,
|
||||||
$e
|
$e
|
||||||
);
|
);
|
||||||
@@ -45,6 +66,67 @@ final class KeaHostRepository
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function findReservations(int $limit): array
|
||||||
|
{
|
||||||
|
if (!$this->tableExists('hosts')) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$driver = $this->driver();
|
||||||
|
$macExpr = $this->hexExpression('dhcp_identifier');
|
||||||
|
$ipExpr = $this->ipv4Expression('ipv4_address');
|
||||||
|
$sortExpr = $driver === 'pgsql' ? 'host_id::text' : 'CAST(host_id AS CHAR)';
|
||||||
|
|
||||||
|
$stmt = $this->pdo->prepare(
|
||||||
|
"SELECT
|
||||||
|
host_id,
|
||||||
|
{$macExpr} AS dhcp_identifier_hex,
|
||||||
|
{$ipExpr} AS ipv4_address_text,
|
||||||
|
hostname,
|
||||||
|
user_context,
|
||||||
|
'reservation' AS source,
|
||||||
|
host_id AS sort_id,
|
||||||
|
{$sortExpr} AS sort_time
|
||||||
|
FROM hosts
|
||||||
|
ORDER BY host_id DESC
|
||||||
|
LIMIT :limit"
|
||||||
|
);
|
||||||
|
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
return array_map(fn(array $row): array => $this->normalizeRow($row), $stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function findLeases(int $limit): array
|
||||||
|
{
|
||||||
|
if (!$this->tableExists('lease4')) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$macExpr = $this->hexExpression('hwaddr');
|
||||||
|
$ipExpr = $this->ipv4Expression('address');
|
||||||
|
$expireExpr = $this->driver() === 'pgsql' ? 'expire::text' : 'CAST(expire AS CHAR)';
|
||||||
|
|
||||||
|
$stmt = $this->pdo->prepare(
|
||||||
|
"SELECT
|
||||||
|
address AS host_id,
|
||||||
|
{$macExpr} AS dhcp_identifier_hex,
|
||||||
|
{$ipExpr} AS ipv4_address_text,
|
||||||
|
hostname,
|
||||||
|
user_context,
|
||||||
|
'lease' AS source,
|
||||||
|
address AS sort_id,
|
||||||
|
{$expireExpr} AS sort_time
|
||||||
|
FROM lease4
|
||||||
|
ORDER BY expire DESC
|
||||||
|
LIMIT :limit"
|
||||||
|
);
|
||||||
|
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
return array_map(fn(array $row): array => $this->normalizeRow($row), $stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sucht einen Host anhand der MAC-Adresse (dhcp_identifier).
|
* Sucht einen Host anhand der MAC-Adresse (dhcp_identifier).
|
||||||
*/
|
*/
|
||||||
@@ -78,7 +160,7 @@ final class KeaHostRepository
|
|||||||
|
|
||||||
private function isMissingTable(\PDOException $e): bool
|
private function isMissingTable(\PDOException $e): bool
|
||||||
{
|
{
|
||||||
return $e->getCode() === '42P01';
|
return in_array((string)$e->getCode(), ['42P01', '42S02'], true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -119,10 +201,15 @@ final class KeaHostRepository
|
|||||||
return $hosts;
|
return $hosts;
|
||||||
}
|
}
|
||||||
|
|
||||||
$metadataByHost = $this->metadata->findByHostIds(array_column($hosts, 'host_id'));
|
$metadataByHost = $this->metadata->findByHostIds(
|
||||||
|
array_column(
|
||||||
|
array_filter($hosts, static fn(array $host): bool => ($host['source'] ?? '') === 'reservation'),
|
||||||
|
'host_id'
|
||||||
|
)
|
||||||
|
);
|
||||||
foreach ($hosts as &$host) {
|
foreach ($hosts as &$host) {
|
||||||
$hostId = (int)($host['host_id'] ?? 0);
|
$hostId = (int)($host['host_id'] ?? 0);
|
||||||
$host['metadata'] = $metadataByHost[$hostId] ?? [];
|
$host['metadata'] = ($host['source'] ?? '') === 'reservation' ? ($metadataByHost[$hostId] ?? []) : [];
|
||||||
}
|
}
|
||||||
unset($host);
|
unset($host);
|
||||||
|
|
||||||
@@ -134,6 +221,82 @@ final class KeaHostRepository
|
|||||||
return (string)$this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
|
return (string)$this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function tableExists(string $table): bool
|
||||||
|
{
|
||||||
|
$driver = $this->driver();
|
||||||
|
|
||||||
|
if ($driver === 'pgsql') {
|
||||||
|
$stmt = $this->pdo->prepare(
|
||||||
|
"SELECT 1
|
||||||
|
FROM information_schema.tables
|
||||||
|
WHERE table_schema = current_schema()
|
||||||
|
AND table_name = :table
|
||||||
|
LIMIT 1"
|
||||||
|
);
|
||||||
|
$stmt->execute(['table' => $table]);
|
||||||
|
return (bool)$stmt->fetchColumn();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($driver === 'sqlite') {
|
||||||
|
$stmt = $this->pdo->prepare(
|
||||||
|
"SELECT 1
|
||||||
|
FROM sqlite_master
|
||||||
|
WHERE type = 'table'
|
||||||
|
AND name = :table
|
||||||
|
LIMIT 1"
|
||||||
|
);
|
||||||
|
$stmt->execute(['table' => $table]);
|
||||||
|
return (bool)$stmt->fetchColumn();
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $this->pdo->prepare(
|
||||||
|
"SELECT 1
|
||||||
|
FROM information_schema.tables
|
||||||
|
WHERE table_schema = DATABASE()
|
||||||
|
AND table_name = :table
|
||||||
|
LIMIT 1"
|
||||||
|
);
|
||||||
|
$stmt->execute(['table' => $table]);
|
||||||
|
return (bool)$stmt->fetchColumn();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function hexExpression(string $column): string
|
||||||
|
{
|
||||||
|
return match ($this->driver()) {
|
||||||
|
'pgsql' => "encode({$column}, 'hex')",
|
||||||
|
default => "HEX({$column})",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private function ipv4Expression(string $column): string
|
||||||
|
{
|
||||||
|
return match ($this->driver()) {
|
||||||
|
'pgsql' => "host('0.0.0.0'::inet + ({$column})::bigint)",
|
||||||
|
'mysql' => "INET_NTOA({$column})",
|
||||||
|
default => "CAST({$column} AS TEXT)",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private function normalizeRow(array $row): array
|
||||||
|
{
|
||||||
|
$row['dhcp_identifier'] = $this->formatMac((string)($row['dhcp_identifier_hex'] ?? ''));
|
||||||
|
$row['ipv4_address'] = (string)($row['ipv4_address_text'] ?? '');
|
||||||
|
$row['hostname'] = (string)($row['hostname'] ?? '');
|
||||||
|
$row['metadata'] = [];
|
||||||
|
|
||||||
|
return $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatMac(string $hex): string
|
||||||
|
{
|
||||||
|
$hex = strtolower(preg_replace('/[^a-fA-F0-9]/', '', $hex) ?? '');
|
||||||
|
if ($hex === '') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode(':', str_split($hex, 2));
|
||||||
|
}
|
||||||
|
|
||||||
private function lastInsertIdSafe(): int
|
private function lastInsertIdSafe(): int
|
||||||
{
|
{
|
||||||
$driver = $this->driver();
|
$driver = $this->driver();
|
||||||
|
|||||||
Reference in New Issue
Block a user