diff --git a/modules/kea/pages/edit.php b/modules/kea/pages/edit.php
new file mode 100644
index 0000000..3dc3192
--- /dev/null
+++ b/modules/kea/pages/edit.php
@@ -0,0 +1,129 @@
+get('kea');
+$settings = modules()->settings('kea');
+$fallback = $module['db_defaults'] ?? [];
+$metadataFallback = is_array($module['metadata_db_defaults'] ?? null) ? $module['metadata_db_defaults'] : [];
+$metadataConfig = is_array($settings['metadata_db'] ?? null)
+ ? array_replace($metadataFallback, $settings['metadata_db'])
+ : $metadataFallback;
+
+$source = (string)($_GET['source'] ?? $_POST['source'] ?? 'reservation');
+$source = $source === 'lease' ? 'lease' : 'reservation';
+$id = (int)($_GET['id'] ?? $_POST['id'] ?? 0);
+$error = null;
+$notice = null;
+$host = null;
+$metadataRepo = null;
+
+try {
+ $pdo = modules()->modulePdo('kea', $fallback);
+ if (empty($metadataConfig['driver']) || empty($metadataConfig['dbname'])) {
+ throw new RuntimeException('Nexus DHCP Zusatzdatenbank ist nicht konfiguriert.');
+ }
+
+ $metadataRepo = new KeaHostMetadataRepository(Database::createFromArray($metadataConfig));
+ $metadataRepo->ensureSchema();
+ $repo = new KeaHostRepository($pdo, $metadataRepo);
+ $host = $repo->findDisplayByKey($source, $id);
+ if (!$host) {
+ throw new RuntimeException('KEA Eintrag wurde nicht gefunden.');
+ }
+
+ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ $metadata = [
+ 'real_name' => $_POST['real_name'] ?? '',
+ 'device_name' => $_POST['device_name'] ?? '',
+ 'owner' => $_POST['owner'] ?? '',
+ 'location' => $_POST['location'] ?? '',
+ 'device_type' => $_POST['device_type'] ?? '',
+ 'notes' => $_POST['notes'] ?? '',
+ 'tags' => [],
+ ];
+ $metadataRepo->saveForHost(
+ $id,
+ (string)($host['dhcp_identifier'] ?? ''),
+ (string)($host['ipv4_address'] ?? ''),
+ $metadata
+ );
+ $notice = 'Zusatzdaten gespeichert.';
+ $host = $repo->findDisplayByKey($source, $id) ?: $host;
+ }
+} catch (Throwable $e) {
+ $error = $e->getMessage();
+}
+
+$metadata = is_array($host['metadata'] ?? null) ? $host['metadata'] : [];
+?>
+
+
+
+
KEA Eintrag bearbeiten
+
Zusatzdaten werden separat von der KEA-Datenbank gespeichert.
+
+
Zurueck
+
+
+
+
+
Fehler
+
= e($error) ?>
+
+
+
+
+ = e($notice) ?>
+
+
+
+
+
+
+
= ($source === 'lease') ? 'Lease' : 'Reservierung' ?>
+
= e((string)($host['hostname'] ?: 'Unbekannt')) ?>
+
+ IP = e((string)($host['ipv4_address'] ?? '')) ?> · MAC = e((string)($host['dhcp_identifier'] ?? '')) ?>
+
+
+
+
+
+
+
+
diff --git a/modules/kea/partials/dashboard.php b/modules/kea/partials/dashboard.php
index a1fda56..423533c 100644
--- a/modules/kea/partials/dashboard.php
+++ b/modules/kea/partials/dashboard.php
@@ -5,81 +5,81 @@
* @var array $warnings Hinweise, falls Zusatzdaten nicht geladen werden konnten.
*/
?>
-
-
-
KEA DHCP Hosts
-
+
+
+
+
KEA DHCP Hosts
+
Reservierungen und aktuelle Leases aus der KEA-Datenbank.
+
+
Setup
-
-
Fehler
+
-
+
-
-
-
- Registrierte Geräte
-
-
- Übersicht der statischen Reservierungen und bekannten Clients.
-
+
+
+
+
Inventar
+
Registrierte Geräte
+
Zusatzdaten werden in der separaten Nexus-DHCP-Datenbank gespeichert.
+
-
-
-
+
+
+
- | Quelle |
- Hostname |
- IP Adresse |
- MAC Adresse |
- Echter Name |
- Standort |
-
- Edit
- |
+ Quelle |
+ Hostname |
+ IP Adresse |
+ MAC Adresse |
+ Echter Name |
+ Standort |
+ Aktion |
-
+
- |
+ |
Keine Reservierungen oder aktiven Leases gefunden.
|
-
- |
- = ($host['source'] ?? '') === 'lease' ? 'Lease' : 'Reservierung' ?>
+ |
+ |
+ = ($host['source'] ?? '') === 'lease' ? 'Lease' : 'Reservierung' ?>
|
-
+ |
= e($host['hostname'] ?: 'Unbekannt') ?>
|
-
+ |
= e($host['ipv4_address']) ?>
|
-
+ |
= e($host['dhcp_identifier']) ?>
|
-
+ |
= e((string)($host['metadata']['real_name'] ?? '-')) ?>
|
-
+ |
= e((string)($host['metadata']['location'] ?? '-')) ?>
|
-
- Bearbeiten
+ |
+
+ Bearbeiten
+
|
@@ -88,4 +88,4 @@
-
+
diff --git a/public/assets/css/app.css b/public/assets/css/app.css
index d58d63c..f90cf93 100644
--- a/public/assets/css/app.css
+++ b/public/assets/css/app.css
@@ -756,3 +756,100 @@ a {
gap: 10px;
flex-wrap: wrap;
}
+
+.kea-page {
+ display: grid;
+ gap: 16px;
+}
+
+.kea-panel {
+ border: 1px solid var(--line);
+ border-radius: 8px;
+ background: color-mix(in srgb, var(--surface) 94%, transparent);
+ box-shadow: 0 10px 24px rgba(1, 22, 32, 0.06);
+ overflow: hidden;
+}
+
+.kea-panel__head {
+ padding: 16px;
+ border-bottom: 1px solid var(--line);
+}
+
+.kea-panel__head h3 {
+ margin: 8px 0 0;
+}
+
+.kea-table-wrap {
+ overflow-x: auto;
+}
+
+.kea-table {
+ width: 100%;
+ border-collapse: collapse;
+ min-width: 920px;
+}
+
+.kea-table th,
+.kea-table td {
+ padding: 13px 16px;
+ border-bottom: 1px solid var(--line);
+ text-align: left;
+ vertical-align: middle;
+}
+
+.kea-table th {
+ color: var(--muted);
+ font-size: 0.78rem;
+ font-weight: 800;
+ text-transform: uppercase;
+}
+
+.kea-table tbody tr:hover {
+ background: color-mix(in srgb, var(--brand-accent) 6%, transparent);
+}
+
+.kea-table .mono {
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
+}
+
+.kea-empty {
+ color: var(--muted);
+ text-align: center;
+}
+
+.kea-message {
+ border: 1px solid var(--line);
+ border-radius: 8px;
+ background: var(--surface);
+ padding: 14px 16px;
+}
+
+.kea-message p {
+ margin: 6px 0 0;
+}
+
+.kea-message--error {
+ border-color: color-mix(in srgb, #d92d20 60%, var(--line));
+ background: color-mix(in srgb, #d92d20 10%, var(--surface));
+}
+
+.kea-message--warning {
+ border-color: color-mix(in srgb, var(--accent-orange) 60%, var(--line));
+ background: color-mix(in srgb, var(--accent-orange) 12%, var(--surface));
+}
+
+.kea-message--success {
+ border-color: color-mix(in srgb, var(--accent-green) 60%, var(--line));
+ background: color-mix(in srgb, var(--accent-green) 12%, var(--surface));
+}
+
+.kea-edit-form {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
+ gap: 14px;
+ padding: 16px;
+}
+
+.kea-edit-form__wide {
+ grid-column: 1 / -1;
+}
diff --git a/src/Repository/KeaHostRepository.php b/src/Repository/KeaHostRepository.php
index 416cea8..5b14976 100644
--- a/src/Repository/KeaHostRepository.php
+++ b/src/Repository/KeaHostRepository.php
@@ -158,6 +158,52 @@ final class KeaHostRepository
}
}
+ public function findDisplayByKey(string $source, int $id): ?array
+ {
+ if ($source === 'lease') {
+ foreach ($this->findLeases(500) as $lease) {
+ if ((int)($lease['host_id'] ?? 0) === $id) {
+ return $this->withMetadata([$lease])[0] ?? $lease;
+ }
+ }
+
+ return null;
+ }
+
+ if (!$this->tableExists('hosts')) {
+ return null;
+ }
+
+ $macExpr = $this->hexExpression('dhcp_identifier');
+ $ipExpr = $this->ipv4Expression('ipv4_address');
+ $driver = $this->driver();
+ $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
+ WHERE host_id = :id
+ LIMIT 1"
+ );
+ $stmt->bindValue(':id', $id, PDO::PARAM_INT);
+ $stmt->execute();
+
+ $row = $stmt->fetch(PDO::FETCH_ASSOC);
+ if (!$row) {
+ return null;
+ }
+
+ $row = $this->normalizeRow($row);
+ return $this->withMetadata([$row])[0] ?? $row;
+ }
+
private function isMissingTable(\PDOException $e): bool
{
return in_array((string)$e->getCode(), ['42P01', '42S02'], true);
@@ -201,15 +247,10 @@ final class KeaHostRepository
return $hosts;
}
- $metadataByHost = $this->metadata->findByHostIds(
- array_column(
- array_filter($hosts, static fn(array $host): bool => ($host['source'] ?? '') === 'reservation'),
- 'host_id'
- )
- );
+ $metadataByHost = $this->metadata->findByHostIds(array_column($hosts, 'host_id'));
foreach ($hosts as &$host) {
$hostId = (int)($host['host_id'] ?? 0);
- $host['metadata'] = ($host['source'] ?? '') === 'reservation' ? ($metadataByHost[$hostId] ?? []) : [];
+ $host['metadata'] = $metadataByHost[$hostId] ?? [];
}
unset($host);