feat: show update service status in UI

This commit is contained in:
2026-01-15 11:06:54 +01:00
parent 55e452b6a2
commit f513d46d08
22 changed files with 351 additions and 2 deletions

View File

@ -65,6 +65,12 @@ By: Codex (GPT-5)
| 15.01.2026 | 📝 Req | ID: Makefile als bevorzugter Einstieg in Doku ergaenzt. By: Codex (GPT-5) | | 15.01.2026 | 📝 Req | ID: Makefile als bevorzugter Einstieg in Doku ergaenzt. By: Codex (GPT-5) |
| 15.01.2026 | 🏗️ Planning | ID: US_000043/TASK_000049 Enrollment-Token Script geplant. By: Codex (GPT-5) | | 15.01.2026 | 🏗️ Planning | ID: US_000043/TASK_000049 Enrollment-Token Script geplant. By: Codex (GPT-5) |
| 15.01.2026 | ⚙️ Code | ID: US_000043 Enrollment-Script fuer Update-Service hinzugefuegt. By: Codex (GPT-5) | | 15.01.2026 | ⚙️ Code | ID: US_000043 Enrollment-Script fuer Update-Service hinzugefuegt. By: Codex (GPT-5) |
| 15.01.2026 | 🏗️ Planning | ID: EPIC_000013/US_000044/TASK_000050-TASK_000051 System Telemetry im Dashboard geplant. By: Codex (GPT-5) |
| 15.01.2026 | 🏗️ Planning | ID: EPIC_000014/US_000045/TASK_000052-TASK_000054 Login-Zeitfenster und Parent-Control Regeln geplant. By: Codex (GPT-5) |
| 15.01.2026 | 🏗️ Planning | ID: US_000045 Kriterien konkretisiert (lokale Zeit, OS-Login, Auto-Reaktivierung). By: Codex (GPT-5) |
| 15.01.2026 | 🏗️ Planning | ID: US_000046/TASK_000055 Update-Service Erreichbarkeit anzeigen. By: Codex (GPT-5) |
| 15.01.2026 | ⚙️ Code | ID: US_000046 Update-Service Statusanzeige implementiert. By: Codex (GPT-5) |
| 15.01.2026 | ⚙️ Code | ID: Makefile restart-Target hinzugefuegt. By: Codex (GPT-5) |
--- ---
## Legende ## Legende

View File

@ -13,7 +13,7 @@ HEALTH_URL ?= http://$(HOST):$(PORT)/health
TOKEN ?= $(shell awk -F= '/^SKD_AUTH_TOKEN=/{print $$2}' $(ENV_FILE) 2>/dev/null) TOKEN ?= $(shell awk -F= '/^SKD_AUTH_TOKEN=/{print $$2}' $(ENV_FILE) 2>/dev/null)
KEEP_INSTALL_DIR ?= 1 KEEP_INSTALL_DIR ?= 1
.PHONY: install up down uninstall healthcheck update token .PHONY: install up down restart uninstall healthcheck update token
install: install:
$(SUDO) env SERVICE_NAME=$(SERVICE) SERVICE_USER=$(SERVICE_USER) SERVICE_GROUP=$(SERVICE_GROUP) INSTALL_DIR=$(INSTALL_DIR) ./scripts/install.sh $(SUDO) env SERVICE_NAME=$(SERVICE) SERVICE_USER=$(SERVICE_USER) SERVICE_GROUP=$(SERVICE_GROUP) INSTALL_DIR=$(INSTALL_DIR) ./scripts/install.sh
@ -24,6 +24,9 @@ up:
down: down:
$(SUDO) systemctl stop $(SERVICE).service $(SUDO) systemctl stop $(SERVICE).service
restart:
$(SUDO) systemctl restart $(SERVICE).service
uninstall: uninstall:
-$(SUDO) systemctl stop $(SERVICE).service -$(SUDO) systemctl stop $(SERVICE).service
-$(SUDO) systemctl disable $(SERVICE).service -$(SUDO) systemctl disable $(SERVICE).service

View File

