feat: implement update management web UI

Add complete update management interface to web UI:
- Update status display (current version, last status, timestamp, errors)
- Check for updates button with availability indicator
- Apply update button with confirmation dialog and backup warnings
- Rollback button with confirmation dialog
- Update logs viewer (collapsible, reverse chronological)
- Auto-refresh after update/rollback actions (5s delay)

Features:
- Uses existing Pico CSS framework for consistent styling
- Integrates with existing auth system (Bearer token/session cookies)
- Real-time feedback with loading states and error handling
- German UI language matching existing interface
- All API calls use existing api() helper function

Complete US_000029-033 and TASK_000030-034:
- US_000029: Display update status in web UI
- US_000030: Trigger update check from UI
- US_000031: Apply updates from UI
- US_000032: Display update logs in UI
- US_000033: Trigger rollback from UI

EPIC_000008 (Client-Side Update Mechanism) now fully complete.
Documentation updated per SOP (CHANGELOG, PROJECT_STATUS, stories/tasks).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-30 11:32:39 +01:00
parent 107cdabe8d
commit d799c0f042
13 changed files with 210 additions and 25 deletions

View File

@ -38,6 +38,7 @@ By: Codex (GPT-5)
| 29.12.2025 | ⚙️ Code | ID: Update-Models und Settings erweitert (Models, Status-File Paths). By: Codex (GPT-5) |
| 29.12.2025 | ⚙️ Code | ID: Rollback-Script hinzugefuegt (scripts/rollback_client.sh). By: Codex (GPT-5) |
| 30.12.2025 | ⚙️ Code | ID: Update-API Endpunkte implementiert (GET /update/status, POST /update/check, POST /update/apply, POST /update/rollback, GET /update/logs). By: Claude Sonnet 4.5 |
| 30.12.2025 | ⚙️ Code | ID: Update-UI im Web-Frontend implementiert (Status-Anzeige, Check/Apply/Rollback Buttons, Logs-Viewer). By: Claude Sonnet 4.5 |
---
## Legende

View File

