kea
This commit is contained in:
26
modules/kea/migrations/002_1.1.0_groups.php
Normal file
26
modules/kea/migrations/002_1.1.0_groups.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Database;
|
||||
use App\ModuleMigrationContext;
|
||||
use App\Repository\KeaHostMetadataRepository;
|
||||
|
||||
return new class {
|
||||
public function up(ModuleMigrationContext $context): void
|
||||
{
|
||||
$settings = $context->settings();
|
||||
$fallback = is_array($context->module['metadata_db_defaults'] ?? null)
|
||||
? $context->module['metadata_db_defaults']
|
||||
: [];
|
||||
$config = is_array($settings['metadata_db'] ?? null)
|
||||
? array_replace($fallback, $settings['metadata_db'])
|
||||
: $fallback;
|
||||
|
||||
if (empty($config['driver']) || empty($config['dbname'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$repo = new KeaHostMetadataRepository(Database::createFromArray($config));
|
||||
$repo->ensureSchema();
|
||||
}
|
||||
};
|
||||
@@ -1,10 +1,11 @@
|
||||
{
|
||||
"title": "KEA DHCP",
|
||||
"version": "1.0.0",
|
||||
"schema_version": 1,
|
||||
"version": "1.1.0",
|
||||
"schema_version": 2,
|
||||
"description": "Verwaltung von KEA DHCP Hosts und Reservierungen.",
|
||||
"menu": [
|
||||
{ "label": "Hosts", "href": "/module/kea" },
|
||||
{ "label": "Gruppen", "href": "/module/kea/groups" },
|
||||
{ "label": "Setup", "href": "/modules/setup/kea" }
|
||||
],
|
||||
"sidebar": {
|
||||
@@ -13,6 +14,7 @@
|
||||
"default": "collapsed",
|
||||
"items": [
|
||||
{ "label": "Hosts", "href": "/module/kea" },
|
||||
{ "label": "Gruppen", "href": "/module/kea/groups" },
|
||||
{ "label": "Setup", "href": "/modules/setup/kea" }
|
||||
]
|
||||
},
|
||||
|
||||
@@ -19,6 +19,7 @@ $notice = null;
|
||||
$host = null;
|
||||
$metadataRepo = null;
|
||||
$groups = [];
|
||||
$availableIpsByGroup = [];
|
||||
|
||||
try {
|
||||
$pdo = modules()->modulePdo('kea', $fallback);
|
||||
@@ -64,11 +65,19 @@ try {
|
||||
}
|
||||
$host = $repo->findDisplayByKey($source, $id) ?: $host;
|
||||
}
|
||||
|
||||
$usedIps = array_diff(
|
||||
array_merge($repo->usedIpAddresses(), $metadataRepo->desiredIps()),
|
||||
[(string)($host['ipv4_address'] ?? ''), (string)($host['metadata']['desired_ip'] ?? '')]
|
||||
);
|
||||
$availableIpsByGroup = $metadataRepo->availableIpsByGroup($usedIps);
|
||||
} catch (Throwable $e) {
|
||||
$error = $e->getMessage();
|
||||
}
|
||||
|
||||
$metadata = is_array($host['metadata'] ?? null) ? $host['metadata'] : [];
|
||||
$selectedGroup = (string)($metadata['group_name'] ?? '');
|
||||
$selectedIp = (string)($metadata['desired_ip'] ?? '');
|
||||
?>
|
||||
<section class="kea-page">
|
||||
<div class="section-head">
|
||||
@@ -128,17 +137,19 @@ $metadata = is_array($host['metadata'] ?? null) ? $host['metadata'] : [];
|
||||
</label>
|
||||
<label class="setup-field">
|
||||
<span>Gruppe</span>
|
||||
<input type="text" name="group_name" list="kea-groups" value="<?= e((string)($metadata['group_name'] ?? '')) ?>">
|
||||
<datalist id="kea-groups">
|
||||
<select name="group_name" data-kea-group-select>
|
||||
<option value="">Bitte waehlen</option>
|
||||
<?php foreach ($groups as $group): ?>
|
||||
<option value="<?= e($group) ?>"></option>
|
||||
<option value="<?= e($group) ?>" <?= $selectedGroup === $group ? 'selected' : '' ?>><?= e($group) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</datalist>
|
||||
</select>
|
||||
</label>
|
||||
<label class="setup-field">
|
||||
<span>Feste IP</span>
|
||||
<input type="text" name="desired_ip" value="<?= e((string)($metadata['desired_ip'] ?? '')) ?>" placeholder="<?= e((string)($host['ipv4_address'] ?? '')) ?>">
|
||||
<small class="muted">Wenn gesetzt, wird der Eintrag als KEA-Reservierung gespeichert.</small>
|
||||
<select name="desired_ip" data-kea-ip-select data-selected-ip="<?= e($selectedIp) ?>">
|
||||
<option value="">Erst Gruppe waehlen</option>
|
||||
</select>
|
||||
<small class="muted">Es werden nur freie IPs aus dem IP-Bereich der gewaehlten Gruppe angeboten.</small>
|
||||
</label>
|
||||
<label class="setup-field kea-edit-form__wide">
|
||||
<span>Notizen</span>
|
||||
@@ -151,5 +162,36 @@ $metadata = is_array($host['metadata'] ?? null) ? $host['metadata'] : [];
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<script>
|
||||
(() => {
|
||||
const ipsByGroup = <?= json_encode($availableIpsByGroup, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) ?>;
|
||||
const groupSelect = document.querySelector('[data-kea-group-select]');
|
||||
const ipSelect = document.querySelector('[data-kea-ip-select]');
|
||||
if (!groupSelect || !ipSelect) {
|
||||
return;
|
||||
}
|
||||
const selectedIp = ipSelect.dataset.selectedIp || '';
|
||||
const renderIps = () => {
|
||||
const ips = ipsByGroup[groupSelect.value] || [];
|
||||
ipSelect.innerHTML = '';
|
||||
const empty = document.createElement('option');
|
||||
empty.value = '';
|
||||
empty.textContent = groupSelect.value ? 'Keine feste IP' : 'Erst Gruppe waehlen';
|
||||
ipSelect.appendChild(empty);
|
||||
if (selectedIp && !ips.includes(selectedIp)) {
|
||||
ips.unshift(selectedIp);
|
||||
}
|
||||
for (const ip of ips) {
|
||||
const option = document.createElement('option');
|
||||
option.value = ip;
|
||||
option.textContent = ip;
|
||||
option.selected = ip === selectedIp;
|
||||
ipSelect.appendChild(option);
|
||||
}
|
||||
};
|
||||
groupSelect.addEventListener('change', renderIps);
|
||||
renderIps();
|
||||
})();
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
167
modules/kea/pages/groups.php
Normal file
167
modules/kea/pages/groups.php
Normal file
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
use App\Database;
|
||||
use App\Repository\KeaHostMetadataRepository;
|
||||
use App\Repository\KeaHostRepository;
|
||||
|
||||
$module = modules()->get('kea');
|
||||
$settings = modules()->settings('kea');
|
||||
$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;
|
||||
$fallback = $module['db_defaults'] ?? [];
|
||||
$error = null;
|
||||
$notice = null;
|
||||
$groups = [];
|
||||
$availableIpsByGroup = [];
|
||||
|
||||
try {
|
||||
if (empty($metadataConfig['driver']) || empty($metadataConfig['dbname'])) {
|
||||
throw new RuntimeException('Nexus DHCP Zusatzdatenbank ist nicht konfiguriert.');
|
||||
}
|
||||
|
||||
$metadataRepo = new KeaHostMetadataRepository(Database::createFromArray($metadataConfig));
|
||||
$metadataRepo->ensureSchema();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$action = (string)($_POST['action'] ?? '');
|
||||
if ($action === 'save_group') {
|
||||
$metadataRepo->saveGroup((string)($_POST['name'] ?? ''), (string)($_POST['description'] ?? ''));
|
||||
$notice = 'Gruppe gespeichert.';
|
||||
} elseif ($action === 'add_range') {
|
||||
$metadataRepo->addRange(
|
||||
(string)($_POST['group_name'] ?? ''),
|
||||
(string)($_POST['start_ip'] ?? ''),
|
||||
(string)($_POST['end_ip'] ?? '')
|
||||
);
|
||||
$notice = 'IP-Bereich gespeichert.';
|
||||
}
|
||||
}
|
||||
|
||||
$groups = $metadataRepo->listGroupsWithRanges();
|
||||
$keaRepo = new KeaHostRepository(modules()->modulePdo('kea', $fallback), $metadataRepo);
|
||||
$availableIpsByGroup = $metadataRepo->availableIpsByGroup(
|
||||
array_merge($keaRepo->usedIpAddresses(), $metadataRepo->desiredIps()),
|
||||
4096
|
||||
);
|
||||
} catch (Throwable $e) {
|
||||
$error = $e->getMessage();
|
||||
}
|
||||
?>
|
||||
<section class="kea-page">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<h2 class="section-title">KEA Gruppen</h2>
|
||||
<p>Gruppen und IP-Bereiche fuer DHCP-Reservierungen.</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 ($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">Gruppe</span>
|
||||
<h3>Gruppe anlegen</h3>
|
||||
</div>
|
||||
</div>
|
||||
<form method="post" class="kea-edit-form">
|
||||
<input type="hidden" name="action" value="save_group">
|
||||
<label class="setup-field">
|
||||
<span>Name</span>
|
||||
<input type="text" name="name" required>
|
||||
</label>
|
||||
<label class="setup-field">
|
||||
<span>Beschreibung</span>
|
||||
<input type="text" name="description">
|
||||
</label>
|
||||
<div class="setup-actions kea-edit-form__wide">
|
||||
<button class="cta-button" type="submit">Gruppe speichern</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="kea-panel">
|
||||
<div class="kea-panel__head">
|
||||
<div>
|
||||
<span class="pill">IP-Bereich</span>
|
||||
<h3>Bereich zuweisen</h3>
|
||||
</div>
|
||||
</div>
|
||||
<form method="post" class="kea-edit-form">
|
||||
<input type="hidden" name="action" value="add_range">
|
||||
<label class="setup-field">
|
||||
<span>Gruppe</span>
|
||||
<select name="group_name" required>
|
||||
<option value="">Bitte waehlen</option>
|
||||
<?php foreach ($groups as $group): ?>
|
||||
<option value="<?= e((string)$group['name']) ?>"><?= e((string)$group['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
<label class="setup-field">
|
||||
<span>Start-IP</span>
|
||||
<input type="text" name="start_ip" placeholder="192.168.178.50" required>
|
||||
</label>
|
||||
<label class="setup-field">
|
||||
<span>End-IP</span>
|
||||
<input type="text" name="end_ip" placeholder="192.168.178.99" required>
|
||||
</label>
|
||||
<div class="setup-actions kea-edit-form__wide">
|
||||
<button class="cta-button" type="submit">Bereich speichern</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="kea-panel">
|
||||
<div class="kea-panel__head">
|
||||
<div>
|
||||
<span class="pill">Uebersicht</span>
|
||||
<h3>Gruppen und freie IPs</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="kea-table-wrap">
|
||||
<table class="kea-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Gruppe</th>
|
||||
<th>Beschreibung</th>
|
||||
<th>Bereiche</th>
|
||||
<th>Freie IPs</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if ($groups === []): ?>
|
||||
<tr><td colspan="4" class="kea-empty">Noch keine Gruppen definiert.</td></tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($groups as $group): ?>
|
||||
<?php $available = $availableIpsByGroup[(string)$group['name']] ?? []; ?>
|
||||
<tr>
|
||||
<td><?= e((string)$group['name']) ?></td>
|
||||
<td><?= e((string)($group['description'] ?? '-')) ?></td>
|
||||
<td>
|
||||
<?php if (($group['ranges'] ?? []) === []): ?>
|
||||
<span class="muted">Kein Bereich</span>
|
||||
<?php else: ?>
|
||||
<?php foreach ($group['ranges'] as $range): ?>
|
||||
<div class="mono"><?= e((string)$range['start_ip']) ?> - <?= e((string)$range['end_ip']) ?></div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><?= e((string)count($available)) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -20,6 +20,7 @@ $stats = [
|
||||
'reservations' => 0,
|
||||
'leases' => 0,
|
||||
'groups' => [],
|
||||
'free_ips' => [],
|
||||
];
|
||||
|
||||
try {
|
||||
@@ -48,6 +49,15 @@ try {
|
||||
$stats['groups'][$group] = ($stats['groups'][$group] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
if ($metadataRepo !== null) {
|
||||
$stats['free_ips'] = array_map(
|
||||
static fn(array $ips): int => count($ips),
|
||||
$metadataRepo->availableIpsByGroup(
|
||||
array_merge($repo->usedIpAddresses(), $metadataRepo->desiredIps()),
|
||||
4096
|
||||
)
|
||||
);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$error = "Datenbankfehler: " . $e->getMessage();
|
||||
}
|
||||
|
||||
@@ -45,6 +45,10 @@
|
||||
<span class="stat-label">Gruppen</span>
|
||||
<span class="stat-value"><?= e((string)count($stats['groups'] ?? [])) ?></span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-label">Freie Gruppen-IPs</span>
|
||||
<span class="stat-value"><?= e((string)array_sum($stats['free_ips'] ?? [])) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kea-panel">
|
||||
|
||||
@@ -33,6 +33,7 @@ final class KeaHostMetadataRepository
|
||||
);
|
||||
$this->ensureColumn('nexus_dhcp_host_meta', 'group_name', 'TEXT');
|
||||
$this->ensureColumn('nexus_dhcp_host_meta', 'desired_ip', 'TEXT');
|
||||
$this->ensureGroupSchema($driver);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -56,6 +57,7 @@ final class KeaHostMetadataRepository
|
||||
);
|
||||
$this->ensureColumn('nexus_dhcp_host_meta', 'group_name', 'TEXT');
|
||||
$this->ensureColumn('nexus_dhcp_host_meta', 'desired_ip', 'TEXT');
|
||||
$this->ensureGroupSchema($driver);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -79,6 +81,7 @@ final class KeaHostMetadataRepository
|
||||
|
||||
$this->ensureColumn('nexus_dhcp_host_meta', 'group_name', 'TEXT');
|
||||
$this->ensureColumn('nexus_dhcp_host_meta', 'desired_ip', 'TEXT');
|
||||
$this->ensureGroupSchema($driver);
|
||||
}
|
||||
|
||||
public function findByHostIds(array $hostIds): array
|
||||
@@ -183,19 +186,278 @@ final class KeaHostMetadataRepository
|
||||
|
||||
public function listGroups(): array
|
||||
{
|
||||
$groups = [];
|
||||
|
||||
if ($this->tableExists('nexus_dhcp_groups')) {
|
||||
$stmt = $this->pdo->query("SELECT name FROM nexus_dhcp_groups ORDER BY name ASC");
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$name = trim((string)($row['name'] ?? ''));
|
||||
if ($name !== '') {
|
||||
$groups[] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->query(
|
||||
"SELECT DISTINCT group_name
|
||||
FROM nexus_dhcp_host_meta
|
||||
WHERE group_name IS NOT NULL AND group_name <> ''
|
||||
ORDER BY group_name ASC"
|
||||
WHERE group_name IS NOT NULL AND group_name <> ''"
|
||||
);
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$name = trim((string)($row['group_name'] ?? ''));
|
||||
if ($name !== '') {
|
||||
$groups[] = $name;
|
||||
}
|
||||
}
|
||||
|
||||
sort($groups, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
return array_values(array_unique($groups));
|
||||
}
|
||||
|
||||
public function listGroupsWithRanges(): array
|
||||
{
|
||||
if (!$this->tableExists('nexus_dhcp_groups')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$groups = [];
|
||||
$stmt = $this->pdo->query(
|
||||
"SELECT id, name, description
|
||||
FROM nexus_dhcp_groups
|
||||
ORDER BY name ASC"
|
||||
);
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$id = (int)$row['id'];
|
||||
$groups[$id] = [
|
||||
'id' => $id,
|
||||
'name' => (string)$row['name'],
|
||||
'description' => (string)($row['description'] ?? ''),
|
||||
'ranges' => [],
|
||||
];
|
||||
}
|
||||
|
||||
if ($groups !== [] && $this->tableExists('nexus_dhcp_group_ranges')) {
|
||||
$stmt = $this->pdo->query(
|
||||
"SELECT id, group_id, start_ip, end_ip
|
||||
FROM nexus_dhcp_group_ranges
|
||||
ORDER BY start_ip ASC"
|
||||
);
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$groupId = (int)$row['group_id'];
|
||||
if (isset($groups[$groupId])) {
|
||||
$groups[$groupId]['ranges'][] = [
|
||||
'id' => (int)$row['id'],
|
||||
'start_ip' => (string)$row['start_ip'],
|
||||
'end_ip' => (string)$row['end_ip'],
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($groups);
|
||||
}
|
||||
|
||||
public function saveGroup(string $name, string $description = ''): void
|
||||
{
|
||||
$name = trim($name);
|
||||
if ($name === '') {
|
||||
throw new \RuntimeException('Gruppenname fehlt.');
|
||||
}
|
||||
|
||||
$driver = (string)$this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
|
||||
if ($driver === 'pgsql' || $driver === 'sqlite') {
|
||||
$sql = "INSERT INTO nexus_dhcp_groups (name, description, updated_at)
|
||||
VALUES (:name, :description, CURRENT_TIMESTAMP)
|
||||
ON CONFLICT(name) DO UPDATE SET
|
||||
description = excluded.description,
|
||||
updated_at = CURRENT_TIMESTAMP";
|
||||
} else {
|
||||
$sql = "INSERT INTO nexus_dhcp_groups (name, description, updated_at)
|
||||
VALUES (:name, :description, CURRENT_TIMESTAMP)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
description = VALUES(description),
|
||||
updated_at = CURRENT_TIMESTAMP";
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
'name' => $name,
|
||||
'description' => $this->nullableString($description),
|
||||
]);
|
||||
}
|
||||
|
||||
public function addRange(string $groupName, string $startIp, string $endIp): void
|
||||
{
|
||||
$groupName = trim($groupName);
|
||||
$startIp = trim($startIp);
|
||||
$endIp = trim($endIp);
|
||||
if ($groupName === '' || !filter_var($startIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) || !filter_var($endIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||
throw new \RuntimeException('Gruppe oder IP-Bereich ist ungueltig.');
|
||||
}
|
||||
|
||||
if (ip2long($startIp) > ip2long($endIp)) {
|
||||
throw new \RuntimeException('Start-IP muss vor der End-IP liegen.');
|
||||
}
|
||||
|
||||
$groupId = $this->groupIdByName($groupName);
|
||||
if ($groupId <= 0) {
|
||||
throw new \RuntimeException('Gruppe wurde nicht gefunden.');
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare(
|
||||
"INSERT INTO nexus_dhcp_group_ranges (group_id, start_ip, end_ip, updated_at)
|
||||
VALUES (:group_id, :start_ip, :end_ip, CURRENT_TIMESTAMP)"
|
||||
);
|
||||
$stmt->execute([
|
||||
'group_id' => $groupId,
|
||||
'start_ip' => $startIp,
|
||||
'end_ip' => $endIp,
|
||||
]);
|
||||
}
|
||||
|
||||
public function availableIpsByGroup(array $usedIps, int $limitPerGroup = 512): array
|
||||
{
|
||||
$used = array_flip(array_filter(array_map('strval', $usedIps)));
|
||||
$items = [];
|
||||
foreach ($this->listGroupsWithRanges() as $group) {
|
||||
$available = [];
|
||||
foreach ($group['ranges'] as $range) {
|
||||
$start = ip2long((string)$range['start_ip']);
|
||||
$end = ip2long((string)$range['end_ip']);
|
||||
if ($start === false || $end === false) {
|
||||
continue;
|
||||
}
|
||||
for ($ip = $start; $ip <= $end && count($available) < $limitPerGroup; $ip++) {
|
||||
$address = long2ip($ip);
|
||||
if ($address !== false && !isset($used[$address])) {
|
||||
$available[] = $address;
|
||||
}
|
||||
}
|
||||
}
|
||||
$items[(string)$group['name']] = $available;
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
public function desiredIps(): array
|
||||
{
|
||||
$stmt = $this->pdo->query(
|
||||
"SELECT desired_ip
|
||||
FROM nexus_dhcp_host_meta
|
||||
WHERE desired_ip IS NOT NULL AND desired_ip <> ''"
|
||||
);
|
||||
|
||||
return array_values(array_filter(array_map(
|
||||
static fn(array $row): string => (string)($row['group_name'] ?? ''),
|
||||
static fn(array $row): string => (string)($row['desired_ip'] ?? ''),
|
||||
$stmt->fetchAll(PDO::FETCH_ASSOC)
|
||||
)));
|
||||
}
|
||||
|
||||
private function ensureGroupSchema(string $driver): void
|
||||
{
|
||||
if ($driver === 'pgsql') {
|
||||
$this->pdo->exec(
|
||||
"CREATE TABLE IF NOT EXISTS nexus_dhcp_groups (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
)"
|
||||
);
|
||||
$this->pdo->exec(
|
||||
"CREATE TABLE IF NOT EXISTS nexus_dhcp_group_ranges (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
group_id BIGINT NOT NULL REFERENCES nexus_dhcp_groups(id) ON DELETE CASCADE,
|
||||
start_ip TEXT NOT NULL,
|
||||
end_ip TEXT NOT NULL,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
)"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($driver === 'sqlite') {
|
||||
$this->pdo->exec(
|
||||
"CREATE TABLE IF NOT EXISTS nexus_dhcp_groups (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
)"
|
||||
);
|
||||
$this->pdo->exec(
|
||||
"CREATE TABLE IF NOT EXISTS nexus_dhcp_group_ranges (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
group_id INTEGER NOT NULL,
|
||||
start_ip TEXT NOT NULL,
|
||||
end_ip TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
)"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->pdo->exec(
|
||||
"CREATE TABLE IF NOT EXISTS nexus_dhcp_groups (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(190) NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
)"
|
||||
);
|
||||
$this->pdo->exec(
|
||||
"CREATE TABLE IF NOT EXISTS nexus_dhcp_group_ranges (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
group_id BIGINT NOT NULL,
|
||||
start_ip VARCHAR(45) NOT NULL,
|
||||
end_ip VARCHAR(45) NOT NULL,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_nexus_dhcp_group_ranges_group_id (group_id)
|
||||
)"
|
||||
);
|
||||
}
|
||||
|
||||
private function groupIdByName(string $name): int
|
||||
{
|
||||
$stmt = $this->pdo->prepare('SELECT id FROM nexus_dhcp_groups WHERE name = :name LIMIT 1');
|
||||
$stmt->execute(['name' => $name]);
|
||||
return (int)$stmt->fetchColumn();
|
||||
}
|
||||
|
||||
private function tableExists(string $table): bool
|
||||
{
|
||||
$driver = (string)$this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
if ($driver === 'pgsql') {
|
||||
$stmt = $this->pdo->prepare(
|
||||
"SELECT 1
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = current_schema()
|
||||
AND table_name = :table
|
||||
LIMIT 1"
|
||||
);
|
||||
} else {
|
||||
$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 ensureColumn(string $table, string $column, string $definition): void
|
||||
{
|
||||
if ($this->columnExists($table, $column)) {
|
||||
|
||||
@@ -66,6 +66,14 @@ final class KeaHostRepository
|
||||
}
|
||||
}
|
||||
|
||||
public function usedIpAddresses(int $limit = 10000): array
|
||||
{
|
||||
return array_values(array_unique(array_filter(array_map(
|
||||
static fn(array $host): string => (string)($host['ipv4_address'] ?? ''),
|
||||
$this->findAll($limit)
|
||||
))));
|
||||
}
|
||||
|
||||
private function findReservations(int $limit): array
|
||||
{
|
||||
if (!$this->tableExists('hosts')) {
|
||||
|
||||
Reference in New Issue
Block a user