@ -26,6 +26,7 @@ from backend.models import (
UpdateActionResponse, UpdateActionResponse,
UpdateCheckResponse, UpdateCheckResponse,
UpdateLogEntry, UpdateLogEntry,
UpdateServiceStatus,
UpdateStatus, UpdateStatus,
UserStatus, UserStatus,
) )
@ -314,6 +315,12 @@ def update_logs(settings: Settings = Depends(get_settings), limit: int = 200) ->
) from exc ) from exc
@app.get("/update/service-status", response_model=UpdateServiceStatus, dependencies=[Depends(get_current_admin)])
def update_service_status(settings: Settings = Depends(get_settings)) -> UpdateServiceStatus:
data = update.get_service_status(settings)
return UpdateServiceStatus(**data)
@app.get("/", response_class=HTMLResponse) @app.get("/", response_class=HTMLResponse)
@app.get("/login", response_class=HTMLResponse) @app.get("/login", response_class=HTMLResponse)
@app.get("/dashboard", response_class=HTMLResponse) @app.get("/dashboard", response_class=HTMLResponse)

View File

@ -73,3 +73,11 @@ class UpdateLogEntry(BaseModel):
version: Optional[str] = None version: Optional[str] = None
error: Optional[str] = None error: Optional[str] = None
device_id: Optional[str] = None device_id: Optional[str] = None
class UpdateServiceStatus(BaseModel):
url: str
reachable: bool
status_code: Optional[int] = None
error: Optional[str] = None
checked_url: str

View File

@ -414,7 +414,10 @@
statusDiv.innerHTML = '<div class="spinner" style="margin: 0 auto;"></div><p class="text-center text-muted mt-1">Lade Update-Status...</p>'; statusDiv.innerHTML = '<div class="spinner" style="margin: 0 auto;"></div><p class="text-center text-muted mt-1">Lade Update-Status...</p>';
try { try {
const data = await api('/update/status'); const [data, serviceStatus] = await Promise.all([
api('/update/status'),
api('/update/service-status')
]);
document.querySelector('#metricVersion .value').textContent = data.current_version; document.querySelector('#metricVersion .value').textContent = data.current_version;
document.getElementById('headerVersion').textContent = `v${data.current_version}`; document.getElementById('headerVersion').textContent = `v${data.current_version}`;
@ -436,12 +439,24 @@
enrollSection.classList.remove('hidden'); enrollSection.classList.remove('hidden');
} }
const serviceBadge = serviceStatus.reachable
? '<span class="badge success"><i data-lucide="server"></i> Online</span>'
: '<span class="badge error"><i data-lucide="server-off"></i> Offline</span>';
const serviceHint = serviceStatus.status_code
? `HTTP ${serviceStatus.status_code}`
: (serviceStatus.error || 'keine Antwort');
statusDiv.innerHTML = ` statusDiv.innerHTML = `
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem;"> <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem;">
<div> <div>
<div class="text-muted" style="font-size: 0.75rem; text-transform: uppercase; margin-bottom: 0.25rem;">Version</div> <div class="text-muted" style="font-size: 0.75rem; text-transform: uppercase; margin-bottom: 0.25rem;">Version</div>
<div style="color: var(--color-accent); font-weight: 600;">${data.current_version}</div> <div style="color: var(--color-accent); font-weight: 600;">${data.current_version}</div>
</div> </div>
<div>
<div class="text-muted" style="font-size: 0.75rem; text-transform: uppercase; margin-bottom: 0.25rem;">Update-Service</div>
<div>${serviceBadge}</div>
<div class="text-muted" style="font-size: 0.75rem;">${serviceStatus.url} (${serviceHint})</div>
</div>
<div> <div>
<div class="text-muted" style="font-size: 0.75rem; text-transform: uppercase; margin-bottom: 0.25rem;">Registrierung</div> <div class="text-muted" style="font-size: 0.75rem; text-transform: uppercase; margin-bottom: 0.25rem;">Registrierung</div>
<div>${enrolledBadge}</div> <div>${enrolledBadge}</div>

View File

@ -220,3 +220,35 @@ def get_logs(settings: Settings, limit: int = 200) -> List[Dict[str, Any]]:
except json.JSONDecodeError: except json.JSONDecodeError:
continue continue
return entries return entries
def get_service_status(settings: Settings) -> Dict[str, Any]:
base_url = settings.update_service_url.rstrip("/")
if not base_url:
return {
"url": "",
"reachable": False,
"status_code": None,
"error": "update service url not configured",
"checked_url": "",
}
check_url = base_url
try:
with httpx.Client(timeout=3.0) as client:
response = client.get(check_url)
return {
"url": base_url,
"reachable": True,
"status_code": response.status_code,
"error": None,
"checked_url": check_url,
}
except Exception as exc:
return {
"url": base_url,
"reachable": False,
"status_code": None,
"error": str(exc),
"checked_url": check_url,
}

