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:
@ -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: 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) |
|
| 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-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
|
## Legende
|
||||||
|
|||||||
@ -84,6 +84,27 @@
|
|||||||
<div id="result" class="log"></div>
|
<div id="result" class="log"></div>
|
||||||
</section>
|
</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>
|
<script>
|
||||||
const statusDiv = document.getElementById('status');
|
const statusDiv = document.getElementById('status');
|
||||||
const resultDiv = document.getElementById('result');
|
const resultDiv = document.getElementById('result');
|
||||||
@ -221,6 +242,169 @@
|
|||||||
|
|
||||||
checkSession();
|
checkSession();
|
||||||
checkOidcStatus();
|
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>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -92,16 +92,16 @@ Sicheres, remote steuerbares System zum Sperren/Entsperren lokaler Nutzerkonten.
|
|||||||
- [x] TASK_000028: Verify and apply update
|
- [x] TASK_000028: Verify and apply update
|
||||||
- [x] US_000028: Client meldet Update-Status
|
- [x] US_000028: Client meldet Update-Status
|
||||||
- [x] TASK_000029: Report update status
|
- [x] TASK_000029: Report update status
|
||||||
- [ ] US_000029: Update-Status im Web-UI anzeigen
|
- [x] US_000029: Update-Status im Web-UI anzeigen
|
||||||
- [ ] TASK_000030: UI update status view
|
- [x] TASK_000030: UI update status view
|
||||||
- [ ] US_000030: Update-Check im Web-UI ausloesen
|
- [x] US_000030: Update-Check im Web-UI ausloesen
|
||||||
- [ ] TASK_000031: UI update check trigger
|
- [x] TASK_000031: UI update check trigger
|
||||||
- [ ] US_000031: Update im Web-UI anstossen
|
- [x] US_000031: Update im Web-UI anstossen
|
||||||
- [ ] TASK_000032: UI update apply action
|
- [x] TASK_000032: UI update apply action
|
||||||
- [ ] US_000032: Update-Logs im Web-UI anzeigen
|
- [x] US_000032: Update-Logs im Web-UI anzeigen
|
||||||
- [ ] TASK_000033: UI update logs view
|
- [x] TASK_000033: UI update logs view
|
||||||
- [ ] US_000033: Rollback im Web-UI anstossen
|
- [x] US_000033: Rollback im Web-UI anstossen
|
||||||
- [ ] TASK_000034: UI rollback action
|
- [x] TASK_000034: UI rollback action
|
||||||
|
|
||||||
### EPIC_000009: Update Webservice (External Team)
|
### EPIC_000009: Update Webservice (External Team)
|
||||||
- [ ] US_000026: Client bezieht Updates (Pull)
|
- [ ] US_000026: Client bezieht Updates (Pull)
|
||||||
|
|||||||
@ -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)
|
By: Codex (GPT-5)
|
||||||
|
|
||||||
# US_000029: Update-Status im Web-UI anzeigen
|
# 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.
|
Als Admin moechte ich die aktuelle Version und den Update-Status im Web-UI sehen, damit ich den Zustand schnell pruefen kann.
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
By: Codex (GPT-5)
|
||||||
|
|
||||||
# US_000030: Update-Check im Web-UI ausloesen
|
# 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.
|
Als Admin moechte ich manuell nach Updates suchen koennen, damit ich Updates sofort pruefen kann.
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
By: Codex (GPT-5)
|
||||||
|
|
||||||
# US_000031: Update im Web-UI anstossen
|
# 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.
|
Als Admin moechte ich ein Update im Web-UI anstossen, damit der Client die neue Version installiert.
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
By: Codex (GPT-5)
|
||||||
|
|
||||||
# US_000032: Update-Logs im Web-UI anzeigen
|
# 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.
|
Als Admin moechte ich Update-Logs im Web-UI einsehen, damit Fehler nachvollziehbar sind.
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
By: Codex (GPT-5)
|
||||||
|
|
||||||
# US_000033: Rollback im Web-UI anstossen
|
# 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.
|
Als Admin moechte ich einen Rollback im Web-UI anstossen, damit ich nach einem fehlerhaften Update schnell zur letzten Version zurueckkehre.
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
By: Codex (GPT-5)
|
||||||
|
|
||||||
# TASK_000030: UI update status view
|
# TASK_000030: UI update status view
|
||||||
|
|||||||
@ -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)
|
By: Codex (GPT-5)
|
||||||
|
|
||||||
# TASK_000031: UI update check trigger
|
# TASK_000031: UI update check trigger
|
||||||
|
|||||||
@ -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)
|
By: Codex (GPT-5)
|
||||||
|
|
||||||
# TASK_000032: UI update apply action
|
# TASK_000032: UI update apply action
|
||||||
|
|||||||
@ -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)
|
By: Codex (GPT-5)
|
||||||
|
|
||||||
# TASK_000033: UI update logs view
|
# TASK_000033: UI update logs view
|
||||||
|
|||||||
@ -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)
|
By: Codex (GPT-5)
|
||||||
|
|
||||||
# TASK_000034: UI rollback action
|
# TASK_000034: UI rollback action
|
||||||
|
|||||||
Reference in New Issue
Block a user