diff --git a/.env.example b/.env.example index c89bd30..6ddfe82 100644 --- a/.env.example +++ b/.env.example @@ -85,6 +85,8 @@ ACME_EMAIL= # .git (the prod image ships none) — leave empty; in dev the page reads .git directly. CLUSEV_BUILD_SHA= CLUSEV_BUILD_BRANCH= +# Display timezone for dashboard times (DB stays UTC). install.sh sets this from the host; UTC default. +APP_TIMEZONE=UTC # External reverse-proxy TLS: set to the upstream proxy's address/CIDR so Caddy trusts its # X-Forwarded-Proto. Leave as 127.0.0.1/32 (trust nothing) when Caddy terminates TLS itself. TRUSTED_PROXY_CIDR=127.0.0.1/32 diff --git a/CHANGELOG.md b/CHANGELOG.md index 697e026..cd5c41d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,29 @@ getaggte Releases (Kanal `stable`, optional `beta`) — niemals Entwicklungs-Bui _Keine offenen Änderungen — der nächste Stand wird hier gesammelt und als `vX.Y.Z` getaggt._ +## [0.9.12] - 2026-06-19 + +### Hinzugefügt +- **Fertig-Rückmeldung beim Dashboard-Update.** Nach „Jetzt aktualisieren" zeigte die Seite nur + „Update läuft" ohne Ende. Sie pollt jetzt (alle 5 s) und erkennt den Abschluss daran, dass der + neu gebaute Container eine höhere Version meldet als beim Start des Updates — dann springt die + Karte automatisch zurück auf „Aktuell — vX" und ein Erfolgs-Toast erscheint. Übersteht die + Neustart-Downtime (fehlgeschlagene Polls werden einfach wiederholt) und bricht nach ~5 Min mit + einem Hinweis ab, falls das Update hängt — statt ewig zu drehen. Ein laufendes Update wird auch + nach einem Seiten-Reload wieder aufgenommen (solange die Sentinel-Datei besteht). + +### Geändert +- **Verfügbares Update wird sofort beim Laden angezeigt.** Stand der neueste Tag bereits aus einer + vorherigen Prüfung im Cache, zeigte nur das Badge die neue Version — der „Jetzt aktualisieren"- + Knopf erschien aber erst nach erneutem „Nach Updates suchen". Jetzt erscheint er direkt beim + Seitenaufbau (ohne Netzwerkanfrage — rein aus dem Cache). + +### Behoben +- **Zeitstempel in lokaler Zeit statt UTC.** `app.timezone` ist jetzt env-gesteuert; `install.sh` + ermittelt die Zeitzone des Hosts (systemd / `/etc/timezone` / `/etc/localtime`) und schreibt sie + als `APP_TIMEZONE` in die `.env`. Datenbank-Zeitstempel bleiben UTC; nur die Anzeige (z. B. die + „geprüft"-Zeit) folgt der Host-Zeitzone. Default UTC, wenn nicht ermittelbar. + ## [0.9.11] - 2026-06-19 ### Behoben diff --git a/app/Livewire/Versions/Index.php b/app/Livewire/Versions/Index.php index 285162a..5ef9223 100644 --- a/app/Livewire/Versions/Index.php +++ b/app/Livewire/Versions/Index.php @@ -37,6 +37,63 @@ class Index extends Component /** Set once an update has been requested this session — the stack is rebuilding. */ public bool $updateRequested = false; + /** Running version captured WHEN the update was requested — completion = version moved past it. */ + public ?string $updateBaseVersion = null; + + /** Poll ticks since the update request, to time out a stuck/failed update instead of spinning. */ + public int $updatePolls = 0; + + /** + * On load: resume the "running" state if an update sentinel is still pending (survives a + * reload mid-update), and — without any network call — surface an available update from the + * cached check so the button shows immediately (the badge already knew). No re-click needed. + */ + public function mount(DeploymentService $deployment): void + { + if ($deployment->updateRequested()) { + $this->updateRequested = true; + $this->updateBaseVersion = (string) config('clusev.version'); + } + + $latest = $this->resolveLatestTag($this->channel()); // cached / local .git — never network on load + if (! $this->updateRequested && $latest !== null && $this->isNewer($latest, (string) config('clusev.version'))) { + $this->updateState = 'update'; + $this->updateStatus = __('versions.status_update_available', ['latest' => $latest, 'channel' => $this->channel()]); + } + } + + /** + * Poll while an update runs (driven by wire:poll on the "running" notice). Completion is + * detected purely from the running version moving PAST the value captured at request time — + * the rebuilt container reports the new version, so no host round-trip is needed. Times out + * after ~5 min so a failed update surfaces a hint instead of spinning forever. + */ + public function pollUpdate(): void + { + if (! $this->updateRequested) { + return; + } + $this->updatePolls++; + $current = (string) config('clusev.version'); + $this->updateBaseVersion ??= $current; + + if (version_compare(ltrim($current, 'vV'), ltrim($this->updateBaseVersion, 'vV'), '>')) { + $this->updateRequested = false; + $this->updatePolls = 0; + $this->updateState = 'current'; + $this->updateStatus = __('versions.status_current', ['installed' => $current, 'channel' => $this->channel()]); + $this->dispatch('notify', message: __('versions.update_done', ['version' => $current])); + + return; + } + + if ($this->updatePolls >= 60) { // ~5 min at 5s — almost certainly failed; stop spinning. + $this->updateRequested = false; + $this->updatePolls = 0; + $this->dispatch('notify', message: __('versions.update_stalled'), level: 'error'); + } + } + /** * Honest update check: compare the installed version against the newest release * tag visible in the channel. No external server, no stars, no CVE feed. @@ -107,6 +164,8 @@ class Index extends Component ]); $this->updateRequested = true; + $this->updateBaseVersion = $installed; // completion = the running version moves past this + $this->updatePolls = 0; $this->dispatch('notify', message: __('versions.update_started')); } diff --git a/config/app.php b/config/app.php index aad3922..7eecc33 100644 --- a/config/app.php +++ b/config/app.php @@ -65,7 +65,9 @@ return [ | */ - 'timezone' => 'UTC', + // install.sh sets APP_TIMEZONE from the host's timezone so dashboard times (e.g. the update + // "checked at" stamp) show local time, not UTC. DB timestamps stay UTC. Defaults to UTC. + 'timezone' => env('APP_TIMEZONE', 'UTC'), /* |-------------------------------------------------------------------------- diff --git a/config/clusev.php b/config/clusev.php index 61f5ebb..e8f80b9 100644 --- a/config/clusev.php +++ b/config/clusev.php @@ -2,7 +2,7 @@ return [ // First tagged release is v0.1.0 (semantic, not -dev). - 'version' => '0.9.11', + 'version' => '0.9.12', // Deployed commit + branch. install.sh bakes these into .env from the host's .git (the prod // image ships no .git); the Versions page prefers them and falls back to a live .git read in diff --git a/install.sh b/install.sh index 553f0f6..e51bac1 100755 --- a/install.sh +++ b/install.sh @@ -214,7 +214,14 @@ force_kv HOST_GID "$(id -g clusev)" git config --global --add safe.directory "$(pwd)" 2>/dev/null || true force_kv CLUSEV_BUILD_SHA "$(git rev-parse --short=7 HEAD 2>/dev/null || true)" force_kv CLUSEV_BUILD_BRANCH "$(git rev-parse --abbrev-ref HEAD 2>/dev/null || true)" -info "Secrets ok; Proxy/URL aus APP_DOMAIN abgeleitet" +# Display times in the HOST's timezone (dashboard "geprüft"-Zeit etc.) instead of UTC. DB stays +# UTC. Detect via systemd, then /etc/timezone, then the /etc/localtime symlink; default UTC. +HOST_TZ="$(timedatectl show -p Timezone --value 2>/dev/null || true)" +[ -n "$HOST_TZ" ] || HOST_TZ="$(cat /etc/timezone 2>/dev/null || true)" +[ -n "$HOST_TZ" ] || HOST_TZ="$(readlink -f /etc/localtime 2>/dev/null | sed -n 's#.*/zoneinfo/##p')" +[ -n "$HOST_TZ" ] || HOST_TZ="UTC" +force_kv APP_TIMEZONE "$HOST_TZ" +info "Secrets ok; Proxy/URL aus APP_DOMAIN abgeleitet; Zeitzone ${HOST_TZ}" # ── [4/9] image ────────────────────────────────────────────────────── phase 4/9 "Image bauen" diff --git a/lang/de/versions.php b/lang/de/versions.php index 078f8e0..424fb1b 100644 --- a/lang/de/versions.php +++ b/lang/de/versions.php @@ -52,6 +52,8 @@ return [ 'update_running' => 'Update läuft — Stack wird neu gebaut. Bitte gleich neu laden.', 'update_not_available' => 'Kein Update verfügbar — die installierte Version ist bereits aktuell.', 'update_throttled' => 'Zu viele Update-Anfragen — bitte in :seconds Sekunden erneut versuchen.', + 'update_done' => 'Update abgeschlossen — jetzt auf v:version.', + 'update_stalled' => 'Update dauert ungewöhnlich lange — Logs prüfen (journalctl -u clusev-update.service) oder Seite neu laden.', // Project panel 'project_title' => 'Projekt', diff --git a/lang/en/versions.php b/lang/en/versions.php index b274eed..79e2208 100644 --- a/lang/en/versions.php +++ b/lang/en/versions.php @@ -52,6 +52,8 @@ return [ 'update_running' => 'Update in progress — the stack is rebuilding. Reload shortly.', 'update_not_available' => 'No update available — the installed version is already current.', 'update_throttled' => 'Too many update requests — try again in :seconds seconds.', + 'update_done' => 'Update complete — now on v:version.', + 'update_stalled' => 'Update is taking unusually long — check the logs (journalctl -u clusev-update.service) or reload.', // Project panel 'project_title' => 'Project', diff --git a/resources/views/livewire/versions/index.blade.php b/resources/views/livewire/versions/index.blade.php index 2e25920..fe065f4 100644 --- a/resources/views/livewire/versions/index.blade.php +++ b/resources/views/livewire/versions/index.blade.php @@ -69,7 +69,9 @@ the System restart button (direct click → "running" notice via a flag — not data-destructive, so no modal). Only offered once a check found a newer release. --}} @if ($updateRequested) -
+ {{-- Poll until the rebuilt stack reports a newer version (pollUpdate flips the state + + fires a success toast). Survives the rebuild downtime: failed polls just retry. --}} +