Fix: Mailbox Stats über Dovecot mit config/mailpool.php

main v1.0.19
boban 2025-10-22 16:48:46 +02:00
parent 529979f078
commit 65b3259048
2 changed files with 415 additions and 115 deletions

View File

@ -1,38 +1,46 @@
<?php <?php
namespace App\Livewire\Ui\System; namespace App\Livewire\Ui\System;
use Livewire\Component;
use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Livewire\Component;
class UpdateCard extends Component class UpdateCard extends Component
{ {
public ?string $current = null; // installierte Version public ?string $current = null; // installed (pretty)
public ?string $latest = null; // verfügbare Version (oder null) public ?string $latest = null; // available (pretty or null)
public string $state = 'idle'; // idle|running public string $state = 'idle'; // idle|running
// UI-Textausgabe nach einer Prüfung / Aktion public ?string $message = null; // inline message
public ?string $message = null; // z.B. "Du bist auf dem neuesten Stand (v1.0.16)" public ?bool $messagePositive = null;
public ?bool $messagePositive = null; // true = grün, false = neutral/weiß
// low-level (falls du sie später brauchst) // low-level
public bool $running = false; // aus /var/lib/mailwolt/update/state public bool $running = false;
public ?int $rc = null; public ?int $rc = null;
// ---- lifecycle ---------------------------------------------------------
public function mount(): void public function mount(): void
{ {
$this->readLowLevel();
$this->current = $this->readCurrentVersion(); $this->current = $this->readCurrentVersion();
$this->latest = Cache::get('mailwolt.update_available'); $this->latest = $this->readCachedLatest();
$this->refreshLowLevelState();
// Starttext, falls nichts geprüft wurde
if ($this->message === null) { if ($this->message === null) {
$this->message = $this->latest && $this->hasUpdate() if ($this->hasUpdate()) {
? "Neue Version verfügbar: {$this->latest}" $this->message = "Neue Version verfügbar: {$this->latest}";
: ($this->current ? "Du bist auf dem neuesten Stand ({$this->current})" : "Status unbekannt"); $this->messagePositive = false;
$this->messagePositive = !$this->hasUpdate(); } 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';
} }
} }
@ -41,12 +49,9 @@ class UpdateCard extends Component
return view('livewire.ui.system.update-card'); return view('livewire.ui.system.update-card');
} }
/** // ---- UI actions --------------------------------------------------------
* „Erneut prüfen“ ohne Toast:
* - Progress anzeigen /** Button: „Erneut prüfen“ (no toast; shows inline progress) */
* - Check-Command laufen lassen
* - Message in der Box aktualisieren
*/
public function refreshState(): void public function refreshState(): void
{ {
$this->state = 'running'; $this->state = 'running';
@ -54,41 +59,37 @@ class UpdateCard extends Component
$this->messagePositive = null; $this->messagePositive = null;
try { try {
// Passe den Namen hier an dein tatsächliches Command an: // your checker should update the cache key below
Artisan::call('mailwolt:check-updates'); Artisan::call('mailwolt:check-updates');
} catch (\Throwable $e) { } catch (\Throwable $e) {
// weich fallen // swallow but still continue to show something meaningful
} }
// Daten neu einlesen
$this->current = $this->readCurrentVersion(); $this->current = $this->readCurrentVersion();
$this->latest = Cache::get('mailwolt.update_available'); $this->latest = $this->readCachedLatest();
$this->readLowLevel();
if ($this->hasUpdate()) { if ($this->hasUpdate()) {
$this->message = "Neue Version verfügbar: {$this->latest}"; $this->message = "Neue Version verfügbar: {$this->latest}";
$this->messagePositive = false; // neutral $this->messagePositive = false;
} else { } else {
$cur = $this->current ?: ''; $this->message = "Du bist auf dem neuesten Stand ($this->current ?? '')";
$this->message = "Du bist auf dem neuesten Stand ({$cur})"; $this->messagePositive = true;
$this->messagePositive = true; // grün
} }
$this->refreshLowLevelState();
$this->state = 'idle'; $this->state = 'idle';
} }
/** /** Button: „Jetzt aktualisieren“ (no toast; start wrapper + progress) */
* „Jetzt aktualisieren“ ohne Toast:
* - Hinweis sofort aus Cache entfernen (Badge weg)
* - Update-Wrapper starten
* - Running + Text in der Box anzeigen
*/
public function runUpdate(): void public function runUpdate(): void
{ {
// Hide the yellow badges immediately
Cache::forget('mailwolt.update_available'); Cache::forget('mailwolt.update_available');
// spawn wrapper in the background
@shell_exec('nohup sudo -n /usr/local/sbin/mw-update >/dev/null 2>&1 &'); @shell_exec('nohup sudo -n /usr/local/sbin/mw-update >/dev/null 2>&1 &');
// reflect “running” in UI
$this->latest = null; $this->latest = null;
$this->state = 'running'; $this->state = 'running';
$this->running = true; $this->running = true;
@ -96,16 +97,65 @@ class UpdateCard extends Component
$this->messagePositive = null; $this->messagePositive = null;
} }
/** --------------------- helpers --------------------- */ /**
* 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 protected function hasUpdate(): bool
{ {
if (!$this->latest) return false; $cur = $this->normalizeVersion($this->current);
$cur = $this->current ?: '0.0.0'; $lat = $this->normalizeVersion($this->latest);
return version_compare($this->latest, $cur, '>');
if ($lat === null || $cur === null) return false;
return version_compare($lat, $cur, '>');
} }
protected function refreshLowLevelState(): void protected function readLowLevel(): void
{ {
$state = @trim(@file_get_contents('/var/lib/mailwolt/update/state') ?: ''); $state = @trim(@file_get_contents('/var/lib/mailwolt/update/state') ?: '');
$this->running = ($state === 'running'); $this->running = ($state === 'running');
@ -116,21 +166,183 @@ class UpdateCard extends Component
protected function readCurrentVersion(): ?string protected function readCurrentVersion(): ?string
{ {
// bevorzugt /etc/mailwolt/build.info (wird im Installer/Updater gepflegt) // Prefer the installer/updater stamp
$build = @file_get_contents('/etc/mailwolt/build.info'); $build = @file_get_contents('/etc/mailwolt/build.info');
if ($build) { if ($build) {
foreach (preg_split('/\R+/', $build) as $line) { foreach (preg_split('/\R+/', $build) as $line) {
if (str_starts_with($line, 'version=')) { if (str_starts_with($line, 'version=')) {
$v = trim(substr($line, 8)); $v = trim(substr($line, 8));
if ($v !== '') return $v; if ($v !== '') return $this->prettyVersion($v);
} }
} }
} }
$v = config('app.version'); $v = trim((string)config('app.version', ''));
return $v !== '' ? $v : null; 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;
//use Illuminate\Support\Facades\Artisan;
//use Illuminate\Support\Facades\Cache;
//
//class UpdateCard extends Component
//{
// public ?string $current = null; // installierte Version
// public ?string $latest = null; // verfügbare Version (oder null)
// public string $state = 'idle'; // idle | running
//
// // UI-Textausgabe nach einer Prüfung / Aktion
// public ?string $message = null; // z.B. "Du bist auf dem neuesten Stand (v1.0.16)"
// public ?bool $messagePositive = null; // true = grün, false = neutral/weiß
//
// // low-level (falls du sie später brauchst)
// public bool $running = false; // aus /var/lib/mailwolt/update/state
// public ?int $rc = null;
//
// public function mount(): void
// {
// $this->current = $this->readCurrentVersion();
// $this->latest = Cache::get('mailwolt.update_available');
// $this->refreshLowLevelState();
//
// // Starttext, falls nichts geprüft wurde
// if ($this->message === null) {
// $this->message = $this->latest && $this->hasUpdate()
// ? "Neue Version verfügbar: {$this->latest}"
// : ($this->current ? "Du bist auf dem neuesten Stand ({$this->current})" : "Status unbekannt");
// $this->messagePositive = !$this->hasUpdate();
// }
// }
//
// public function render()
// {
// return view('livewire.ui.system.update-card');
// }
//
// /**
// * „Erneut prüfen“ ohne Toast:
// * - Progress anzeigen
// * - Check-Command laufen lassen
// * - Message in der Box aktualisieren
// */
// public function refreshState(): void
// {
// $this->state = 'running';
// $this->message = 'Prüfe auf Updates …';
// $this->messagePositive = null;
//
// try {
// // Passe den Namen hier an dein tatsächliches Command an:
// Artisan::call('mailwolt:check-updates');
// } catch (\Throwable $e) {
// // weich fallen
// }
//
// // Daten neu einlesen
// $this->current = $this->readCurrentVersion();
// $this->latest = Cache::get('mailwolt.update_available');
//
// if ($this->hasUpdate()) {
// $this->message = "Neue Version verfügbar: {$this->latest}";
// $this->messagePositive = false; // neutral
// } else {
// $cur = $this->current ?: '';
// $this->message = "Du bist auf dem neuesten Stand ({$cur})";
// $this->messagePositive = true; // grün
// }
//
// $this->refreshLowLevelState();
// $this->state = 'idle';
// }
//
// /**
// * „Jetzt aktualisieren“ ohne Toast:
// * - Hinweis sofort aus Cache entfernen (Badge weg)
// * - Update-Wrapper starten
// * - Running + Text in der Box anzeigen
// */
// public function runUpdate(): void
// {
// Cache::forget('mailwolt.update_available');
//
// @shell_exec('nohup sudo -n /usr/local/sbin/mw-update >/dev/null 2>&1 &');
//
// $this->latest = null;
// $this->state = 'running';
// $this->running = true;
// $this->message = 'Update läuft …';
// $this->messagePositive = null;
// }
//
// /** --------------------- helpers --------------------- */
//
// protected function hasUpdate(): bool
// {
// if (!$this->latest) return false;
// $cur = $this->current ?: '0.0.0';
// return version_compare($this->latest, $cur, '>');
// }
//
// protected function refreshLowLevelState(): 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
// {
// // bevorzugt /etc/mailwolt/build.info (wird im Installer/Updater gepflegt)
// $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 $v;
// }
// }
// }
// $v = config('app.version');
// return $v !== '' ? $v : null;
// }
//}
// //
// //
//namespace App\Livewire\Ui\System; //namespace App\Livewire\Ui\System;

