WebUI: Update-Status anzeigen, installiert wird im Desktop

Umsetzung von #565.

GET /api/updates liefert die laufende Fassung (BuildInfo), die verfuegbare,
den Hinweis auf ein Sicherheitsupdate und den Changelog. Geprueft wird mit
demselben Aufruf wie beim Start des Desktops (UpdateClient gegen das
Deploymentcenter). Den Lizenzschluessel fuer den Paketabruf liest der Server,
er verlaesst ihn nicht.

Zwischenspeicher: UpdateStatusService haelt das Ergebnis 30 Minuten vor.
"Jetzt pruefen" umgeht das, aber fruehestens nach 60 Sekunden; bis dahin kommt
der vorige Stand mit der Wartezeit zurueck. Gleichzeitige Aufrufe loesen eine
einzige Pruefung aus. Ohne das fragte jeder Seitenaufruf, jedes offene
Browserfenster, das Deploymentcenter. Die Pruefung liegt hinter IUpdateChecker
und ist damit ohne Netz testbar.

Kein Installieren ueber das WebUI, und ein Test haelt fest, dass es keine
solche Route gibt: Die Installation startet den Update-Agent und beendet
danach die Anwendung und mit ihr den Webserver. Richards Vorgabe beschraenkt
das Beenden ueber das WebUI auf das Trading (#563).

Oberflaeche:
- Karte "Version & Updates" in den Einstellungen, mit "Jetzt pruefen".
- Im Dashboard ein Banner wie im Desktop-Launcher, sobald ein Update vorliegt.
  Er verweist zur Installation auf den Desktop.

Nicht durch Unit-Tests abgedeckt: DeploymentcenterUpdateChecker selbst, weil
er ueber das Netz geht. Er wird auf der VM gegen das echte Deploymentcenter
geprueft.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-09-13 14:20:07 +02:00
co-authored by Claude Opus 5
parent 0067e0b761
commit bc96807d81
6 changed files with 417 additions and 0 deletions
+57
View File
@@ -6,6 +6,7 @@ let allLogs = [];
// Init on DOM ready
document.addEventListener('DOMContentLoaded', () => {
loadUpdateStatus(false); // #565: Banner im Dashboard; der Server haelt das Ergebnis vor
document.addEventListener('keydown', e => {
if (e.key === 'Escape' && document.getElementById('modalStopTrading')?.style.display === 'flex') closeStopTradingModal();
});
@@ -813,6 +814,7 @@ async function copyLogViewer() {
// Settings View
async function loadSettings() {
loadServerSettings();
loadUpdateStatus(false);
try {
const res = await fetch('/api/settings/webui');
if (!res.ok) return;
@@ -829,6 +831,61 @@ async function loadSettings() {
} catch (e) {}
}
// ==================== Updates (#565) ====================
async function loadUpdateStatus(refresh) {
const box = document.getElementById('updateStatusBody');
try {
const res = await fetch('/api/updates' + (refresh ? '?refresh=true' : ''));
if (!res.ok) throw new Error(await errorText(res));
renderUpdateStatus(await res.json(), refresh);
} catch (e) {
if (box) {
box.innerHTML = '';
appendDefinition(box, 'Stand', `Update-Status nicht abrufbar: ${e.message}`);
}
}
}
function appendDefinition(list, term, value) {
const dt = document.createElement('dt');
dt.textContent = term;
const dd = document.createElement('dd');
dd.textContent = value;
list.append(dt, dd);
}
function renderUpdateStatus(u, refreshRequested) {
let state;
if (!u.enabled) state = u.error || 'Die Update-Prüfung ist nicht eingerichtet.';
else if (u.error) state = u.error;
else if (u.updateAvailable) state = `Update verfügbar: v${u.latestVersion}${u.isCritical ? ' – empfohlenes Sicherheitsupdate' : ''}`;
else state = 'Diese Fassung ist aktuell.';
const box = document.getElementById('updateStatusBody');
if (box) {
box.innerHTML = '';
appendDefinition(box, 'Laufende Fassung', u.currentBuild);
appendDefinition(box, 'Stand', state);
appendDefinition(box, 'Geprüft', new Date(u.checkedAt).toLocaleString('de-DE') + (u.fromCache ? ' (zwischengespeichert)' : ''));
if (u.updateAvailable && u.changelog) appendDefinition(box, 'Änderungen', u.changelog);
appendDefinition(box, 'Installation', 'Nur im Desktop – sie beendet die Anwendung und damit auch das WebUI.');
}
const msg = document.getElementById('updateStatusMsg');
if (msg) {
msg.hidden = !(refreshRequested && u.retryAfterSeconds);
if (!msg.hidden) msg.textContent = `Gerade erst geprüft – erneut möglich in ${u.retryAfterSeconds} s.`;
}
const banner = document.getElementById('dashUpdateBanner');
if (banner) {
banner.hidden = !u.updateAvailable;
if (u.updateAvailable)
banner.textContent = `Update verfügbar: v${u.latestVersion}${u.isCritical ? ' (empfohlenes Sicherheitsupdate)' : ''} – Installation über den Desktop.`;
}
}
// ==================== Server-Einstellungen (#561) ====================
let serverSettingsLoaded = {};