View File

@ -27,6 +27,7 @@ sudo systemctl status skd.service
make install make install
make up make up
make down make down
make restart
make update make update
make uninstall make uninstall
``` ```

View File

@ -47,6 +47,7 @@ Hinweis: `jq` ist optional; ohne jq das Token manuell aus der JSON-Antwort lesen
make install # Installation (systemd, env, venv) make install # Installation (systemd, env, venv)
make up # Service starten make up # Service starten
make down # Service stoppen make down # Service stoppen
make restart # Service neu starten
make update # Code-Update (git reset --hard origin/main) make update # Code-Update (git reset --hard origin/main)
make healthcheck TOKEN=... # Healthcheck mit Bearer-Token make healthcheck TOKEN=... # Healthcheck mit Bearer-Token
``` ```

View File

@ -54,6 +54,7 @@ curl -X POST -H "Authorization: Bearer $token" \
### Update-API (lokal) ### Update-API (lokal)
Alle Update-Endpunkte erfordern Admin-Auth. Alle Update-Endpunkte erfordern Admin-Auth.
- `GET /update/status` - `GET /update/status`
- `GET /update/service-status` (zeigt URL und Erreichbarkeit des Update-Services)
- `POST /update/enroll` (optional Body: `{ "enroll_token": "..." }`) - `POST /update/enroll` (optional Body: `{ "enroll_token": "..." }`)
- `POST /update/check` - `POST /update/check`
- `POST /update/apply` (optional Body: `{ "version": "x.y.z" }`) - `POST /update/apply` (optional Body: `{ "version": "x.y.z" }`)

View File

@ -22,6 +22,7 @@ Sicheres, remote steuerbares System zum Sperren/Entsperren lokaler Nutzerkonten.
| 15.01.2026 | 📝 Req | Doku-Overhaul mit neuer Struktur und Archivierung. | | 15.01.2026 | 📝 Req | Doku-Overhaul mit neuer Struktur und Archivierung. |
| 15.01.2026 | 📝 Req | Externe Service-Links in der Doku ergaenzt. | | 15.01.2026 | 📝 Req | Externe Service-Links in der Doku ergaenzt. |
| 15.01.2026 | 📝 Req | Makefile als Einstieg in Doku aufgenommen. | | 15.01.2026 | 📝 Req | Makefile als Einstieg in Doku aufgenommen. |
| 15.01.2026 | ⚙️ Code | Makefile restart-Target hinzugefuegt. |
## Epic-Backlog (Uebersicht) ## Epic-Backlog (Uebersicht)
### EPIC_000001: Legacy CLI Account Control (sk.sh) ### EPIC_000001: Legacy CLI Account Control (sk.sh)
@ -108,6 +109,8 @@ Sicheres, remote steuerbares System zum Sperren/Entsperren lokaler Nutzerkonten.
- [x] TASK_000034: UI rollback action - [x] TASK_000034: UI rollback action
- [x] US_000043: Enrollment-Token per Script abrufen - [x] US_000043: Enrollment-Token per Script abrufen
- [x] TASK_000049: Script fuer Enrollment-Flow erstellen - [x] TASK_000049: Script fuer Enrollment-Flow erstellen
- [x] US_000046: Update-Service Erreichbarkeit anzeigen
- [x] TASK_000055: Endpoint und UI fuer Update-Service Status
### EPIC_000009: Update Webservice (External Team) ### EPIC_000009: Update Webservice (External Team)
- [ ] US_000026: Client bezieht Updates (Pull) - [ ] US_000026: Client bezieht Updates (Pull)
@ -138,6 +141,17 @@ Sicheres, remote steuerbares System zum Sperren/Entsperren lokaler Nutzerkonten.
- [x] TASK_000047: README und Audience-Split anpassen - [x] TASK_000047: README und Audience-Split anpassen
- [x] TASK_000048: External Dependencies und Audience-Ergaenzungen - [x] TASK_000048: External Dependencies und Audience-Ergaenzungen
### EPIC_000013: System Telemetry im Dashboard
- [ ] US_000044: Systemmetriken im Dashboard anzeigen
- [ ] TASK_000050: Backend-Endpoint fuer Systemmetriken
- [ ] TASK_000051: UI-Kacheln im Dashboard (System Information)
### EPIC_000014: Login-Zeitfenster und Parent-Control Regeln
- [ ] US_000045: Regeln fuer Login-Zeitfenster definieren und durchsetzen
- [ ] TASK_000052: Regelmodell und Speicherung definieren
- [ ] TASK_000053: Login-Pruefung und Enforcement
- [ ] TASK_000054: Admin-UI fuer Regeln und Scheduler
## Offene Risiken / Abhaengigkeiten ## Offene Risiken / Abhaengigkeiten
- Betrieb erfordert Root/sudo und lokale System-Tools (notify-send, sound player, uvicorn). - Betrieb erfordert Root/sudo und lokale System-Tools (notify-send, sound player, uvicorn).
- OIDC-Validierung blockiert bis IdP bereit und Service laeuft. - OIDC-Validierung blockiert bis IdP bereit und Service laeuft.

