adasd
All checks were successful
Deploy / deploy-staging (push) Successful in 7s
Deploy / deploy-production (push) Has been skipped

This commit is contained in:
2026-04-15 01:56:18 +02:00
parent 5a3ebc607c
commit 08a8df87e2
3 changed files with 201 additions and 19 deletions

View File

@@ -22,21 +22,42 @@ final class KeaHostRepository
public function findAll(int $limit = 50): array
{
try {
// 'dhcp_identifier' ist in KEA i.d.R. die MAC-Adresse (bei type=1)
$stmt = $this->pdo->prepare(
'SELECT host_id, dhcp_identifier, ipv4_address, hostname, user_context
FROM hosts
ORDER BY host_id DESC
LIMIT :limit'
);
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
$stmt->execute();
$hasReservations = $this->tableExists('hosts');
$hasLeases = $this->tableExists('lease4');
if (!$hasReservations && !$hasLeases) {
throw new \RuntimeException(
'Im aktuellen KEA-DB-Schema wurden weder hosts noch lease4 gefunden. Bitte Datenbank, Schema/Search-Path und Benutzerrechte pruefen.'
);
}
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) {
if ($this->isMissingTable($e)) {
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,
$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).
*/
@@ -78,7 +160,7 @@ final class KeaHostRepository
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;
}
$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) {
$hostId = (int)($host['host_id'] ?? 0);
$host['metadata'] = $metadataByHost[$hostId] ?? [];
$host['metadata'] = ($host['source'] ?? '') === 'reservation' ? ($metadataByHost[$hostId] ?? []) : [];
}
unset($host);
@@ -134,6 +221,82 @@ final class KeaHostRepository
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
{
$driver = $this->driver();