Wartung: Update-Ausgabe live streamen, Zeitstempel pro Zeile, Auto-Scroll

- Neue _run_ssh_command_streaming(): liest stdout/stderr zeilenweise WÄHREND
  das SSH-Update noch läuft, statt alles erst nach Abschluss auf einmal zu
  liefern. Jede Zeile bekommt sofort einen eigenen Zeitstempel und wird per
  _maintenance_job_set() live in den Job-Status geschrieben (bereits alle
  2s gepollt) -- Puffer auf die letzten 400 Zeilen begrenzt statt eines
  harten Zeichen-Limits.
- Eindeutiger Abschluss-Marker als letzte Ausgabezeile: "✔ All Updates
  Done" bei Erfolg, "✖ Update abgebrochen/fehlgeschlagen …" bei Fehler --
  vorher endete die Ausgabe einfach mitten im rohen apt-Output.
- Frontend: Ausgabefenster scrollt jetzt automatisch mit, sobald neue
  Zeilen reinkommen ("stick to bottom", solange man nicht selbst
  hochgescrollt hat) und beim ersten Öffnen direkt ans Ende.
- Live auf POETEST verifiziert: Zeilen tragen korrekte Zeitstempel, Update
  endet sichtbar mit "✔ All Updates Done".
This commit is contained in:
2026-08-13 16:02:07 +02:00
parent 51d105399b
commit 4c13127679
2 changed files with 160 additions and 12 deletions
+21 -2
View File
@@ -152,7 +152,22 @@ function filterTable(inputId, tableId) {
function toggleJobOutput(mac) {
const row = document.getElementById("joboutput-" + mac);
if (row) row.style.display = (row.style.display === "none" || !row.style.display) ? "table-row" : "none";
if (!row) return;
const opening = row.style.display === "none" || !row.style.display;
row.style.display = opening ? "table-row" : "none";
if (opening) {
const pre = row.querySelector(".job-output-pre");
if (pre) pre.scrollTop = pre.scrollHeight;
}
}
// "Klebt" am unteren Rand, solange man nicht selbst nach oben gescrollt hat
// -- reine Update-Ausgabe kann bei vielen Paketen sehr lang werden, ohne
// das würde man sie sonst manuell nachziehen müssen, um live mitzulesen.
// Ein kleiner Schwellwert (20px) toleriert, dass "ganz unten" durch
// Rundungsfehler beim Scrollen selten exakt 0 ist.
function isNearBottom(el) {
return el.scrollHeight - el.scrollTop - el.clientHeight < 20;
}
function statusPillHtml(job) {
@@ -177,7 +192,11 @@ function pollMaintenanceStatus() {
const outRow = document.getElementById("joboutput-" + mac);
if (outRow && job) {
const pre = outRow.querySelector(".job-output-pre");
if (pre) pre.textContent = job.output || "Keine Ausgabe.";
if (pre) {
const stick = isNearBottom(pre);
pre.textContent = job.output || "Keine Ausgabe.";
if (stick) pre.scrollTop = pre.scrollHeight;
}
}
if (job && job.status === "running") anyRunning = true;
});