View File

@ -45,3 +45,4 @@ Ermoegliche einen robusten Client-Update-Flow mit Verifikation und Rollback.
- US_000032: Update-Logs im Web-UI anzeigen - US_000032: Update-Logs im Web-UI anzeigen
- US_000033: Rollback im Web-UI anstossen - US_000033: Rollback im Web-UI anstossen
- US_000043: Enrollment-Token per Script abrufen - US_000043: Enrollment-Token per Script abrufen
- US_000046: Update-Service Erreichbarkeit anzeigen

View File

@ -0,0 +1,38 @@
ID: EPIC_000013 | Version: 0.2.1 | Status: Draft
By: Codex (GPT-5)
# EPIC_000013: System Telemetry im Dashboard
## Beschreibung
Anzeige von Systemmetriken (CPU, RAM, GPU, Netzwerk) fuer Operatoren im Web-Dashboard.
## Ziel / Business Value
Schneller Ueberblick ueber Systemzustand und Last ohne externe Tools.
## Mission Statement
Stelle aktuelle Telemetriedaten im Dashboard bereit, aktualisiert in kurzen Intervallen.
## Business Value & Metriken
- Operatoren erkennen Engpaesse sofort.
- Erfolgsmetrik: Metriken aktualisieren sich alle 5 Sekunden im UI.
## In-Scope
- API liefert aktuelle CPU/RAM/GPU/Netzwerkwerte.
- Web-UI zeigt Kacheln in einem "System Information" Container.
## Out-of-Scope
- Langzeit-Historie oder Trend-Analysen.
- Alerting oder Threshold-Management.
## High-Level Akzeptanzkriterien
- CPU als Gesamt-% angezeigt.
- RAM: Total und Usage %.
- GPU: VRAM Total und Usage % (falls GPU vorhanden).
- Netzwerk: aktuelle RX/TX in Mbps.
## Technische Constraints & Risiken
- GPU-Metriken sind hardware-/driver-abhaengig.
- Netzwerkwerte benoetigen Delta-Berechnung ueber Zeitfenster.
## Zugeordnete User Stories
- US_000044: Systemmetriken im Dashboard anzeigen

View File

