asdad
This commit is contained in:
25
modules/boersenchecker/module.json
Normal file
25
modules/boersenchecker/module.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"title": "Börsenchecker",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Grundgeruest fuer ein Nexus-Modul zur Beobachtung und Auswertung von Boersenwerten.",
|
||||||
|
"enabled_by_default": false,
|
||||||
|
"menu": [
|
||||||
|
{ "label": "Übersicht", "href": "/module/boersenchecker" }
|
||||||
|
],
|
||||||
|
"sidebar": {
|
||||||
|
"enabled": true,
|
||||||
|
"collapsible": true,
|
||||||
|
"default": "collapsed",
|
||||||
|
"items": [
|
||||||
|
{ "label": "Übersicht", "href": "/module/boersenchecker" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"setup": {
|
||||||
|
"fields": []
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"required": true,
|
||||||
|
"users": [],
|
||||||
|
"groups": []
|
||||||
|
}
|
||||||
|
}
|
||||||
28
modules/boersenchecker/pages/index.php
Normal file
28
modules/boersenchecker/pages/index.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
?>
|
||||||
|
<div class="card">
|
||||||
|
<div class="pill">Börsenchecker</div>
|
||||||
|
<h1 style="margin-top:.75rem;">Börsenchecker</h1>
|
||||||
|
<p class="muted">
|
||||||
|
Das Modul ist im Nexus registriert und kann jetzt schrittweise um Datenquellen,
|
||||||
|
Watchlists, Kennzahlen und Benachrichtigungen erweitert werden.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="card" style="margin-top:1rem; background:var(--panel-2);">
|
||||||
|
<strong>Status</strong>
|
||||||
|
<div class="muted" style="margin-top:.5rem;">
|
||||||
|
Aktuell ist dies das initiale Grundgeruest des Moduls.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card" style="margin-top:1rem; background:var(--panel-2);">
|
||||||
|
<strong>Nächste sinnvolle Ausbaustufen</strong>
|
||||||
|
<ul style="margin:.75rem 0 0 1.1rem;">
|
||||||
|
<li>Watchlist fuer Ticker und ISINs</li>
|
||||||
|
<li>Kursdaten per API einlesen</li>
|
||||||
|
<li>Performance, Tagesdelta und historische Trends anzeigen</li>
|
||||||
|
<li>Alerts fuer Schwellwerte definieren</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
91
modules/kea/pages/data.php
Normal file
91
modules/kea/pages/data.php
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
<?php
|
||||||
|
use App\Database;
|
||||||
|
use App\Repository\KeaHostMetadataRepository;
|
||||||
|
use App\Repository\KeaHostRepository;
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
header('Cache-Control: no-store');
|
||||||
|
|
||||||
|
$module = modules()->get('kea');
|
||||||
|
$fallback = $module['db_defaults'] ?? [];
|
||||||
|
$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;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$metadataRepo = null;
|
||||||
|
$pdo = modules()->modulePdo('kea', $fallback);
|
||||||
|
if (!empty($metadataConfig['driver']) && !empty($metadataConfig['dbname'])) {
|
||||||
|
try {
|
||||||
|
$metadataRepo = new KeaHostMetadataRepository(Database::createFromArray($metadataConfig));
|
||||||
|
$metadataRepo->ensureSchema();
|
||||||
|
} catch (\Throwable) {
|
||||||
|
$metadataRepo = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$repo = new KeaHostRepository($pdo, $metadataRepo);
|
||||||
|
$hosts = $repo->findAll(200);
|
||||||
|
$stats = [
|
||||||
|
'total' => $repo->countReservations() + $repo->countLeases(),
|
||||||
|
'reservations' => $repo->countReservations(),
|
||||||
|
'leases' => $repo->countLeases(),
|
||||||
|
'groups' => [],
|
||||||
|
'free_ips' => [],
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($hosts as $host) {
|
||||||
|
$group = trim((string)($host['metadata']['group_name'] ?? ''));
|
||||||
|
if ($group !== '') {
|
||||||
|
$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
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$rows = array_map(static function (array $host): array {
|
||||||
|
return [
|
||||||
|
'source' => (string)($host['source'] ?? 'reservation'),
|
||||||
|
'host_id' => (string)($host['host_id'] ?? '0'),
|
||||||
|
'display_name' => (string)($host['metadata']['device_name'] ?? $host['metadata']['real_name'] ?? $host['display_name'] ?? $host['hostname'] ?? 'Unbekannt'),
|
||||||
|
'ipv4_address' => (string)($host['ipv4_address'] ?? ''),
|
||||||
|
'dhcp_identifier' => (string)($host['dhcp_identifier'] ?? ''),
|
||||||
|
'last_seen_at' => (string)($host['last_seen_at'] ?? '-'),
|
||||||
|
'lease_expires_at' => (string)($host['lease_expires_at'] ?? '-'),
|
||||||
|
'real_name' => (string)($host['metadata']['real_name'] ?? '-'),
|
||||||
|
'location' => (string)($host['metadata']['location'] ?? '-'),
|
||||||
|
'group_name' => (string)($host['metadata']['group_name'] ?? '-'),
|
||||||
|
];
|
||||||
|
}, $hosts);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'ok' => true,
|
||||||
|
'stats' => [
|
||||||
|
'total' => (int)$stats['total'],
|
||||||
|
'reservations' => (int)$stats['reservations'],
|
||||||
|
'leases' => (int)$stats['leases'],
|
||||||
|
'groups' => count($stats['groups']),
|
||||||
|
'free_ips' => array_sum($stats['free_ips']),
|
||||||
|
],
|
||||||
|
'rows' => $rows,
|
||||||
|
'updated_at' => date('H:i:s'),
|
||||||
|
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode([
|
||||||
|
'ok' => false,
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||||
|
}
|
||||||
|
|
||||||
|
exit;
|
||||||
@@ -11,6 +11,9 @@
|
|||||||
<div>
|
<div>
|
||||||
<h2 class="section-title">KEA DHCP Hosts</h2>
|
<h2 class="section-title">KEA DHCP Hosts</h2>
|
||||||
<p>Reservierungen und aktuelle Leases aus der KEA-Datenbank.</p>
|
<p>Reservierungen und aktuelle Leases aus der KEA-Datenbank.</p>
|
||||||
|
<p class="muted kea-refresh-state" data-kea-refresh-state>
|
||||||
|
Automatische Aktualisierung alle 5 Sekunden.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="setup-actions">
|
<div class="setup-actions">
|
||||||
<a class="cta-button" href="/module/kea/groups">Gruppen verwalten</a>
|
<a class="cta-button" href="/module/kea/groups">Gruppen verwalten</a>
|
||||||
@@ -34,23 +37,23 @@
|
|||||||
<div class="stats">
|
<div class="stats">
|
||||||
<div class="stat-card">
|
<div class="stat-card">
|
||||||
<span class="stat-label">Einträge</span>
|
<span class="stat-label">Einträge</span>
|
||||||
<span class="stat-value"><?= e((string)($stats['total'] ?? 0)) ?></span>
|
<span class="stat-value" data-kea-stat="total"><?= e((string)($stats['total'] ?? 0)) ?></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card">
|
<div class="stat-card">
|
||||||
<span class="stat-label">Reservierungen</span>
|
<span class="stat-label">Reservierungen</span>
|
||||||
<span class="stat-value"><?= e((string)($stats['reservations'] ?? 0)) ?></span>
|
<span class="stat-value" data-kea-stat="reservations"><?= e((string)($stats['reservations'] ?? 0)) ?></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card">
|
<div class="stat-card">
|
||||||
<span class="stat-label">Leases</span>
|
<span class="stat-label">Leases</span>
|
||||||
<span class="stat-value"><?= e((string)($stats['leases'] ?? 0)) ?></span>
|
<span class="stat-value" data-kea-stat="leases"><?= e((string)($stats['leases'] ?? 0)) ?></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card">
|
<div class="stat-card">
|
||||||
<span class="stat-label">Gruppen</span>
|
<span class="stat-label">Gruppen</span>
|
||||||
<span class="stat-value"><?= e((string)count($stats['groups'] ?? [])) ?></span>
|
<span class="stat-value" data-kea-stat="groups"><?= e((string)count($stats['groups'] ?? [])) ?></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card">
|
<div class="stat-card">
|
||||||
<span class="stat-label">Freie Gruppen-IPs</span>
|
<span class="stat-label">Freie Gruppen-IPs</span>
|
||||||
<span class="stat-value"><?= e((string)array_sum($stats['free_ips'] ?? [])) ?></span>
|
<span class="stat-value" data-kea-stat="free_ips"><?= e((string)array_sum($stats['free_ips'] ?? [])) ?></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -78,7 +81,7 @@
|
|||||||
<th>Aktion</th>
|
<th>Aktion</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody data-kea-host-rows>
|
||||||
<?php if (empty($hosts)): ?>
|
<?php if (empty($hosts)): ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="10" class="kea-empty">
|
<td colspan="10" class="kea-empty">
|
||||||
@@ -128,3 +131,98 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
<script>
|
||||||
|
(() => {
|
||||||
|
const rowsTarget = document.querySelector('[data-kea-host-rows]');
|
||||||
|
const stateTarget = document.querySelector('[data-kea-refresh-state]');
|
||||||
|
if (!rowsTarget) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const setText = (selector, value) => {
|
||||||
|
const target = document.querySelector(selector);
|
||||||
|
if (target) {
|
||||||
|
target.textContent = String(value ?? '0');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const cell = (value, className = '') => {
|
||||||
|
const td = document.createElement('td');
|
||||||
|
if (className) {
|
||||||
|
td.className = className;
|
||||||
|
}
|
||||||
|
td.textContent = value || '-';
|
||||||
|
return td;
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderRows = (rows) => {
|
||||||
|
rowsTarget.textContent = '';
|
||||||
|
if (!Array.isArray(rows) || rows.length === 0) {
|
||||||
|
const tr = document.createElement('tr');
|
||||||
|
const td = cell('Keine Reservierungen oder aktiven Leases gefunden.', 'kea-empty');
|
||||||
|
td.colSpan = 10;
|
||||||
|
tr.appendChild(td);
|
||||||
|
rowsTarget.appendChild(tr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const row of rows) {
|
||||||
|
const tr = document.createElement('tr');
|
||||||
|
const source = document.createElement('td');
|
||||||
|
const pill = document.createElement('span');
|
||||||
|
pill.className = 'pill';
|
||||||
|
pill.textContent = row.source === 'lease' ? 'Lease' : 'Reservierung';
|
||||||
|
source.appendChild(pill);
|
||||||
|
tr.appendChild(source);
|
||||||
|
|
||||||
|
tr.appendChild(cell(row.display_name));
|
||||||
|
tr.appendChild(cell(row.ipv4_address, 'mono'));
|
||||||
|
tr.appendChild(cell(row.dhcp_identifier, 'mono'));
|
||||||
|
tr.appendChild(cell(row.last_seen_at));
|
||||||
|
tr.appendChild(cell(row.lease_expires_at));
|
||||||
|
tr.appendChild(cell(row.real_name));
|
||||||
|
tr.appendChild(cell(row.location));
|
||||||
|
tr.appendChild(cell(row.group_name));
|
||||||
|
|
||||||
|
const action = document.createElement('td');
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.className = 'nav-link';
|
||||||
|
link.href = `/module/kea/edit?source=${encodeURIComponent(row.source || 'reservation')}&id=${encodeURIComponent(row.host_id || '0')}`;
|
||||||
|
link.textContent = 'Bearbeiten';
|
||||||
|
action.appendChild(link);
|
||||||
|
tr.appendChild(action);
|
||||||
|
|
||||||
|
rowsTarget.appendChild(tr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const refresh = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/module/kea/data', {
|
||||||
|
headers: {Accept: 'application/json'},
|
||||||
|
cache: 'no-store',
|
||||||
|
});
|
||||||
|
const payload = await response.json();
|
||||||
|
if (!response.ok || !payload.ok) {
|
||||||
|
throw new Error(payload.error || 'Aktualisierung fehlgeschlagen.');
|
||||||
|
}
|
||||||
|
|
||||||
|
setText('[data-kea-stat="total"]', payload.stats?.total);
|
||||||
|
setText('[data-kea-stat="reservations"]', payload.stats?.reservations);
|
||||||
|
setText('[data-kea-stat="leases"]', payload.stats?.leases);
|
||||||
|
setText('[data-kea-stat="groups"]', payload.stats?.groups);
|
||||||
|
setText('[data-kea-stat="free_ips"]', payload.stats?.free_ips);
|
||||||
|
renderRows(payload.rows);
|
||||||
|
if (stateTarget) {
|
||||||
|
stateTarget.textContent = `Automatische Aktualisierung aktiv. Zuletzt aktualisiert: ${payload.updated_at || '-'}`;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (stateTarget) {
|
||||||
|
stateTarget.textContent = `Automatische Aktualisierung fehlgeschlagen: ${error.message}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.setInterval(refresh, 5000);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user