Zuweisungen von Public Keys an Hosts oder Hostgruppen, ein Trockenlauf mit Diff je Ziel und das Ausrollen genau dieses Plans. Hat sich die Datei oder das Soll seit dem Trockenlauf geaendert, bricht das Ziel ab: es wird nie etwas anderes geschrieben, als jemand gesehen hat. Auf dem Host geschieht alles als Zielbenutzer, mit Sicherung, Temp- Datei, mv und 0700/0600; der Hash wird dort erneut geprueft. Der Deployment-Key gehoert immer zum Soll des Anmeldebenutzers. Integrationstests gegen echtes sshd, die Oberflaechenbausteine folgen mit der Schluesselseite aus #012. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
54 lines
1.7 KiB
TypeScript
54 lines
1.7 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
|
|
/** Was /api/vorgaenge/[id] liefert (#009, #016). */
|
|
export type VorgangsStand = {
|
|
readonly id: string;
|
|
readonly status: string;
|
|
readonly fehler: string | null;
|
|
readonly plan: unknown;
|
|
readonly ergebnis: unknown;
|
|
};
|
|
|
|
const LAUFEND = new Set(["WARTEND", "LAEUFT"]);
|
|
|
|
export function laeuftNoch(stand: VorgangsStand | undefined): boolean {
|
|
return stand !== undefined && LAUFEND.has(stand.status);
|
|
}
|
|
|
|
/** Fragt einen Vorgang jede Sekunde ab, bis er fertig ist. */
|
|
export function useVorgang(vorgangId: string | undefined): { stand?: VorgangsStand; fehler?: string } {
|
|
// Der Stand merkt sich seine Vorgangs-ID: so zeigt ein neuer Vorgang nie den alten Stand,
|
|
// ohne dass der Effekt synchron zuruecksetzen muss.
|
|
const [stand, setStand] = useState<VorgangsStand>();
|
|
const [fehler, setFehler] = useState<{ id: string; text: string }>();
|
|
|
|
useEffect(() => {
|
|
if (!vorgangId) return;
|
|
let abgebrochen = false;
|
|
void (async () => {
|
|
while (!abgebrochen) {
|
|
const antwort = await fetch(`/api/vorgaenge/${vorgangId}`, { cache: "no-store" });
|
|
if (!antwort.ok) {
|
|
setFehler({ id: vorgangId, text: "Status des Vorgangs nicht abrufbar." });
|
|
return;
|
|
}
|
|
const neu = (await antwort.json()) as VorgangsStand;
|
|
if (abgebrochen) return;
|
|
setStand(neu);
|
|
if (!LAUFEND.has(neu.status)) return;
|
|
await new Promise((weiter) => setTimeout(weiter, 1000));
|
|
}
|
|
})();
|
|
return () => {
|
|
abgebrochen = true;
|
|
};
|
|
}, [vorgangId]);
|
|
|
|
return {
|
|
...(stand && stand.id === vorgangId ? { stand } : {}),
|
|
...(fehler && fehler.id === vorgangId ? { fehler: fehler.text } : {}),
|
|
};
|
|
}
|