@ -0,0 +1,42 @@
ID: EPIC_000014 | Version: 0.2.1 | Status: Draft
By: Codex (GPT-5)
# EPIC_000014: Login-Zeitfenster und Parent-Control Regeln
## Beschreibung
Admins definieren Zeitfenster pro Nutzer, in denen Login erlaubt ist. Beim Login wird
geprueft, ob der Zeitpunkt erlaubt ist; andernfalls erfolgt Hinweis, Konto wird deaktiviert
und der Rechner heruntergefahren. Beim Systemstart wird geprueft, welche Nutzer sich anmelden duerfen.
## Ziel / Business Value
Verlaessliche Einhaltung von Nutzungszeiten ohne manuelle Eingriffe.
## Mission Statement
Stelle regelbasierte Login-Beschraenkungen bereit, die zentral verwaltbar und systemweit
durchsetzbar sind.
## Business Value & Metriken
- Reduzierte manuelle Sperren.
- Erfolgsmetrik: Verbotene Logins werden zu 100% blockiert.
## In-Scope
- Regeln pro Nutzer (erlaubte Zeitfenster).
- Automatische Aktivierung/Deaktivierung nach Regeln.
- Durchsetzung bei Login und beim Systemstart.
## Out-of-Scope
- Historische Reports oder Nutzungsstatistiken.
- MFA oder externe Auth-Systeme.
## High-Level Akzeptanzkriterien
- Ohne Regel ist Login erlaubt.
- Mit Regel wird Login nur in erlaubten Zeitfenstern zugelassen.
- Bei Verbot: Hinweis, Konto wird deaktiviert, Shutdown wird gestartet.
- Optional: Konto wird nach Zeitfenster automatisch wieder aktiviert.
## Technische Constraints & Risiken
- Zeit- und Zeitzonenhandling.
- Durchsetzung erfordert Hook in Auth/Login-Prozess.
## Zugeordnete User Stories
- US_000045: Regeln fuer Login-Zeitfenster definieren und durchsetzen

View File

@ -0,0 +1,20 @@
ID: US_000044 | Version: 0.2.1 | Status: Draft
By: Codex (GPT-5)
# US_000044: Systemmetriken im Dashboard anzeigen
Als User moechte ich die aktuelle CPU-, GPU-, RAM- und Netzwerkauslastung sehen,
damit ich den Systemzustand schnell bewerten kann.
## Akzeptanzkriterien
- Given ich bin im Dashboard eingeloggt
- When die System Information Kacheln angezeigt werden
- Then sehe ich CPU Gesamt-Auslastung in Prozent
- And RAM Total sowie Usage in Prozent
- And GPU VRAM Total sowie Usage in Prozent (falls GPU vorhanden)
- And Netzwerk RX/TX als aktuelle Mbps
- And die Werte aktualisieren sich alle 5 Sekunden
## Task-Platzhalter
- TASK_000050: Backend-Endpoint fuer Systemmetriken
- TASK_000051: UI-Kacheln im Dashboard (System Information)

View File

@ -0,0 +1,33 @@
ID: US_000045 | Version: 0.2.1 | Status: Draft
By: Codex (GPT-5)
# US_000045: Regeln fuer Login-Zeitfenster definieren und durchsetzen
Als Administrator moechte ich Regeln definieren, in welchen Zeitfenstern sich ein Nutzer
anmelden darf, damit Parent-Control verbindlich umgesetzt wird.
## Akzeptanzkriterien
- Given ein Nutzer hat keine Regel
- When er sich anmeldet
- Then ist Login erlaubt
- Given ein Nutzer hat eine Regel mit erlaubten Zeitfenstern
- When er sich ausserhalb dieser Zeiten anmeldet
- Then erhaelt er einen Hinweis
- And das Konto wird deaktiviert
- And der Rechner wird heruntergefahren
- Given das Geraet startet
- When Regeln existieren
- Then wird geprueft, welche Nutzer anmelden duerfen
- And gesperrte Nutzer bleiben deaktiviert
- And Nutzer ohne Auto-Reaktivierung bleiben gesperrt, auch wenn ein Zeitfenster startet
- Given eine Regel erlaubt automatische Reaktivierung
- When ein Zeitfenster beginnt
- Then wird das Konto automatisch wieder aktiviert
## Task-Platzhalter
- TASK_000052: Regelmodell und Speicherung definieren
- TASK_000053: Login-Pruefung und Enforcement
- TASK_000054: Admin-UI fuer Regeln und Scheduler

View File

@ -0,0 +1,17 @@
ID: US_000046 | Version: 0.2.1 | Status: Done
By: Codex (GPT-5)
# US_000046: Update-Service Erreichbarkeit anzeigen
Als Admin moechte ich sehen, ob der Update-Service erreichbar ist und welche URL aktiv ist,
damit ich erkenne, ob das Geraet gegen Dev oder Prod konfiguriert ist.
## Akzeptanzkriterien
- Given ich bin im Update-Dashboard eingeloggt
- When der Statusbereich angezeigt wird
- Then sehe ich die konfigurierte Update-Service URL
- And einen Online/Offline-Status basierend auf Erreichbarkeit
- And die Anzeige aktualisiert sich bei Status-Refresh
## Task-Platzhalter
- TASK_000055: Endpoint und UI fuer Update-Service Status

