adads
This commit is contained in:
129
modules/kea/pages/edit.php
Normal file
129
modules/kea/pages/edit.php
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
<?php
|
||||||
|
use App\Database;
|
||||||
|
use App\Repository\KeaHostMetadataRepository;
|
||||||
|
use App\Repository\KeaHostRepository;
|
||||||
|
|
||||||
|
$module = modules()->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'] : [];
|
||||||
|
?>
|
||||||
|
<section class="kea-page">
|
||||||
|
<div class="section-head">
|
||||||
|
<div>
|
||||||
|
<h2 class="section-title">KEA Eintrag bearbeiten</h2>
|
||||||
|
<p>Zusatzdaten werden separat von der KEA-Datenbank gespeichert.</p>
|
||||||
|
</div>
|
||||||
|
<a class="nav-link" href="/module/kea">Zurueck</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($error): ?>
|
||||||
|
<div class="kea-message kea-message--error" role="alert">
|
||||||
|
<strong>Fehler</strong>
|
||||||
|
<p><?= e($error) ?></p>
|
||||||
|
</div>
|
||||||
|
<?php elseif ($host): ?>
|
||||||
|
<?php if ($notice): ?>
|
||||||
|
<div class="kea-message kea-message--success">
|
||||||
|
<?= e($notice) ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="kea-panel">
|
||||||
|
<div class="kea-panel__head">
|
||||||
|
<div>
|
||||||
|
<span class="pill"><?= ($source === 'lease') ? 'Lease' : 'Reservierung' ?></span>
|
||||||
|
<h3><?= e((string)($host['hostname'] ?: 'Unbekannt')) ?></h3>
|
||||||
|
<p class="muted">
|
||||||
|
IP <?= e((string)($host['ipv4_address'] ?? '')) ?> · MAC <?= e((string)($host['dhcp_identifier'] ?? '')) ?>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="post" class="kea-edit-form">
|
||||||
|
<input type="hidden" name="source" value="<?= e($source) ?>">
|
||||||
|
<input type="hidden" name="id" value="<?= e((string)$id) ?>">
|
||||||
|
|
||||||
|
<label class="setup-field">
|
||||||
|
<span>Echter Name</span>
|
||||||
|
<input type="text" name="real_name" value="<?= e((string)($metadata['real_name'] ?? '')) ?>">
|
||||||
|
</label>
|
||||||
|
<label class="setup-field">
|
||||||
|
<span>Gerätename</span>
|
||||||
|
<input type="text" name="device_name" value="<?= e((string)($metadata['device_name'] ?? '')) ?>">
|
||||||
|
</label>
|
||||||
|
<label class="setup-field">
|
||||||
|
<span>Besitzer</span>
|
||||||
|
<input type="text" name="owner" value="<?= e((string)($metadata['owner'] ?? '')) ?>">
|
||||||
|
</label>
|
||||||
|
<label class="setup-field">
|
||||||
|
<span>Standort</span>
|
||||||
|
<input type="text" name="location" value="<?= e((string)($metadata['location'] ?? '')) ?>">
|
||||||
|
</label>
|
||||||
|
<label class="setup-field">
|
||||||
|
<span>Gerätetyp</span>
|
||||||
|
<input type="text" name="device_type" value="<?= e((string)($metadata['device_type'] ?? '')) ?>">
|
||||||
|
</label>
|
||||||
|
<label class="setup-field kea-edit-form__wide">
|
||||||
|
<span>Notizen</span>
|
||||||
|
<textarea name="notes" rows="4"><?= e((string)($metadata['notes'] ?? '')) ?></textarea>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="setup-actions kea-edit-form__wide">
|
||||||
|
<button class="cta-button" type="submit">Speichern</button>
|
||||||
|
<a class="nav-link" href="/module/kea">Abbrechen</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</section>
|
||||||
@@ -5,81 +5,81 @@
|
|||||||
* @var array $warnings Hinweise, falls Zusatzdaten nicht geladen werden konnten.
|
* @var array $warnings Hinweise, falls Zusatzdaten nicht geladen werden konnten.
|
||||||
*/
|
*/
|
||||||
?>
|
?>
|
||||||
<div class="px-4 py-6 sm:px-0">
|
<section class="kea-page">
|
||||||
<div class="flex justify-between items-center mb-6">
|
<div class="section-head">
|
||||||
<h1 class="text-2xl font-semibold text-white">KEA DHCP Hosts</h1>
|
<div>
|
||||||
<button class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded shadow transition-colors">
|
<h2 class="section-title">KEA DHCP Hosts</h2>
|
||||||
+ Neuer Host
|
<p>Reservierungen und aktuelle Leases aus der KEA-Datenbank.</p>
|
||||||
</button>
|
</div>
|
||||||
|
<a class="cta-button" href="/modules/setup/kea">Setup</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php if ($error): ?>
|
<?php if ($error): ?>
|
||||||
<div class="bg-red-900 border-l-4 border-red-500 text-red-100 p-4 mb-6" role="alert">
|
<div class="kea-message kea-message--error" role="alert">
|
||||||
<p class="font-bold">Fehler</p>
|
<strong>Fehler</strong>
|
||||||
<p><?= e($error) ?></p>
|
<p><?= e($error) ?></p>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php foreach (($warnings ?? []) as $warning): ?>
|
<?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">
|
<div class="kea-message kea-message--warning" role="alert">
|
||||||
<p><?= e((string)$warning) ?></p>
|
<p><?= e((string)$warning) ?></p>
|
||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
|
||||||
<div class="bg-gray-800 shadow overflow-hidden sm:rounded-lg border border-gray-700">
|
<div class="kea-panel">
|
||||||
<div class="px-4 py-5 sm:px-6 border-b border-gray-700">
|
<div class="kea-panel__head">
|
||||||
<h3 class="text-lg leading-6 font-medium text-gray-200">
|
<div>
|
||||||
Registrierte Geräte
|
<span class="pill">Inventar</span>
|
||||||
</h3>
|
<h3>Registrierte Geräte</h3>
|
||||||
<p class="mt-1 max-w-2xl text-sm text-gray-400">
|
<p class="muted">Zusatzdaten werden in der separaten Nexus-DHCP-Datenbank gespeichert.</p>
|
||||||
Übersicht der statischen Reservierungen und bekannten Clients.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="overflow-x-auto">
|
</div>
|
||||||
<table class="min-w-full divide-y divide-gray-700">
|
<div class="kea-table-wrap">
|
||||||
<thead class="bg-gray-900">
|
<table class="kea-table">
|
||||||
|
<thead>
|
||||||
<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>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>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>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>MAC Adresse</th>
|
||||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">Echter Name</th>
|
<th>Echter Name</th>
|
||||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">Standort</th>
|
<th>Standort</th>
|
||||||
<th scope="col" class="relative px-6 py-3">
|
<th>Aktion</th>
|
||||||
<span class="sr-only">Edit</span>
|
|
||||||
</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="bg-gray-800 divide-y divide-gray-700">
|
<tbody>
|
||||||
<?php if (empty($hosts)): ?>
|
<?php if (empty($hosts)): ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="7" class="px-6 py-4 text-center text-sm text-gray-500">
|
<td colspan="7" class="kea-empty">
|
||||||
Keine Reservierungen oder aktiven Leases gefunden.
|
Keine Reservierungen oder aktiven Leases gefunden.
|
||||||
</td>
|
</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>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-300">
|
<td>
|
||||||
<?= ($host['source'] ?? '') === 'lease' ? 'Lease' : 'Reservierung' ?>
|
<span class="pill"><?= ($host['source'] ?? '') === 'lease' ? 'Lease' : 'Reservierung' ?></span>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-white">
|
<td>
|
||||||
<?= e($host['hostname'] ?: 'Unbekannt') ?>
|
<?= e($host['hostname'] ?: 'Unbekannt') ?>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-300 font-mono">
|
<td class="mono">
|
||||||
<?= e($host['ipv4_address']) ?>
|
<?= e($host['ipv4_address']) ?>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-400 font-mono">
|
<td class="mono">
|
||||||
<?= e($host['dhcp_identifier']) ?>
|
<?= e($host['dhcp_identifier']) ?>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-400">
|
<td>
|
||||||
<?= e((string)($host['metadata']['real_name'] ?? '-')) ?>
|
<?= e((string)($host['metadata']['real_name'] ?? '-')) ?>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-400">
|
<td>
|
||||||
<?= e((string)($host['metadata']['location'] ?? '-')) ?>
|
<?= e((string)($host['metadata']['location'] ?? '-')) ?>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
<td>
|
||||||
<a href="#" class="text-indigo-400 hover:text-indigo-300">Bearbeiten</a>
|
<a class="nav-link" href="/module/kea/edit?source=<?= e((string)($host['source'] ?? 'reservation')) ?>&id=<?= e((string)($host['host_id'] ?? '0')) ?>">
|
||||||
|
Bearbeiten
|
||||||
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
@@ -88,4 +88,4 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
|
|||||||
@@ -756,3 +756,100 @@ a {
|
|||||||
gap: 10px;
|
gap: 10px;
|
||||||
flex-wrap: wrap;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
private function isMissingTable(\PDOException $e): bool
|
||||||
{
|
{
|
||||||
return in_array((string)$e->getCode(), ['42P01', '42S02'], true);
|
return in_array((string)$e->getCode(), ['42P01', '42S02'], true);
|
||||||
@@ -201,15 +247,10 @@ final class KeaHostRepository
|
|||||||
return $hosts;
|
return $hosts;
|
||||||
}
|
}
|
||||||
|
|
||||||
$metadataByHost = $this->metadata->findByHostIds(
|
$metadataByHost = $this->metadata->findByHostIds(array_column($hosts, 'host_id'));
|
||||||
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'] = ($host['source'] ?? '') === 'reservation' ? ($metadataByHost[$hostId] ?? []) : [];
|
$host['metadata'] = $metadataByHost[$hostId] ?? [];
|
||||||
}
|
}
|
||||||
unset($host);
|
unset($host);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user