diff --git a/app/Livewire/Ui/System/UpdateCard.php b/app/Livewire/Ui/System/UpdateCard.php
index fd81183..6b4dc46 100644
--- a/app/Livewire/Ui/System/UpdateCard.php
+++ b/app/Livewire/Ui/System/UpdateCard.php
@@ -1,5 +1,6 @@
readLowLevel();
$this->current = $this->readCurrentVersion();
- $this->latest = $this->readCachedLatest();
+ $this->latest = Cache::get('mailwolt.update_available');
+ $this->refreshLowLevelState();
+ // initiale Message
if ($this->message === null) {
- if ($this->hasUpdate()) {
+ if ($this->getHasUpdateProperty()) {
$this->message = "Neue Version verfügbar: {$this->latest}";
$this->messagePositive = false;
} else {
- $cur = $this->current ?? '–';
+ $cur = $this->current ?: '–';
$this->message = "Du bist auf dem neuesten Stand ({$cur})";
$this->messagePositive = true;
}
}
- // if wrapper is already running when page loads, reflect that
+ // falls der Wrapper gerade läuft → visuell „running“
if ($this->running) {
$this->state = 'running';
}
@@ -49,9 +53,8 @@ class UpdateCard extends Component
return view('livewire.ui.system.update-card');
}
- // ---- UI actions --------------------------------------------------------
+ /* =================== Aktionen =================== */
- /** Button: „Erneut prüfen“ (no toast; shows inline progress) */
public function refreshState(): void
{
$this->state = 'running';
@@ -59,37 +62,26 @@ class UpdateCard extends Component
$this->messagePositive = null;
try {
- // your checker should update the cache key below
Artisan::call('mailwolt:check-updates');
} catch (\Throwable $e) {
- // swallow but still continue to show something meaningful
+ // weich fallen
}
- $this->current = $this->readCurrentVersion();
- $this->latest = $this->readCachedLatest();
- $this->readLowLevel();
-
- if ($this->hasUpdate()) {
- $this->message = "Neue Version verfügbar: {$this->latest}";
- $this->messagePositive = false;
- } else {
- $this->message = "Du bist auf dem neuesten Stand ($this->current ?? '–')";
- $this->messagePositive = true;
- }
-
- $this->state = 'idle';
+ $this->reloadVersionsAndStatus();
+ $this->finishUiIfNoUpdate();
}
- /** Button: „Jetzt aktualisieren“ (no toast; start wrapper + progress) */
public function runUpdate(): void
{
- // Hide the yellow badges immediately
+ // Badge „Update verfügbar“ sofort ausblenden
Cache::forget('mailwolt.update_available');
- // spawn wrapper in the background
+ // Startzeit merken (Failsafe)
+ Cache::put($this->cacheStartedAtKey, time(), now()->addHours(1));
+
+ // Wrapper asynchron starten
@shell_exec('nohup sudo -n /usr/local/sbin/mw-update >/dev/null 2>&1 &');
- // reflect “running” in UI
$this->latest = null;
$this->state = 'running';
$this->running = true;
@@ -98,64 +90,67 @@ class UpdateCard extends Component
}
/**
- * Called from the blade with `wire:poll.1200ms="tick"` – but **only**
- * when $state === 'running'. This finishes the UX once the wrapper ends.
+ * Wird vom Blade nur während running gepollt (wire:poll="tick").
+ * Bricht den Fortschritt ab, sobald der Wrapper „done“ meldet ODER
+ * der Failsafe greift. Lädt danach Versionen & Badge neu.
*/
public function tick(): void
{
- if ($this->state !== 'running') return;
+ $this->refreshLowLevelState();
- $this->readLowLevel();
-
- if (!$this->running) { // wrapper finished
- // give build.info a split second to land on slow disks
- usleep(150000);
-
- $this->current = $this->readCurrentVersion();
- $this->latest = $this->readCachedLatest();
-
- if ($this->rc === 0) {
- // success path
- if ($this->hasUpdate()) {
- // very unlikely right after success, but handle anyway
- $this->message = "Neue Version verfügbar: {$this->latest}";
- $this->messagePositive = false;
- } else {
- $this->message = "Update erfolgreich – jetzt: {$this->current}";
- $this->messagePositive = true;
- }
- } else {
- // failure path
- $rc = $this->rc ?? 1;
- $this->message = "Update fehlgeschlagen (rc={$rc})";
- $this->messagePositive = false;
- }
-
- $this->state = 'idle';
+ // Failsafe: nach N Minuten Fortschritt aus
+ $started = (int)Cache::get($this->cacheStartedAtKey, 0);
+ if ($this->running && $started > 0 && (time() - $started) > $this->failsafeSeconds) {
+ $this->running = false;
}
+
+ if (!$this->running) {
+ // abgeschlossen → Startmarke löschen
+ Cache::forget($this->cacheStartedAtKey);
+
+ // Versionen/Caches neu laden
+ $this->reloadVersionsAndStatus();
+
+ // wenn erfolgreich (rc=0) und keine neue Version mehr → Done
+ $this->finishUiIfNoUpdate();
+ }
+ // wenn weiterhin running: nichts tun, UI zeigt Progress weiter
}
- // ---- helpers -----------------------------------------------------------
+ /* =================== Computed =================== */
+ // Blade nutzt $this->hasUpdate
public function getHasUpdateProperty(): bool
{
- // gleiche Logik wie in deiner Klasse:
$cur = $this->normalizeVersion($this->current ?? null);
$lat = $this->normalizeVersion($this->latest ?? null);
if ($lat === null || $cur === null) return false;
return version_compare($lat, $cur, '>');
}
- protected function hasUpdate(): bool
- {
- $cur = $this->normalizeVersion($this->current);
- $lat = $this->normalizeVersion($this->latest);
+ /* =================== Helpers =================== */
- if ($lat === null || $cur === null) return false;
- return version_compare($lat, $cur, '>');
+ protected function reloadVersionsAndStatus(): void
+ {
+ $this->current = $this->readCurrentVersion();
+ $this->latest = Cache::get('mailwolt.update_available');
+ $this->refreshLowLevelState();
}
- protected function readLowLevel(): void
+ protected function finishUiIfNoUpdate(): void
+ {
+ if (!$this->getHasUpdateProperty()) {
+ // alles aktuell → Fortschritt aus, Badge „Aktuell“, Hinweistext grün
+ $this->state = 'idle';
+ $this->message = "Du bist auf dem neuesten Stand (" . $this->current ?? '–' . ")";
+ $this->messagePositive = true;
+
+ // zur Sicherheit Badge-Cache entfernen
+ Cache::forget('mailwolt.update_available');
+ }
+ }
+
+ protected function refreshLowLevelState(): void
{
$state = @trim(@file_get_contents('/var/lib/mailwolt/update/state') ?: '');
$this->running = ($state === 'running');
@@ -166,53 +161,241 @@ class UpdateCard extends Component
protected function readCurrentVersion(): ?string
{
- // Prefer the installer/updater stamp
$build = @file_get_contents('/etc/mailwolt/build.info');
if ($build) {
foreach (preg_split('/\R+/', $build) as $line) {
if (str_starts_with($line, 'version=')) {
$v = trim(substr($line, 8));
- if ($v !== '') return $this->prettyVersion($v);
+ if ($v !== '') return $v;
}
}
}
- $v = trim((string)config('app.version', ''));
- return $v !== '' ? $this->prettyVersion($v) : null;
+ $v = config('app.version');
+ return $v !== '' ? $v : null;
}
- protected function readCachedLatest(): ?string
- {
- $v = Cache::get('mailwolt.update_available');
- if (!is_string($v) || $v === '') return null;
- return $this->prettyVersion($v);
- }
-
- /**
- * Return a “compare-safe” semver string: e.g. `v1.0.18` → `1.0.18`.
- * Accepts tags like `release-1.0.18` too.
- */
protected function normalizeVersion(?string $v): ?string
{
- if (!$v) return null;
- // keep only the first x.y.z like portion
- if (preg_match('/(\d+\.\d+\.\d+)/', $v, $m)) {
- return $m[1];
- }
- // fallback: bare digits (x.y)
- if (preg_match('/(\d+\.\d+)/', $v, $m)) {
- return $m[1];
- }
- return null;
- }
-
- /** Always render with a single leading `v` (no “vv…”) */
- protected function prettyVersion(string $v): string
- {
- $n = $this->normalizeVersion($v);
- return $n ? 'v' . $n : $v;
+ if ($v === null) return null;
+ $v = trim($v);
+ $v = ltrim($v, 'v'); // führt "v1.0.19" und "1.0.19" zusammen
+ return $v === '' ? null : $v;
}
}
+//namespace App\Livewire\Ui\System;
+//
+//use Illuminate\Support\Facades\Artisan;
+//use Illuminate\Support\Facades\Cache;
+//use Livewire\Component;
+//
+//class UpdateCard extends Component
+//{
+// public ?string $current = null; // installed (pretty)
+// public ?string $latest = null; // available (pretty or null)
+// public string $state = 'idle'; // idle|running
+//
+// public ?string $message = null; // inline message
+// public ?bool $messagePositive = null;
+//
+// // low-level
+// public bool $running = false;
+// public ?int $rc = null;
+//
+// // ---- lifecycle ---------------------------------------------------------
+//
+// public function mount(): void
+// {
+// $this->readLowLevel();
+// $this->current = $this->readCurrentVersion();
+// $this->latest = $this->readCachedLatest();
+//
+// if ($this->message === null) {
+// if ($this->hasUpdate()) {
+// $this->message = "Neue Version verfügbar: {$this->latest}";
+// $this->messagePositive = false;
+// } else {
+// $cur = $this->current ?? '–';
+// $this->message = "Du bist auf dem neuesten Stand ({$cur})";
+// $this->messagePositive = true;
+// }
+// }
+//
+// // if wrapper is already running when page loads, reflect that
+// if ($this->running) {
+// $this->state = 'running';
+// }
+// }
+//
+// public function render()
+// {
+// return view('livewire.ui.system.update-card');
+// }
+//
+// // ---- UI actions --------------------------------------------------------
+//
+// /** Button: „Erneut prüfen“ (no toast; shows inline progress) */
+// public function refreshState(): void
+// {
+// $this->state = 'running';
+// $this->message = 'Prüfe auf Updates …';
+// $this->messagePositive = null;
+//
+// try {
+// // your checker should update the cache key below
+// Artisan::call('mailwolt:check-updates');
+// } catch (\Throwable $e) {
+// // swallow but still continue to show something meaningful
+// }
+//
+// $this->current = $this->readCurrentVersion();
+// $this->latest = $this->readCachedLatest();
+// $this->readLowLevel();
+//
+// if ($this->hasUpdate()) {
+// $this->message = "Neue Version verfügbar: {$this->latest}";
+// $this->messagePositive = false;
+// } else {
+// $this->message = "Du bist auf dem neuesten Stand ($this->current ?? '–')";
+// $this->messagePositive = true;
+// }
+//
+// $this->state = 'idle';
+// }
+//
+// /** Button: „Jetzt aktualisieren“ (no toast; start wrapper + progress) */
+// public function runUpdate(): void
+// {
+// // Hide the yellow badges immediately
+// Cache::forget('mailwolt.update_available');
+//
+// // spawn wrapper in the background
+// @shell_exec('nohup sudo -n /usr/local/sbin/mw-update >/dev/null 2>&1 &');
+//
+// // reflect “running” in UI
+// $this->latest = null;
+// $this->state = 'running';
+// $this->running = true;
+// $this->message = 'Update läuft …';
+// $this->messagePositive = null;
+// }
+//
+// /**
+// * Called from the blade with `wire:poll.1200ms="tick"` – but **only**
+// * when $state === 'running'. This finishes the UX once the wrapper ends.
+// */
+// public function tick(): void
+// {
+// if ($this->state !== 'running') return;
+//
+// $this->readLowLevel();
+//
+// if (!$this->running) { // wrapper finished
+// // give build.info a split second to land on slow disks
+// usleep(150000);
+//
+// $this->current = $this->readCurrentVersion();
+// $this->latest = $this->readCachedLatest();
+//
+// if ($this->rc === 0) {
+// // success path
+// if ($this->hasUpdate()) {
+// // very unlikely right after success, but handle anyway
+// $this->message = "Neue Version verfügbar: {$this->latest}";
+// $this->messagePositive = false;
+// } else {
+// $this->message = "Update erfolgreich – jetzt: {$this->current}";
+// $this->messagePositive = true;
+// }
+// } else {
+// // failure path
+// $rc = $this->rc ?? 1;
+// $this->message = "Update fehlgeschlagen (rc={$rc})";
+// $this->messagePositive = false;
+// }
+//
+// $this->state = 'idle';
+// }
+// }
+//
+// // ---- helpers -----------------------------------------------------------
+//
+// public function getHasUpdateProperty(): bool
+// {
+// // gleiche Logik wie in deiner Klasse:
+// $cur = $this->normalizeVersion($this->current ?? null);
+// $lat = $this->normalizeVersion($this->latest ?? null);
+// if ($lat === null || $cur === null) return false;
+// return version_compare($lat, $cur, '>');
+// }
+//
+// protected function hasUpdate(): bool
+// {
+// $cur = $this->normalizeVersion($this->current);
+// $lat = $this->normalizeVersion($this->latest);
+//
+// if ($lat === null || $cur === null) return false;
+// return version_compare($lat, $cur, '>');
+// }
+//
+// protected function readLowLevel(): void
+// {
+// $state = @trim(@file_get_contents('/var/lib/mailwolt/update/state') ?: '');
+// $this->running = ($state === 'running');
+//
+// $rcRaw = @trim(@file_get_contents('/var/lib/mailwolt/update/rc') ?: '');
+// $this->rc = is_numeric($rcRaw) ? (int)$rcRaw : null;
+// }
+//
+// protected function readCurrentVersion(): ?string
+// {
+// // Prefer the installer/updater stamp
+// $build = @file_get_contents('/etc/mailwolt/build.info');
+// if ($build) {
+// foreach (preg_split('/\R+/', $build) as $line) {
+// if (str_starts_with($line, 'version=')) {
+// $v = trim(substr($line, 8));
+// if ($v !== '') return $this->prettyVersion($v);
+// }
+// }
+// }
+// $v = trim((string)config('app.version', ''));
+// return $v !== '' ? $this->prettyVersion($v) : null;
+// }
+//
+// protected function readCachedLatest(): ?string
+// {
+// $v = Cache::get('mailwolt.update_available');
+// if (!is_string($v) || $v === '') return null;
+// return $this->prettyVersion($v);
+// }
+//
+// /**
+// * Return a “compare-safe” semver string: e.g. `v1.0.18` → `1.0.18`.
+// * Accepts tags like `release-1.0.18` too.
+// */
+// protected function normalizeVersion(?string $v): ?string
+// {
+// if (!$v) return null;
+// // keep only the first x.y.z like portion
+// if (preg_match('/(\d+\.\d+\.\d+)/', $v, $m)) {
+// return $m[1];
+// }
+// // fallback: bare digits (x.y)
+// if (preg_match('/(\d+\.\d+)/', $v, $m)) {
+// return $m[1];
+// }
+// return null;
+// }
+//
+// /** Always render with a single leading `v` (no “vv…”) */
+// protected function prettyVersion(string $v): string
+// {
+// $n = $this->normalizeVersion($v);
+// return $n ? 'v' . $n : $v;
+// }
+//}
+
//namespace App\Livewire\Ui\System;
//
//use Livewire\Component;
diff --git a/resources/views/livewire/ui/system/update-card.blade.php b/resources/views/livewire/ui/system/update-card.blade.php
index 0f5112d..28743c8 100644
--- a/resources/views/livewire/ui/system/update-card.blade.php
+++ b/resources/views/livewire/ui/system/update-card.blade.php
@@ -122,7 +122,7 @@
fill="url(#shieldGradient)" filter="url(#glow)"/>
-
+