View File

@ -0,0 +1,19 @@
ID: TASK_000050 | Version: 0.2.1 | Status: Draft
By: Codex (GPT-5)
# TASK_000050: Backend-Endpoint fuer Systemmetriken
## Outcome
API liefert aktuelle CPU/RAM/GPU/Netzwerkdaten in einem Payload.
## Story-Bezug
US_000044
## Beschreibung
- Endpoint (z.B. `GET /system/metrics`) mit Auth.
- CPU Gesamt-%, RAM total + usage %, GPU VRAM total + usage % (fallback wenn nicht vorhanden).
- Netzwerk RX/TX in Mbps via Delta ueber 5s Fenster.
## Definition of Done (DoD)
- Endpoint antwortet in <1s.
- Payload klar dokumentiert.

View File

@ -0,0 +1,18 @@
ID: TASK_000051 | Version: 0.2.1 | Status: Draft
By: Codex (GPT-5)
# TASK_000051: UI-Kacheln im Dashboard (System Information)
## Outcome
Dashboard zeigt System Information Container mit Kacheln fuer CPU/RAM/GPU/Netzwerk.
## Story-Bezug
US_000044
## Beschreibung
- UI-Container "System Information" mit Kacheln.
- Polling alle 5 Sekunden.
- Fallback-Text, wenn GPU nicht vorhanden.
## Definition of Done (DoD)
- UI aktualisiert Werte ohne Page-Reload.

View File

@ -0,0 +1,19 @@
ID: TASK_000052 | Version: 0.2.1 | Status: Draft
By: Codex (GPT-5)
# TASK_000052: Regelmodell und Speicherung definieren
## Outcome
Ein persistentes Regelmodell fuer Login-Zeitfenster und Auto-Reaktivierung.
## Story-Bezug
US_000045
## Beschreibung
- Regelstruktur (User, erlaubte Zeitfenster pro Tag, Wochentage, Auto-Reaktivierung).
- Speicherort (Datei/DB), Format und Validierung.
- Zeitzonen-Handling definieren (lokale Systemzeit).
## Definition of Done (DoD)
- Regelmodell dokumentiert.
- Persistenzstrategie festgelegt.

View File

@ -0,0 +1,18 @@
ID: TASK_000053 | Version: 0.2.1 | Status: Draft
By: Codex (GPT-5)
# TASK_000053: Login-Pruefung und Enforcement
## Outcome
Login wird anhand der Regeln geprueft und bei Verbot durchgesetzt (Hinweis, Sperre, Shutdown).
## Story-Bezug
US_000045
## Beschreibung
- Hook in Login-Flow (PAM am OS).
- Hinweistext definieren.
- Deaktivierung und Shutdown-Logik ausloesen.
## Definition of Done (DoD)
- Login-Blockade funktioniert bei Verbot.

View File

@ -0,0 +1,19 @@
ID: TASK_000054 | Version: 0.2.1 | Status: Draft
By: Codex (GPT-5)
# TASK_000054: Admin-UI fuer Regeln und Scheduler
## Outcome
Admins koennen Regeln verwalten; ein Scheduler aktiviert Nutzer bei erlaubten Zeiten.
## Story-Bezug
US_000045
## Beschreibung
- UI: Regeln anlegen/aktualisieren/loeschen.
- Scheduler: Pruefung beim Boot (Systemstart) und regelmaessig.
- Auto-Reaktivierung umsetzen (ausser Regel sagt nein).
## Definition of Done (DoD)
- UI verwaltet Regeln.
- Scheduler aktiviert Nutzer entsprechend Regeln.

View File

@ -0,0 +1,17 @@
ID: TASK_000055 | Version: 0.2.1 | Status: Done
By: Codex (GPT-5)
# TASK_000055: Endpoint und UI fuer Update-Service Status
## Outcome
UI zeigt Update-Service URL und Erreichbarkeit; Backend liefert Status.
## Story-Bezug
US_000046
## Beschreibung
- `GET /update/service-status` liefert URL, Reachability, Status-Code.
- UI zeigt Status im Update-Bereich.
## Definition of Done (DoD)
- Statusanzeige funktioniert auch bei Fehlern (Offline).