Add a KantTresor tab to the central registry
CMS quality / php (8.2) (push) Canceled after 0s
CMS quality / php (8.4) (push) Canceled after 0s
CMS quality / browser (push) Canceled after 0s
CMS quality / mysql (push) Canceled after 0s

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:
kantarciandClaude Opus 5.5 committed 2026-10-27 10:02:07 +01:00
1 parent 9d80af3795
commit 968d19b611
15 files changed
+392 -80

No files matched your search

+46 -20
View File
@@ -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,
]);
}
}