View File

@ -1,60 +1,60 @@
@php {{--@php--}}
$hasUpdate = $latest && $current && version_compare($latest, $current, '>'); {{-- $hasUpdate = $latest && $current && version_compare($latest, $current, '>');--}}
@endphp {{--@endphp--}}
<div class="glass-card rounded-2xl p-4 border border-white/10 bg-white/5 max-h-fit"> {{--<div class="glass-card rounded-2xl p-4 border border-white/10 bg-white/5 max-h-fit">--}}
<div class="flex items-start gap-2"> {{-- <div class="flex items-start gap-2">--}}
{{-- Shield-Bot --}} {{-- --}}{{-- Shield-Bot --}}
<div class="relative shrink-0"> {{-- <div class="relative shrink-0">--}}
<div class="shrink-0 relative"> {{-- <div class="shrink-0 relative">--}}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64"> {{-- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">--}}
<defs> {{-- <defs>--}}
<linearGradient id="shieldGradient" x1="0" y1="0" x2="0" y2="1"> {{-- <linearGradient id="shieldGradient" x1="0" y1="0" x2="0" y2="1">--}}
<stop offset="0" stop-color="#4ade80"></stop> {{-- <stop offset="0" stop-color="#4ade80"></stop>--}}
<stop offset="1" stop-color="#15803d"></stop> {{-- <stop offset="1" stop-color="#15803d"></stop>--}}
</linearGradient> {{-- </linearGradient>--}}
<radialGradient id="shine" cx="30%" cy="20%" r="70%"> {{-- <radialGradient id="shine" cx="30%" cy="20%" r="70%">--}}
<stop offset="0%" stop-color="rgba(255,255,255,0.4)"></stop> {{-- <stop offset="0%" stop-color="rgba(255,255,255,0.4)"></stop>--}}
<stop offset="100%" stop-color="rgba(255,255,255,0)"></stop> {{-- <stop offset="100%" stop-color="rgba(255,255,255,0)"></stop>--}}
</radialGradient> {{-- </radialGradient>--}}
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%"> {{-- <filter id="glow" x="-20%" y="-20%" width="140%" height="140%">--}}
<feDropShadow dx="0" dy="0" stdDeviation="3" flood-color="#22c55e" flood-opacity="0.6"></feDropShadow> {{-- <feDropShadow dx="0" dy="0" stdDeviation="3" flood-color="#22c55e" flood-opacity="0.6"></feDropShadow>--}}
</filter> {{-- </filter>--}}
</defs> {{-- </defs>--}}
<path d="M32 6l20 8v12c0 13.5-8.7 22.7-20 27-11.3-4.3-20-13.5-20-27V14l20-8z" fill="url(#shieldGradient)" filter="url(#glow)"></path> {{-- <path d="M32 6l20 8v12c0 13.5-8.7 22.7-20 27-11.3-4.3-20-13.5-20-27V14l20-8z" fill="url(#shieldGradient)" filter="url(#glow)"></path>--}}
<path d="M32 6l20 8v12c0 13.5-8.7 22.7-20 27-11.3-4.3-20-13.5-20-27V14l20-8z" fill="url(#shine)"></path> {{-- <path d="M32 6l20 8v12c0 13.5-8.7 22.7-20 27-11.3-4.3-20-13.5-20-27V14l20-8z" fill="url(#shine)"></path>--}}
<i class="ph-bold ph-arrows-clockwise absolute top-1/2 left-1/2 -translate-1/2 text-2xl {{ $state === 'running' ? 'animate-spin' : '' }}"></i> {{-- <i class="ph-bold ph-arrows-clockwise absolute top-1/2 left-1/2 -translate-1/2 text-2xl {{ $state === 'running' ? 'animate-spin' : '' }}"></i>--}}
</svg> {{-- </svg>--}}
</div> {{-- </div>--}}
</div> {{-- </div>--}}
<div class="min-w-0 flex-1"> {{-- <div class="min-w-0 flex-1">--}}
<div class="flex items-center justify-between gap-3"> {{-- <div class="flex items-center justify-between gap-3">--}}
<div class="min-w-0"> {{-- <div class="min-w-0">--}}
<div class="text-white/90 font-semibold">MailWolt Update</div> {{-- <div class="text-white/90 font-semibold">MailWolt Update</div>--}}
{{-- kleine Statuszeile mit Versionen, wenn vorhanden --}} {{-- --}}{{-- kleine Statuszeile mit Versionen, wenn vorhanden --}}
<div class="text-xs text-white/70"> {{-- <div class="text-xs text-white/70">--}}
@if($current) {{-- @if($current)--}}
aktuell: <span class="text-white/90">v{{ $current }}</span> {{-- aktuell: <span class="text-white/90">{{ $current }}</span>--}}
@else {{-- @else--}}
aktuell: <span class="text-white/60"></span> {{-- aktuell: <span class="text-white/60"></span>--}}
@endif {{-- @endif--}}
@if($latest) {{-- @if($latest)--}}
<span class="mx-1 text-white/30"></span> {{-- <span class="mx-1 text-white/30"></span>--}}
verfügbar: <span class="text-emerald-200">v{{ $latest }}</span> {{-- verfügbar: <span class="text-emerald-200">{{ $latest }}</span>--}}
@endif {{-- @endif--}}
</div> {{-- </div>--}}
</div> {{-- </div>--}}
<div> {{-- <div>--}}
{{-- Badge rechts --}} {{-- --}}{{-- Badge rechts --}}
<span class="shrink-0 inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-[11px] border px-3 py-1 {{-- <span class="shrink-0 inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-[11px] border px-3 py-1--}}
{{ $hasUpdate {{-- {{ $hasUpdate--}}
? 'text-yellow-200 bg-yellow-500/10 border-yellow-400/30' {{-- ? 'text-yellow-200 bg-yellow-500/10 border-yellow-400/30'--}}
: 'text-emerald-200 bg-emerald-500/10 border-emerald-400/30' }}"> {{-- : 'text-emerald-200 bg-emerald-500/10 border-emerald-400/30' }}">--}}
<i class="ph {{ $hasUpdate ? 'ph-arrow-fat-line-up' : 'ph-check-circle' }} text-[12px]"></i> {{-- <i class="ph {{ $hasUpdate ? 'ph-arrow-fat-line-up' : 'ph-check-circle' }} text-[12px]"></i>--}}
{{ $hasUpdate ? 'Update verfügbar' : 'Aktuell' }} {{-- {{ $hasUpdate ? 'Update verfügbar' : 'Aktuell' }}--}}
</span> {{-- </span>--}}
{{-- <button wire:click="refreshState"--}} {{-- <button wire:click="refreshState"--}}
{{-- @disabled($state==='running')--}} {{-- @disabled($state==='running')--}}
{{-- class="inline-flex items-center gap-2 rounded p-1.5--}} {{-- class="inline-flex items-center gap-2 rounded p-1.5--}}
@ -63,10 +63,100 @@
{{-- <i class="ph ph-arrow-clockwise text-[11px]"></i>--}} {{-- <i class="ph ph-arrow-clockwise text-[11px]"></i>--}}
{{-- </button>--}} {{-- </button>--}}
{{-- </div>--}}
{{-- </div>--}}
{{-- <div class="mt-4 flex items-center gap-2">--}}
{{-- @if($hasUpdate)--}}
{{-- <button wire:click="runUpdate"--}}
{{-- @disabled($state==='running')--}}
{{-- class="inline-flex items-center gap-2 rounded-lg px-3 py-1.5--}}
{{-- text-emerald-200 bg-emerald-500/10 border border-emerald-400/30--}}
{{-- hover:bg-emerald-500/15 hover:border-emerald-300/50--}}
{{-- disabled:opacity-60">--}}
{{-- <i class="ph ph-arrow-fat-lines-up text-[14px]"></i> Jetzt aktualisieren--}}
{{-- </button>--}}
{{-- @endif--}}
{{-- </div>--}}
{{-- --}}{{-- Progress-Bar, wenn running --}}
{{-- @if($state === 'running')--}}
{{-- <div class="mt-3 h-1.5 rounded bg-white/10 overflow-hidden">--}}
{{-- <div class="h-full bg-emerald-400/70 animate-[progress_1.4s_infinite]" style="width:40%"></div>--}}
{{-- </div>--}}
{{-- <style>@keyframes progress {--}}
{{-- 0% {--}}
{{-- transform: translateX(-100%)--}}
{{-- }--}}
{{-- 100% {--}}
{{-- transform: translateX(260%)--}}
{{-- }--}}
{{-- }</style>--}}
{{-- @endif--}}
{{-- </div>--}}
{{-- </div>--}}
{{--</div>--}}
<div
class="glass-card rounded-2xl p-4 border border-white/10 bg-white/5 max-h-fit"
@if($state === 'running') wire:poll.1200ms="tick" @endif
>
<div class="flex items-start gap-2">
{{-- Shield + Update-Icon --}}
<div class="relative shrink-0">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
<defs>
<linearGradient id="shieldGradient" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#4ade80"/>
<stop offset="1" stop-color="#15803d"/>
</linearGradient>
<radialGradient id="shine" cx="30%" cy="20%" r="70%">
<stop offset="0%" stop-color="rgba(255,255,255,0.35)"/>
<stop offset="100%" stop-color="rgba(255,255,255,0)"/>
</radialGradient>
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="0" dy="0" stdDeviation="3" flood-color="#22c55e" flood-opacity="0.6"/>
</filter>
</defs>
<!-- Schild -->
<path d="M32 6l20 8v12c0 13.5-8.7 22.7-20 27-11.3-4.3-20-13.5-20-27V14l20-8z"
fill="url(#shieldGradient)" filter="url(#glow)"/>
<path d="M32 6l20 8v12c0 13.5-8.7 22.7-20 27-11.3-4.3-20-13.5-20-27V14l20-8z"
fill="url(#shine)"/>
<i class="ph-bold ph-arrows-clockwise absolute top-1/2 left-1/2 -translate-1/2 text-2xl {{ $state === 'running' ? 'animate-spin' : '' }}"></i>
</svg>
</div>
<div class="min-w-0 flex-1">
<div class="flex items-center justify-between gap-3">
<div class="min-w-0">
<div class="text-white/90 font-semibold">MailWolt Update</div>
<div class="text-xs text-white/70">
@if($current)
aktuell: <span class="text-white/90">{{ $current }}</span>
@else
aktuell: <span class="text-white/60"></span>
@endif
@if($latest)
<span class="mx-1 text-white/30"></span>
verfügbar: <span class="text-emerald-200">{{ $latest }}</span>
@endif
</div> </div>
</div> </div>
{{-- Badge rechts --}}
<span class="shrink-0 inline-flex items-center gap-1.5 rounded-full text-[11px] border px-3 py-1
{{ $this->hasUpdate
? 'text-yellow-200 bg-yellow-500/10 border-yellow-400/30'
: 'text-emerald-200 bg-emerald-500/10 border-emerald-400/30' }}">
<i class="ph {{ $this->hasUpdate ? 'ph-arrow-fat-line-up' : 'ph-check-circle' }} text-[12px]"></i>
{{ $this->hasUpdate ? 'Update verfügbar' : 'Aktuell' }}
</span>
</div>
<div class="mt-4 flex items-center gap-2"> <div class="mt-4 flex items-center gap-2">
@if($hasUpdate) @if($this->hasUpdate)
<button wire:click="runUpdate" <button wire:click="runUpdate"
@disabled($state==='running') @disabled($state==='running')
class="inline-flex items-center gap-2 rounded-lg px-3 py-1.5 class="inline-flex items-center gap-2 rounded-lg px-3 py-1.5
@ -78,19 +168,17 @@
@endif @endif
</div> </div>
{{-- Progress-Bar, wenn running --}} {{-- Fortschritt nur während running --}}
@if($state === 'running') @if($state === 'running')
<div class="mt-3 h-1.5 rounded bg-white/10 overflow-hidden"> <div class="mt-3 h-1.5 rounded bg-white/10 overflow-hidden">
<div class="h-full bg-emerald-400/70 animate-[progress_1.4s_infinite]" style="width:40%"></div> <div class="h-full bg-emerald-400/70 animate-[mwprogress_1.4s_infinite]" style="width:40%"></div>
</div> </div>
<style>@keyframes progress { <style>
0% { @keyframes mwprogress {
transform: translateX(-100%) 0% { transform: translateX(-100%) }
100% { transform: translateX(260%) }
} }
100% { </style>
transform: translateX(260%)
}
}</style>
@endif @endif
</div> </div>
</div> </div>