687 lines
26 KiB
PHP
687 lines
26 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Repository;
|
|
|
|
use PDO;
|
|
|
|
final class KeaHostMetadataRepository
|
|
{
|
|
public function __construct(private PDO $pdo) {}
|
|
|
|
public function ensureSchema(): void
|
|
{
|
|
$driver = (string)$this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
|
|
|
|
if ($driver === 'pgsql') {
|
|
$this->pdo->exec(
|
|
"CREATE TABLE IF NOT EXISTS nexus_dhcp_host_meta (
|
|
host_id BIGINT PRIMARY KEY,
|
|
hardware_address TEXT,
|
|
ip_address TEXT,
|
|
real_name TEXT,
|
|
device_name TEXT,
|
|
owner TEXT,
|
|
location TEXT,
|
|
device_type TEXT,
|
|
group_name TEXT,
|
|
desired_ip TEXT,
|
|
notes TEXT,
|
|
tags_json JSONB,
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
)"
|
|
);
|
|
$this->ensureColumn('nexus_dhcp_host_meta', 'group_name', 'TEXT');
|
|
$this->ensureColumn('nexus_dhcp_host_meta', 'desired_ip', 'TEXT');
|
|
$this->ensureGroupSchema($driver);
|
|
$this->ensureCheckSchema($driver);
|
|
return;
|
|
}
|
|
|
|
if ($driver === 'sqlite') {
|
|
$this->pdo->exec(
|
|
"CREATE TABLE IF NOT EXISTS nexus_dhcp_host_meta (
|
|
host_id INTEGER PRIMARY KEY,
|
|
hardware_address TEXT,
|
|
ip_address TEXT,
|
|
real_name TEXT,
|
|
device_name TEXT,
|
|
owner TEXT,
|
|
location TEXT,
|
|
device_type TEXT,
|
|
group_name TEXT,
|
|
desired_ip TEXT,
|
|
notes TEXT,
|
|
tags_json TEXT,
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
)"
|
|
);
|
|
$this->ensureColumn('nexus_dhcp_host_meta', 'group_name', 'TEXT');
|
|
$this->ensureColumn('nexus_dhcp_host_meta', 'desired_ip', 'TEXT');
|
|
$this->ensureGroupSchema($driver);
|
|
$this->ensureCheckSchema($driver);
|
|
return;
|
|
}
|
|
|
|
$this->pdo->exec(
|
|
"CREATE TABLE IF NOT EXISTS nexus_dhcp_host_meta (
|
|
host_id BIGINT PRIMARY KEY,
|
|
hardware_address TEXT,
|
|
ip_address TEXT,
|
|
real_name TEXT,
|
|
device_name TEXT,
|
|
owner TEXT,
|
|
location TEXT,
|
|
device_type TEXT,
|
|
group_name TEXT,
|
|
desired_ip TEXT,
|
|
notes TEXT,
|
|
tags_json TEXT,
|
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
)"
|
|
);
|
|
|
|
$this->ensureColumn('nexus_dhcp_host_meta', 'group_name', 'TEXT');
|
|
$this->ensureColumn('nexus_dhcp_host_meta', 'desired_ip', 'TEXT');
|
|
$this->ensureGroupSchema($driver);
|
|
$this->ensureCheckSchema($driver);
|
|
}
|
|
|
|
public function findByHostIds(array $hostIds): array
|
|
{
|
|
$hostIds = array_values(array_unique(array_filter(array_map('intval', $hostIds))));
|
|
if ($hostIds === []) {
|
|
return [];
|
|
}
|
|
|
|
$params = [];
|
|
$placeholders = [];
|
|
foreach ($hostIds as $idx => $hostId) {
|
|
$key = ':host_id_' . $idx;
|
|
$placeholders[] = $key;
|
|
$params[$key] = $hostId;
|
|
}
|
|
|
|
$stmt = $this->pdo->prepare(
|
|
'SELECT host_id, hardware_address, ip_address, real_name, device_name, owner, location, device_type, group_name, desired_ip, notes, tags_json, updated_at
|
|
FROM nexus_dhcp_host_meta
|
|
WHERE host_id IN (' . implode(', ', $placeholders) . ')'
|
|
);
|
|
foreach ($params as $key => $value) {
|
|
$stmt->bindValue($key, $value, PDO::PARAM_INT);
|
|
}
|
|
$stmt->execute();
|
|
|
|
$items = [];
|
|
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|
$items[(int)$row['host_id']] = $row;
|
|
}
|
|
|
|
return $items;
|
|
}
|
|
|
|
public function saveForHost(int $hostId, string $hardwareAddress, string $ipAddress, array $metadata): void
|
|
{
|
|
$metadata = array_merge([
|
|
'real_name' => null,
|
|
'device_name' => null,
|
|
'owner' => null,
|
|
'location' => null,
|
|
'device_type' => null,
|
|
'group_name' => null,
|
|
'desired_ip' => null,
|
|
'notes' => null,
|
|
'tags' => [],
|
|
], $metadata);
|
|
|
|
$tagsJson = json_encode($metadata['tags'], JSON_UNESCAPED_UNICODE);
|
|
if ($tagsJson === false) {
|
|
$tagsJson = '[]';
|
|
}
|
|
|
|
$driver = (string)$this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
|
|
if ($driver === 'pgsql') {
|
|
$stmt = $this->pdo->prepare(
|
|
"INSERT INTO nexus_dhcp_host_meta (
|
|
host_id, hardware_address, ip_address, real_name, device_name, owner, location, device_type, group_name, desired_ip, notes, tags_json, updated_at
|
|
) VALUES (
|
|
:host_id, :hardware_address, :ip_address, :real_name, :device_name, :owner, :location, :device_type, :group_name, :desired_ip, :notes, CAST(:tags_json AS jsonb), NOW()
|
|
)
|
|
ON CONFLICT (host_id) DO UPDATE SET
|
|
hardware_address = EXCLUDED.hardware_address,
|
|
ip_address = EXCLUDED.ip_address,
|
|
real_name = EXCLUDED.real_name,
|
|
device_name = EXCLUDED.device_name,
|
|
owner = EXCLUDED.owner,
|
|
location = EXCLUDED.location,
|
|
device_type = EXCLUDED.device_type,
|
|
group_name = EXCLUDED.group_name,
|
|
desired_ip = EXCLUDED.desired_ip,
|
|
notes = EXCLUDED.notes,
|
|
tags_json = EXCLUDED.tags_json,
|
|
updated_at = NOW()"
|
|
);
|
|
} else {
|
|
$stmt = $this->pdo->prepare(
|
|
"REPLACE INTO nexus_dhcp_host_meta (
|
|
host_id, hardware_address, ip_address, real_name, device_name, owner, location, device_type, group_name, desired_ip, notes, tags_json, updated_at
|
|
) VALUES (
|
|
:host_id, :hardware_address, :ip_address, :real_name, :device_name, :owner, :location, :device_type, :group_name, :desired_ip, :notes, :tags_json, CURRENT_TIMESTAMP
|
|
)"
|
|
);
|
|
}
|
|
|
|
$stmt->execute([
|
|
'host_id' => $hostId,
|
|
'hardware_address' => $hardwareAddress,
|
|
'ip_address' => $ipAddress,
|
|
'real_name' => $this->nullableString($metadata['real_name']),
|
|
'device_name' => $this->nullableString($metadata['device_name']),
|
|
'owner' => $this->nullableString($metadata['owner']),
|
|
'location' => $this->nullableString($metadata['location']),
|
|
'device_type' => $this->nullableString($metadata['device_type']),
|
|
'group_name' => $this->nullableString($metadata['group_name']),
|
|
'desired_ip' => $this->nullableString($metadata['desired_ip']),
|
|
'notes' => $this->nullableString($metadata['notes']),
|
|
'tags_json' => $tagsJson,
|
|
]);
|
|
}
|
|
|
|
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 <> ''"
|
|
);
|
|
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 child.id, child.name, child.description, child.parent_id, parent.name AS parent_name
|
|
FROM nexus_dhcp_groups child
|
|
LEFT JOIN nexus_dhcp_groups parent ON parent.id = child.parent_id
|
|
ORDER BY parent.name ASC, child.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'] ?? ''),
|
|
'parent_id' => $row['parent_id'] !== null ? (int)$row['parent_id'] : null,
|
|
'parent_name' => (string)($row['parent_name'] ?? ''),
|
|
'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 = '', string $parentName = ''): void
|
|
{
|
|
$name = trim($name);
|
|
if ($name === '') {
|
|
throw new \RuntimeException('Gruppenname fehlt.');
|
|
}
|
|
$parentName = trim($parentName);
|
|
if ($parentName !== '' && strcasecmp($parentName, $name) === 0) {
|
|
throw new \RuntimeException('Eine Gruppe kann nicht ihre eigene Untergruppe sein.');
|
|
}
|
|
$parentId = $parentName !== '' ? $this->groupIdByName($parentName) : null;
|
|
if ($parentName !== '' && !$parentId) {
|
|
throw new \RuntimeException('Uebergeordnete Gruppe wurde nicht gefunden.');
|
|
}
|
|
|
|
$driver = (string)$this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
|
|
if ($driver === 'pgsql' || $driver === 'sqlite') {
|
|
$sql = "INSERT INTO nexus_dhcp_groups (name, description, parent_id, updated_at)
|
|
VALUES (:name, :description, :parent_id, CURRENT_TIMESTAMP)
|
|
ON CONFLICT(name) DO UPDATE SET
|
|
description = excluded.description,
|
|
parent_id = excluded.parent_id,
|
|
updated_at = CURRENT_TIMESTAMP";
|
|
} else {
|
|
$sql = "INSERT INTO nexus_dhcp_groups (name, description, parent_id, updated_at)
|
|
VALUES (:name, :description, :parent_id, CURRENT_TIMESTAMP)
|
|
ON DUPLICATE KEY UPDATE
|
|
description = VALUES(description),
|
|
parent_id = VALUES(parent_id),
|
|
updated_at = CURRENT_TIMESTAMP";
|
|
}
|
|
|
|
$stmt = $this->pdo->prepare($sql);
|
|
$stmt->bindValue('name', $name);
|
|
$stmt->bindValue('description', $this->nullableString($description));
|
|
if ($parentId === null) {
|
|
$stmt->bindValue('parent_id', null, PDO::PARAM_NULL);
|
|
} else {
|
|
$stmt->bindValue('parent_id', $parentId, PDO::PARAM_INT);
|
|
}
|
|
$stmt->execute();
|
|
}
|
|
|
|
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['desired_ip'] ?? ''),
|
|
$stmt->fetchAll(PDO::FETCH_ASSOC)
|
|
)));
|
|
}
|
|
|
|
public function saveCheck(string $hostKey, string $checkType, string $status, array $result = [], ?string $nextCheckAt = null): void
|
|
{
|
|
$hostKey = trim($hostKey);
|
|
$checkType = trim($checkType);
|
|
$status = trim($status);
|
|
if ($hostKey === '' || $checkType === '' || $status === '') {
|
|
throw new \RuntimeException('Pruefdaten sind unvollstaendig.');
|
|
}
|
|
|
|
$resultJson = json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
if ($resultJson === false) {
|
|
$resultJson = '{}';
|
|
}
|
|
|
|
$driver = (string)$this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
|
|
if ($driver === 'pgsql') {
|
|
$stmt = $this->pdo->prepare(
|
|
"INSERT INTO nexus_dhcp_device_checks (
|
|
host_key, check_type, status, result_json, checked_at, next_check_at
|
|
) VALUES (
|
|
:host_key, :check_type, :status, CAST(:result_json AS jsonb), NOW(), :next_check_at
|
|
)"
|
|
);
|
|
} else {
|
|
$stmt = $this->pdo->prepare(
|
|
"INSERT INTO nexus_dhcp_device_checks (
|
|
host_key, check_type, status, result_json, checked_at, next_check_at
|
|
) VALUES (
|
|
:host_key, :check_type, :status, :result_json, CURRENT_TIMESTAMP, :next_check_at
|
|
)"
|
|
);
|
|
}
|
|
|
|
$stmt->execute([
|
|
'host_key' => $hostKey,
|
|
'check_type' => $checkType,
|
|
'status' => $status,
|
|
'result_json' => $resultJson,
|
|
'next_check_at' => $this->nullableString($nextCheckAt),
|
|
]);
|
|
}
|
|
|
|
public function latestChecks(array $hostKeys): array
|
|
{
|
|
$hostKeys = array_values(array_unique(array_filter(array_map(
|
|
static fn(mixed $value): string => trim((string)$value),
|
|
$hostKeys
|
|
))));
|
|
if ($hostKeys === [] || !$this->tableExists('nexus_dhcp_device_checks')) {
|
|
return [];
|
|
}
|
|
|
|
$params = [];
|
|
$placeholders = [];
|
|
foreach ($hostKeys as $idx => $hostKey) {
|
|
$key = ':host_key_' . $idx;
|
|
$placeholders[] = $key;
|
|
$params[$key] = $hostKey;
|
|
}
|
|
|
|
$stmt = $this->pdo->prepare(
|
|
'SELECT host_key, check_type, status, result_json, checked_at
|
|
FROM nexus_dhcp_device_checks
|
|
WHERE host_key IN (' . implode(', ', $placeholders) . ')
|
|
ORDER BY checked_at DESC, id DESC'
|
|
);
|
|
foreach ($params as $key => $value) {
|
|
$stmt->bindValue($key, $value);
|
|
}
|
|
$stmt->execute();
|
|
|
|
$items = [];
|
|
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|
$hostKey = (string)$row['host_key'];
|
|
$type = (string)$row['check_type'];
|
|
if (!isset($items[$hostKey][$type])) {
|
|
$items[$hostKey][$type] = $row;
|
|
}
|
|
}
|
|
|
|
return $items;
|
|
}
|
|
|
|
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,
|
|
parent_id BIGINT REFERENCES nexus_dhcp_groups(id) ON DELETE SET NULL,
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
)"
|
|
);
|
|
$this->ensureColumn('nexus_dhcp_groups', 'parent_id', 'BIGINT');
|
|
$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,
|
|
parent_id INTEGER,
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
)"
|
|
);
|
|
$this->ensureColumn('nexus_dhcp_groups', 'parent_id', 'INTEGER');
|
|
$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,
|
|
parent_id BIGINT NULL,
|
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
)"
|
|
);
|
|
$this->ensureColumn('nexus_dhcp_groups', 'parent_id', 'BIGINT NULL');
|
|
$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 ensureCheckSchema(string $driver): void
|
|
{
|
|
if ($driver === 'pgsql') {
|
|
$this->pdo->exec(
|
|
"CREATE TABLE IF NOT EXISTS nexus_dhcp_device_checks (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
host_key TEXT NOT NULL,
|
|
check_type TEXT NOT NULL,
|
|
status TEXT NOT NULL,
|
|
result_json JSONB,
|
|
checked_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
next_check_at TIMESTAMPTZ
|
|
)"
|
|
);
|
|
$this->pdo->exec(
|
|
'CREATE INDEX IF NOT EXISTS idx_nexus_dhcp_device_checks_host_type
|
|
ON nexus_dhcp_device_checks (host_key, check_type, checked_at)'
|
|
);
|
|
return;
|
|
}
|
|
|
|
if ($driver === 'sqlite') {
|
|
$this->pdo->exec(
|
|
"CREATE TABLE IF NOT EXISTS nexus_dhcp_device_checks (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
host_key TEXT NOT NULL,
|
|
check_type TEXT NOT NULL,
|
|
status TEXT NOT NULL,
|
|
result_json TEXT,
|
|
checked_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
next_check_at TEXT
|
|
)"
|
|
);
|
|
$this->pdo->exec(
|
|
'CREATE INDEX IF NOT EXISTS idx_nexus_dhcp_device_checks_host_type
|
|
ON nexus_dhcp_device_checks (host_key, check_type, checked_at)'
|
|
);
|
|
return;
|
|
}
|
|
|
|
$this->pdo->exec(
|
|
"CREATE TABLE IF NOT EXISTS nexus_dhcp_device_checks (
|
|
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
|
host_key VARCHAR(190) NOT NULL,
|
|
check_type VARCHAR(80) NOT NULL,
|
|
status VARCHAR(40) NOT NULL,
|
|
result_json JSON,
|
|
checked_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
next_check_at DATETIME NULL,
|
|
INDEX idx_nexus_dhcp_device_checks_host_type (host_key, check_type, checked_at)
|
|
)"
|
|
);
|
|
}
|
|
|
|
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)) {
|
|
return;
|
|
}
|
|
|
|
$driver = (string)$this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
|
|
$quote = $driver === 'mysql' ? '`' : '"';
|
|
try {
|
|
$this->pdo->exec(
|
|
'ALTER TABLE ' . $quote . $table . $quote
|
|
. ' ADD COLUMN ' . $quote . $column . $quote . ' ' . $definition
|
|
);
|
|
} catch (\PDOException $e) {
|
|
if (!$this->columnExists($table, $column)) {
|
|
throw $e;
|
|
}
|
|
}
|
|
}
|
|
|
|
private function columnExists(string $table, string $column): bool
|
|
{
|
|
$driver = (string)$this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
|
|
|
|
if ($driver === 'sqlite') {
|
|
$stmt = $this->pdo->query('PRAGMA table_info(' . $table . ')');
|
|
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|
if (($row['name'] ?? '') === $column) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if ($driver === 'pgsql') {
|
|
$stmt = $this->pdo->prepare(
|
|
"SELECT 1
|
|
FROM information_schema.columns
|
|
WHERE table_schema = current_schema()
|
|
AND table_name = :table
|
|
AND column_name = :column
|
|
LIMIT 1"
|
|
);
|
|
} else {
|
|
$stmt = $this->pdo->prepare(
|
|
"SELECT 1
|
|
FROM information_schema.columns
|
|
WHERE table_schema = DATABASE()
|
|
AND table_name = :table
|
|
AND column_name = :column
|
|
LIMIT 1"
|
|
);
|
|
}
|
|
$stmt->execute(['table' => $table, 'column' => $column]);
|
|
return (bool)$stmt->fetchColumn();
|
|
}
|
|
|
|
private function nullableString(mixed $value): ?string
|
|
{
|
|
$value = trim((string)$value);
|
|
return $value !== '' ? $value : null;
|
|
}
|
|
}
|