Add a KantTresor tab to the central registry
KantTresor instances (from 1.82.0) report to /api/kanttresor/melden with the same key-then-HMAC protocol. The new tab lists them with tenants, disk usage and failing services; it never offers or accepts an update order, since KantTresor is updated with ./update.sh on the server. See ADR 124. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9d80af3795
commit
968d19b611
15 files changed
+392
-80
No files matched your search
+46
-20
@@ -37,8 +37,8 @@ final class Anwendung
|
||||
return;
|
||||
}
|
||||
|
||||
if ('/api/melden' === $pfad) {
|
||||
$this->api($methode);
|
||||
if ('/api/melden' === $pfad || '/api/kanttresor/melden' === $pfad) {
|
||||
$this->api($methode, '/api/melden' === $pfad ? 'kantsites' : 'kanttresor');
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -77,7 +77,8 @@ final class Anwendung
|
||||
$bestand = new Bestand($this->db);
|
||||
|
||||
match (true) {
|
||||
'/' === $pfad && 'GET' === $methode => $this->uebersicht($bestand, $fassungen),
|
||||
'/' === $pfad && 'GET' === $methode => $this->uebersicht($bestand, $fassungen->neueste(), 'kantsites'),
|
||||
'/kanttresor' === $pfad && 'GET' === $methode => $this->uebersicht($bestand, $this->tresorNeueste($bestand), 'kanttresor'),
|
||||
'/installation' === $pfad && 'GET' === $methode => $this->detail($bestand, $fassungen),
|
||||
'/auftrag' === $pfad && 'POST' === $methode => $this->auftrag($bestand, $fassungen),
|
||||
'/auftrag/alle' === $pfad && 'POST' === $methode => $this->alleAuftraege($bestand, $fassungen),
|
||||
@@ -90,7 +91,7 @@ final class Anwendung
|
||||
};
|
||||
}
|
||||
|
||||
private function api(string $methode): void
|
||||
private function api(string $methode, string $produkt): void
|
||||
{
|
||||
header('Content-Type: application/json; charset=UTF-8');
|
||||
if ('POST' !== $methode) {
|
||||
@@ -100,7 +101,8 @@ final class Anwendung
|
||||
|
||||
return;
|
||||
}
|
||||
$antwort = (new Api($this->db))->melden(
|
||||
$api = new Api($this->db);
|
||||
$antwort = ('kanttresor' === $produkt ? $api->meldenTresor(...) : $api->melden(...))(
|
||||
(string) file_get_contents('php://input', false, null, 0, 16385),
|
||||
(string) ($_SERVER['HTTP_X_KANTSITES_SIGNATUR'] ?? ''),
|
||||
(string) ($_SERVER['REMOTE_ADDR'] ?? ''),
|
||||
@@ -112,10 +114,23 @@ final class Anwendung
|
||||
echo $antwort['body'];
|
||||
}
|
||||
|
||||
private function uebersicht(Bestand $bestand, Fassungen $fassungen): void
|
||||
/**
|
||||
* KantTresor hat keine öffentliche Releaseseite; die höchste gemeldete Fassung gilt als neueste.
|
||||
*
|
||||
* @return array{version: string}|null
|
||||
*/
|
||||
private function tresorNeueste(Bestand $bestand): ?array
|
||||
{
|
||||
$neueste = $fassungen->neueste();
|
||||
$alle = $bestand->alle($neueste['version'] ?? null);
|
||||
$hoechste = $bestand->hoechsteFassung('kanttresor');
|
||||
|
||||
return null === $hoechste ? null : ['version' => $hoechste];
|
||||
}
|
||||
|
||||
/** @param array{version: string}|null $neueste */
|
||||
private function uebersicht(Bestand $bestand, ?array $neueste, string $produkt): void
|
||||
{
|
||||
$tresor = 'kanttresor' === $produkt;
|
||||
$alle = $bestand->alle($neueste['version'] ?? null, $produkt);
|
||||
|
||||
// Die Zähler oben sind zugleich die Filter; sie zählen immer den ganzen Bestand.
|
||||
$filter = [
|
||||
@@ -123,8 +138,12 @@ final class Anwendung
|
||||
'veraltet' => static fn (array $i): bool => $i['veraltet'],
|
||||
'still' => static fn (array $i): bool => $i['still'],
|
||||
'unbekannt' => static fn (array $i): bool => $i['unbekannt'],
|
||||
'php' => static fn (array $i): bool => null !== $i['php_luecke'],
|
||||
];
|
||||
if ($tresor) {
|
||||
$filter['gestoert'] = static fn (array $i): bool => $i['gestoert'];
|
||||
} else {
|
||||
$filter['php'] = static fn (array $i): bool => null !== $i['php_luecke'];
|
||||
}
|
||||
$zahlen = ['alle' => count($alle)];
|
||||
foreach ($filter as $name => $passt) {
|
||||
$zahlen[$name] = count(array_filter($alle, $passt));
|
||||
@@ -142,7 +161,7 @@ final class Anwendung
|
||||
if ('' === $suche) {
|
||||
return true;
|
||||
}
|
||||
foreach (['domain', 'domain_vorher', 'adresse', 'notiz', 'fassung', 'php', 'ip'] as $feld) {
|
||||
foreach (['domain', 'domain_vorher', 'adresse', 'notiz', 'fassung', 'php', 'laufzeit', 'stoerungen', 'ip'] as $feld) {
|
||||
if (null !== $i[$feld] && false !== mb_stripos((string) $i[$feld], $suche)) {
|
||||
return true;
|
||||
}
|
||||
@@ -155,17 +174,20 @@ final class Anwendung
|
||||
$seiten = max(1, (int) ceil(count($treffer) / $proSeite));
|
||||
$seite = min($seiten, max(1, (int) ($_GET['seite'] ?? 1)));
|
||||
|
||||
$namen = ['aktuell' => 'aktuell', 'veraltet' => 'veraltet', 'still' => 'ohne Meldung seit 48 h', 'unbekannt' => 'ohne Kundenzuordnung', 'php' => 'PHP mit Sicherheitslücke'];
|
||||
$namen = ['aktuell' => 'aktuell', 'veraltet' => 'veraltet', 'still' => 'ohne Meldung seit 48 h', 'unbekannt' => 'ohne Kundenzuordnung', 'php' => 'PHP mit Sicherheitslücke', 'gestoert' => 'Dienst gestört oder Speicher knapp'];
|
||||
$von = [] === $treffer ? 0 : ($seite - 1) * $proSeite + 1;
|
||||
$bis = min(count($treffer), $seite * $proSeite);
|
||||
$trefferText = [] === $treffer ? 'Keine Treffer.' : sprintf('%d–%d von %d %s', $von, $bis, count($treffer), 1 === count($treffer) ? 'Installation' : 'Installationen')
|
||||
$einheit = $tresor ? (1 === count($treffer) ? 'Instanz' : 'Instanzen') : (1 === count($treffer) ? 'Installation' : 'Installationen');
|
||||
$trefferText = [] === $treffer ? 'Keine Treffer.' : sprintf('%d–%d von %d %s', $von, $bis, count($treffer), $einheit)
|
||||
.(null !== $gewaehlt ? ' · Filter: '.$namen[$gewaehlt] : '')
|
||||
.('' !== $suche ? ' · Suche: „'.$suche.'“' : '');
|
||||
|
||||
// Die Live-Suche holt nur die Liste; ohne JavaScript kommt die ganze Seite.
|
||||
$teil = 'liste' === ($_GET['teil'] ?? null);
|
||||
$this->zeigen($teil ? '_liste' : 'uebersicht', [
|
||||
'titel' => 'Installationen',
|
||||
'titel' => $tresor ? 'KantTresor' : 'Installationen',
|
||||
'produkt' => $produkt,
|
||||
'pfad' => $tresor ? '/kanttresor' : '/',
|
||||
'trefferText' => $trefferText,
|
||||
'neueste' => $neueste,
|
||||
'installationen' => array_slice($treffer, ($seite - 1) * $proSeite, $proSeite),
|
||||
@@ -176,20 +198,22 @@ final class Anwendung
|
||||
'seiten' => $seiten,
|
||||
'zahlen' => $zahlen,
|
||||
// Verstummte bleiben außen vor: Sie holen den Auftrag womöglich nie ab. Wartende haben schon einen.
|
||||
'aktualisierbar' => array_values(array_filter($alle, static fn (array $i): bool => $i['kann_update'] && !$i['still'] && 'wartet' !== ($i['auftrag_zustand']['art'] ?? null))),
|
||||
'aktualisierbar' => $tresor ? [] : array_values(array_filter($alle, static fn (array $i): bool => $i['kann_update'] && !$i['still'] && 'wartet' !== ($i['auftrag_zustand']['art'] ?? null))),
|
||||
], true, $teil);
|
||||
}
|
||||
|
||||
private function detail(Bestand $bestand, Fassungen $fassungen): void
|
||||
{
|
||||
$neueste = $fassungen->neueste();
|
||||
$eine = $bestand->eine((string) ($_GET['id'] ?? ''), $neueste['version'] ?? null);
|
||||
$id = (string) ($_GET['id'] ?? '');
|
||||
$produkt = $bestand->produkt($id);
|
||||
$neueste = 'kanttresor' === $produkt ? $this->tresorNeueste($bestand) : $fassungen->neueste();
|
||||
$eine = $bestand->eine($id, $neueste['version'] ?? null);
|
||||
if (null === $eine) {
|
||||
$this->nichtGefunden();
|
||||
|
||||
return;
|
||||
}
|
||||
$this->zeigen('installation', ['titel' => $eine['domain'], 'i' => $eine, 'neueste' => $neueste]);
|
||||
$this->zeigen('installation', ['titel' => $eine['domain'], 'i' => $eine, 'neueste' => $neueste, 'produkt' => $eine['produkt']]);
|
||||
}
|
||||
|
||||
private function auftrag(Bestand $bestand, Fassungen $fassungen): void
|
||||
@@ -244,9 +268,10 @@ final class Anwendung
|
||||
|
||||
return;
|
||||
}
|
||||
$produkt = $bestand->produkt($id);
|
||||
$bestand->entfernen($id);
|
||||
$this->hinweis('Eintrag entfernt. Meldet sich die Installation wieder, erscheint sie neu.', 'ok');
|
||||
$this->weiter('/');
|
||||
$this->weiter('kanttresor' === $produkt ? '/kanttresor' : '/');
|
||||
}
|
||||
|
||||
private function fassungNeu(Fassungen $fassungen): void
|
||||
@@ -373,20 +398,21 @@ final class Anwendung
|
||||
private function zeigen(string $vorlage, array $daten, bool $angemeldet = true, bool $ohneRahmen = false): void
|
||||
{
|
||||
header('Content-Type: text/html; charset=UTF-8');
|
||||
$v = new Ansicht((string) ($_SESSION['csrf'] ?? ''), (string) ($daten['pfad'] ?? '/'));
|
||||
if ($ohneRahmen) {
|
||||
echo (new Ansicht((string) ($_SESSION['csrf'] ?? '')))->vorlage($this->wurzel.'/app/vorlagen/'.$vorlage.'.php', $daten);
|
||||
echo $v->vorlage($this->wurzel.'/app/vorlagen/'.$vorlage.'.php', $daten);
|
||||
|
||||
return;
|
||||
}
|
||||
$hinweis = $_SESSION['hinweis'] ?? null;
|
||||
unset($_SESSION['hinweis']);
|
||||
$v = new Ansicht((string) ($_SESSION['csrf'] ?? ''));
|
||||
$inhalt = $v->vorlage($this->wurzel.'/app/vorlagen/'.$vorlage.'.php', $daten);
|
||||
echo $v->vorlage($this->wurzel.'/app/vorlagen/rahmen.php', [
|
||||
'titel' => $daten['titel'] ?? 'Zentrale',
|
||||
'inhalt' => $inhalt,
|
||||
'hinweis' => $hinweis,
|
||||
'konto' => $angemeldet ? ($_SESSION['konto'] ?? null) : null,
|
||||
'produkt' => $daten['produkt'] ?? null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user