@ -84,6 +84,27 @@
<div id="result" class="log"></div>
</section>
<section>
<h3>Update-Verwaltung</h3>
<div id="updateStatus" class="log" style="margin-bottom: 1rem; padding: 1rem; background: var(--pico-card-background-color); border-radius: var(--pico-border-radius);">
Lade Update-Status...
</div>
<div class="grid">
<button id="checkUpdateBtn" type="button">Nach Updates suchen</button>
<button id="applyUpdateBtn" type="button" disabled>Update installieren</button>
<button id="rollbackBtn" type="button">Rollback durchführen</button>
</div>
<div id="updateResult" class="log" style="margin-top: 1rem;"></div>
<details style="margin-top: 1.5rem;">
<summary>Update-Logs anzeigen</summary>
<button id="refreshLogsBtn" type="button" style="margin-top: 0.5rem;">Logs neu laden</button>
<div id="updateLogs" class="log" style="margin-top: 1rem; max-height: 400px; overflow-y: auto;"></div>
</details>
</section>
<script>
const statusDiv = document.getElementById('status');
const resultDiv = document.getElementById('result');
@ -221,6 +242,169 @@
checkSession();
checkOidcStatus();
// ==================== Update Management ====================
const updateStatusDiv = document.getElementById('updateStatus');
const updateResultDiv = document.getElementById('updateResult');
const updateLogsDiv = document.getElementById('updateLogs');
const checkUpdateBtn = document.getElementById('checkUpdateBtn');
const applyUpdateBtn = document.getElementById('applyUpdateBtn');
const rollbackBtn = document.getElementById('rollbackBtn');
const refreshLogsBtn = document.getElementById('refreshLogsBtn');
let latestUpdateCheck = null;
async function refreshUpdateStatus() {
try {
const data = await api('/update/status');
const statusText = `
Version: ${data.current_version}
Letzter Status: ${data.last_status || 'unbekannt'}
${data.last_error ? `Fehler: ${data.last_error}` : ''}
${data.last_timestamp ? `Zeitstempel: ${new Date(data.last_timestamp).toLocaleString('de-DE')}` : ''}
`.trim();
updateStatusDiv.textContent = statusText;
} catch (err) {
updateStatusDiv.textContent = `Fehler beim Laden des Update-Status: ${err.message}`;
}
}
checkUpdateBtn.addEventListener('click', async () => {
updateResultDiv.textContent = 'Prüfe auf Updates...';
checkUpdateBtn.disabled = true;
try {
const data = await api('/update/check', { method: 'POST' });
latestUpdateCheck = data;
if (data.available) {
updateResultDiv.textContent = `
✅ Update verfügbar!
Version: ${data.latest_version}
${data.message ? `Info: ${data.message}` : ''}
Klicke auf "Update installieren" um fortzufahren.
`.trim();
applyUpdateBtn.disabled = false;
} else {
updateResultDiv.textContent = `✓ Keine Updates verfügbar. Aktuelle Version ist aktuell.`;
applyUpdateBtn.disabled = true;
}
} catch (err) {
updateResultDiv.textContent = `❌ Fehler beim Update-Check: ${err.message}`;
applyUpdateBtn.disabled = true;
} finally {
checkUpdateBtn.disabled = false;
}
});
applyUpdateBtn.addEventListener('click', async () => {
if (!latestUpdateCheck || !latestUpdateCheck.available) {
updateResultDiv.textContent = '❌ Bitte zuerst nach Updates suchen.';
return;
}
const confirmed = confirm(
`Update auf Version ${latestUpdateCheck.latest_version} installieren?\n\n` +
`⚠️ WICHTIG:\n` +
`- Ein Backup wird automatisch erstellt\n` +
`- Der Service wird neu gestartet\n` +
`- Bei Fehlern erfolgt automatischer Rollback\n\n` +
`Fortfahren?`
);
if (!confirmed) return;
updateResultDiv.textContent = 'Update wird gestartet... (läuft im Hintergrund)';
applyUpdateBtn.disabled = true;
try {
const data = await api('/update/apply', {
method: 'POST',
body: JSON.stringify({ version: latestUpdateCheck.latest_version })
});
updateResultDiv.textContent = `
✓ ${data.message}
Das Update läuft jetzt im Hintergrund.
Aktualisiere den Status in wenigen Sekunden, um den Fortschritt zu sehen.
`.trim();
// Auto-refresh nach 5 Sekunden
setTimeout(() => {
refreshUpdateStatus();
applyUpdateBtn.disabled = true;
}, 5000);
} catch (err) {
updateResultDiv.textContent = `❌ Fehler beim Starten des Updates: ${err.message}`;
applyUpdateBtn.disabled = false;
}
});
rollbackBtn.addEventListener('click', async () => {
const confirmed = confirm(
`Rollback zum letzten Backup durchführen?\n\n` +
`⚠️ WICHTIG:\n` +
`- Dies stellt die vorherige Version wieder her\n` +
`- Der Service wird neu gestartet\n` +
`- Ein Backup muss vorhanden sein\n\n` +
`Fortfahren?`
);
if (!confirmed) return;
updateResultDiv.textContent = 'Rollback wird gestartet... (läuft im Hintergrund)';
rollbackBtn.disabled = true;
try {
const data = await api('/update/rollback', { method: 'POST' });
updateResultDiv.textContent = `
✓ ${data.message}
Der Rollback läuft jetzt im Hintergrund.
Aktualisiere den Status in wenigen Sekunden.
`.trim();
// Auto-refresh nach 5 Sekunden
setTimeout(() => {
refreshUpdateStatus();
}, 5000);
} catch (err) {
updateResultDiv.textContent = `❌ Fehler beim Rollback: ${err.message}`;
} finally {
rollbackBtn.disabled = false;
}
});
async function refreshUpdateLogs() {
updateLogsDiv.textContent = 'Lade Logs...';
try {
const logs = await api('/update/logs');
if (!logs || logs.length === 0) {
updateLogsDiv.textContent = 'Keine Logs vorhanden.';
return;
}
// Reverse chronological (newest first)
const logEntries = logs.reverse().map(entry => {
const timestamp = entry.timestamp ? new Date(entry.timestamp).toLocaleString('de-DE') : 'unbekannt';
const status = entry.status || 'unknown';
const version = entry.version || '-';
const error = entry.error ? `\n Fehler: ${entry.error}` : '';
return `[${timestamp}] ${status} - Version: ${version}${error}`;
});
updateLogsDiv.textContent = logEntries.join('\n\n');
} catch (err) {
updateLogsDiv.textContent = `Fehler beim Laden der Logs: ${err.message}`;
}
}
refreshLogsBtn.addEventListener('click', refreshUpdateLogs);
// Initial load
refreshUpdateStatus();
</script>
</body>
</html>

View File

@ -92,16 +92,16 @@ Sicheres, remote steuerbares System zum Sperren/Entsperren lokaler Nutzerkonten.
- [x] TASK_000028: Verify and apply update
- [x] US_000028: Client meldet Update-Status
- [x] TASK_000029: Report update status
- [ ] US_000029: Update-Status im Web-UI anzeigen
- [ ] TASK_000030: UI update status view
- [ ] US_000030: Update-Check im Web-UI ausloesen
- [ ] TASK_000031: UI update check trigger
- [ ] US_000031: Update im Web-UI anstossen
- [ ] TASK_000032: UI update apply action
- [ ] US_000032: Update-Logs im Web-UI anzeigen
- [ ] TASK_000033: UI update logs view
- [ ] US_000033: Rollback im Web-UI anstossen
- [ ] TASK_000034: UI rollback action
- [x] US_000029: Update-Status im Web-UI anzeigen
- [x] TASK_000030: UI update status view
- [x] US_000030: Update-Check im Web-UI ausloesen
- [x] TASK_000031: UI update check trigger
- [x] US_000031: Update im Web-UI anstossen
- [x] TASK_000032: UI update apply action
- [x] US_000032: Update-Logs im Web-UI anzeigen
- [x] TASK_000033: UI update logs view
- [x] US_000033: Rollback im Web-UI anstossen
- [x] TASK_000034: UI rollback action
### EPIC_000009: Update Webservice (External Team)
- [ ] US_000026: Client bezieht Updates (Pull)

View File

@ -1,9 +1,9 @@
ID: US_000029 | Version: 0.1.0 | Status: Draft
ID: US_000029 | Version: 0.1.0 | Status: Done
By: Codex (GPT-5)
# US_000029: Update-Status im Web-UI anzeigen
Status: In Progress
Status: Done
Als Admin moechte ich die aktuelle Version und den Update-Status im Web-UI sehen, damit ich den Zustand schnell pruefen kann.

View File

@ -1,9 +1,9 @@
ID: US_000030 | Version: 0.1.0 | Status: Draft
ID: US_000030 | Version: 0.1.0 | Status: Done
By: Codex (GPT-5)
# US_000030: Update-Check im Web-UI ausloesen
Status: In Progress
Status: Done
Als Admin moechte ich manuell nach Updates suchen koennen, damit ich Updates sofort pruefen kann.

View File

@ -1,9 +1,9 @@
ID: US_000031 | Version: 0.1.0 | Status: Draft
ID: US_000031 | Version: 0.1.0 | Status: Done
By: Codex (GPT-5)
# US_000031: Update im Web-UI anstossen
Status: In Progress
Status: Done
Als Admin moechte ich ein Update im Web-UI anstossen, damit der Client die neue Version installiert.

View File

@ -1,9 +1,9 @@
ID: US_000032 | Version: 0.1.0 | Status: Draft
ID: US_000032 | Version: 0.1.0 | Status: Done
By: Codex (GPT-5)
# US_000032: Update-Logs im Web-UI anzeigen
Status: In Progress
Status: Done
Als Admin moechte ich Update-Logs im Web-UI einsehen, damit Fehler nachvollziehbar sind.

View File

@ -1,9 +1,9 @@
ID: US_000033 | Version: 0.1.0 | Status: Draft
ID: US_000033 | Version: 0.1.0 | Status: Done
By: Codex (GPT-5)
# US_000033: Rollback im Web-UI anstossen
Status: In Progress
Status: Done
Als Admin moechte ich einen Rollback im Web-UI anstossen, damit ich nach einem fehlerhaften Update schnell zur letzten Version zurueckkehre.

View File

@ -1,4 +1,4 @@
ID: TASK_000030 | Version: 0.1.0 | Status: In Progress
ID: TASK_000030 | Version: 0.1.0 | Status: Done
By: Codex (GPT-5)
# TASK_000030: UI update status view

View File

@ -1,4 +1,4 @@
ID: TASK_000031 | Version: 0.1.0 | Status: In Progress
ID: TASK_000031 | Version: 0.1.0 | Status: Done
By: Codex (GPT-5)
# TASK_000031: UI update check trigger

View File

@ -1,4 +1,4 @@
ID: TASK_000032 | Version: 0.1.0 | Status: In Progress
ID: TASK_000032 | Version: 0.1.0 | Status: Done
By: Codex (GPT-5)
# TASK_000032: UI update apply action

View File

@ -1,4 +1,4 @@
ID: TASK_000033 | Version: 0.1.0 | Status: In Progress
ID: TASK_000033 | Version: 0.1.0 | Status: Done
By: Codex (GPT-5)
# TASK_000033: UI update logs view

View File

@ -1,4 +1,4 @@
ID: TASK_000034 | Version: 0.1.0 | Status: In Progress
ID: TASK_000034 | Version: 0.1.0 | Status: Done
By: Codex (GPT-5)
# TASK_000034: UI rollback action