From d4f667437c26d549c7709c820f12d0900e98cf96 Mon Sep 17 00:00:00 2001 From: HomeOS Bootstrap Date: Fri, 17 Jul 2026 20:56:32 +0200 Subject: [PATCH] Dashboard: live service health + fault surfacing; declutter sidebar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses user feedback on Phase 1: - Dashboard reoriented from static stack facts to LIVE operational status. New health banner surfaces faults prominently (green when all online; amber/red with a pluralized problem count when a service is down). Service tiles probe DB, Redis, Reverb (socket) and Horizon in real time with latency; versions moved to a small, de-emphasized "System" block. wire:poll.10s + a refresh button keep it live. Verified: stopping Reverb flips the banner to "Ein Dienst mit Störung" and the Echtzeit tile to Offline/nicht erreichbar (screenshotted), healthy state restores clean. - Sidebar decluttered: removed the wrapping "In Kürze" count badges; coming-soon items are now dimmed (still show lock icons where gated). R12 re-verified in headless Chromium: 15/15 assertions, 0 console errors, 0 failed requests, breakpoints 375/768/1280. 7 feature tests still green. Co-Authored-By: Claude Opus 4.8 --- .gitignore | 2 +- app/Livewire/Dashboard.php | 107 ++++++++++++++- lang/de/dashboard.php | 31 +++-- lang/en/dashboard.php | 31 +++-- resources/views/components/sidebar.blade.php | 14 +- resources/views/livewire/dashboard.blade.php | 137 +++++++++++-------- 6 files changed, 231 insertions(+), 91 deletions(-) diff --git a/.gitignore b/.gitignore index 0eeb509..ba8ad20 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,6 @@ Thumbs.db # HomeOS /docker-compose.override.yml /_bootstrap -/_verify.mjs +/_verify*.mjs /sidecar/__pycache__ *.pyc diff --git a/app/Livewire/Dashboard.php b/app/Livewire/Dashboard.php index 15db91e..f67305d 100644 --- a/app/Livewire/Dashboard.php +++ b/app/Livewire/Dashboard.php @@ -2,18 +2,117 @@ namespace App\Livewire; +use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Redis; use Livewire\Attributes\Layout; use Livewire\Component; #[Layout('layouts.app')] class Dashboard extends Component { - /** Interactive demo toggle — verifies Livewire reactivity in Phase 1. */ - public bool $demoOn = true; + /** @var array */ + public array $services = []; - public function toggleDemo(): void + public ?string $checkedAt = null; + + public function mount(): void { - $this->demoOn = ! $this->demoOn; + $this->refreshHealth(); + } + + /** Re-run all service health probes (called on mount, wire:poll and the refresh button). */ + public function refreshHealth(): void + { + $this->services = [ + $this->checkDatabase(), + $this->checkRedis(), + $this->checkReverb(), + $this->checkHorizon(), + ]; + + $this->checkedAt = now()->format('H:i:s'); + } + + /** Worst state across services (drives the banner): offline > warning > online. */ + public function worstState(): string + { + $states = array_column($this->services, 'state'); + + return in_array('offline', $states, true) ? 'offline' + : (in_array('warning', $states, true) ? 'warning' : 'online'); + } + + public function problemCount(): int + { + return count(array_filter($this->services, fn ($s) => $s['state'] !== 'online')); + } + + protected function checkDatabase(): array + { + $base = ['key' => 'database', 'label' => __('dashboard.svc_database'), 'icon' => 'network']; + + try { + $start = microtime(true); + DB::connection()->select('select 1'); + $ms = (int) round((microtime(true) - $start) * 1000); + + return $base + ['state' => 'online', 'detail' => $ms.' ms']; + } catch (\Throwable) { + return $base + ['state' => 'offline', 'detail' => __('dashboard.svc_unreachable')]; + } + } + + protected function checkRedis(): array + { + $base = ['key' => 'cache', 'label' => __('dashboard.svc_cache'), 'icon' => 'devices']; + + try { + $start = microtime(true); + Redis::connection()->ping(); + $ms = (int) round((microtime(true) - $start) * 1000); + + return $base + ['state' => 'online', 'detail' => $ms.' ms']; + } catch (\Throwable) { + return $base + ['state' => 'offline', 'detail' => __('dashboard.svc_unreachable')]; + } + } + + protected function checkReverb(): array + { + $base = ['key' => 'realtime', 'label' => __('dashboard.svc_realtime'), 'icon' => 'activity']; + + $host = config('broadcasting.connections.reverb.options.host', 'reverb'); + $port = (int) config('broadcasting.connections.reverb.options.port', 8080); + + $conn = @fsockopen($host, $port, $errno, $errstr, 1.0); + if ($conn) { + fclose($conn); + + return $base + ['state' => 'online', 'detail' => $host.':'.$port]; + } + + return $base + ['state' => 'offline', 'detail' => __('dashboard.svc_unreachable')]; + } + + protected function checkHorizon(): array + { + $base = ['key' => 'queue', 'label' => __('dashboard.svc_queue'), 'icon' => 'automation']; + + try { + $masters = app(\Laravel\Horizon\Contracts\MasterSupervisorRepository::class)->all(); + + if (empty($masters)) { + return $base + ['state' => 'offline', 'detail' => __('dashboard.svc_stopped')]; + } + + $running = collect($masters)->contains(fn ($m) => ($m->status ?? null) === 'running'); + + return $base + ($running + ? ['state' => 'online', 'detail' => __('dashboard.svc_running')] + : ['state' => 'warning', 'detail' => __('dashboard.svc_stopped')]); + } catch (\Throwable) { + return $base + ['state' => 'offline', 'detail' => __('dashboard.svc_unreachable')]; + } } public function render() diff --git a/lang/de/dashboard.php b/lang/de/dashboard.php index 876a92d..4d8b535 100644 --- a/lang/de/dashboard.php +++ b/lang/de/dashboard.php @@ -2,22 +2,31 @@ return [ 'title' => 'Dashboard', - 'subtitle' => 'Überblick über Haus, Geräte und Anwesenheit.', + 'subtitle' => 'Live-Status von System und Diensten.', - 'welcome_title' => 'Willkommen bei HomeOS', - 'welcome_body' => 'Das Grundgerüst steht. Räume, Geräte und Live-Zustände folgen in der nächsten Phase.', + // health banner + 'health_ok_title' => 'Alle Dienste online', + 'health_ok_body' => 'Das System läuft ohne bekannte Störungen.', + 'health_problem_title' => '{1}Ein Dienst mit Störung|[2,*]:count Dienste mit Störungen', + 'health_problem_body' => 'Bitte die betroffenen Dienste unten prüfen.', - 'phase_label' => 'Aufbau', - 'phase_value' => 'Phase 1', - 'phase_meta' => 'Fundament · Auth · Design-System', + // services + 'services_title' => 'Dienste', + 'svc_database' => 'Datenbank', + 'svc_cache' => 'Cache / Redis', + 'svc_realtime' => 'Echtzeit', + 'svc_queue' => 'Queue-Worker', + 'svc_unreachable'=> 'nicht erreichbar', + 'svc_running' => 'läuft', + 'svc_stopped' => 'gestoppt', + 'refresh' => 'Aktualisieren', + 'checked_at' => 'Geprüft um :time', + + // compact system info (versions — belongs to its own area later) + 'system_title' => 'System', 'stack_framework' => 'Framework', 'stack_database' => 'Datenbank', 'stack_realtime' => 'Echtzeit', 'stack_queue' => 'Queue', - - 'legend_title' => 'Design-System', - 'legend_status' => 'Statusfarben', - 'demo_toggle' => 'Beispiel-Schalter', - 'demo_chips' => 'Geräte-Chips', ]; diff --git a/lang/en/dashboard.php b/lang/en/dashboard.php index c03bb1e..21b29bf 100644 --- a/lang/en/dashboard.php +++ b/lang/en/dashboard.php @@ -2,22 +2,31 @@ return [ 'title' => 'Dashboard', - 'subtitle' => 'Overview of home, devices and presence.', + 'subtitle' => 'Live status of system and services.', - 'welcome_title' => 'Welcome to HomeOS', - 'welcome_body' => 'The foundation is in place. Rooms, devices and live state arrive in the next phase.', + // health banner + 'health_ok_title' => 'All services online', + 'health_ok_body' => 'The system is running with no known issues.', + 'health_problem_title' => '{1}One service has an issue|[2,*]:count services have issues', + 'health_problem_body' => 'Please check the affected services below.', - 'phase_label' => 'Build', - 'phase_value' => 'Phase 1', - 'phase_meta' => 'Foundation · Auth · Design system', + // services + 'services_title' => 'Services', + 'svc_database' => 'Database', + 'svc_cache' => 'Cache / Redis', + 'svc_realtime' => 'Realtime', + 'svc_queue' => 'Queue worker', + 'svc_unreachable'=> 'unreachable', + 'svc_running' => 'running', + 'svc_stopped' => 'stopped', + 'refresh' => 'Refresh', + 'checked_at' => 'Checked at :time', + + // compact system info (versions — belongs to its own area later) + 'system_title' => 'System', 'stack_framework' => 'Framework', 'stack_database' => 'Database', 'stack_realtime' => 'Realtime', 'stack_queue' => 'Queue', - - 'legend_title' => 'Design system', - 'legend_status' => 'Status colors', - 'demo_toggle' => 'Example switch', - 'demo_chips' => 'Device chips', ]; diff --git a/resources/views/components/sidebar.blade.php b/resources/views/components/sidebar.blade.php index 2af9020..a2bec7f 100644 --- a/resources/views/components/sidebar.blade.php +++ b/resources/views/components/sidebar.blade.php @@ -51,16 +51,14 @@ {{ __('nav.'.$item['key']) }} @else - + @endif @endforeach diff --git a/resources/views/livewire/dashboard.blade.php b/resources/views/livewire/dashboard.blade.php index 1f720e2..55fde93 100644 --- a/resources/views/livewire/dashboard.blade.php +++ b/resources/views/livewire/dashboard.blade.php @@ -1,68 +1,93 @@ -
+
- {{-- stack facts --}} -
- - - - -
+ @php + $worst = $this->worstState(); + $problems = $this->problemCount(); + $bannerMap = [ + 'online' => ['bg' => 'bg-online/10', 'bar' => 'bg-online', 'icon' => 'check', 'tint' => 'text-online'], + 'warning' => ['bg' => 'bg-warning/10', 'bar' => 'bg-warning', 'icon' => 'alert', 'tint' => 'text-warning'], + 'offline' => ['bg' => 'bg-offline/10', 'bar' => 'bg-offline', 'icon' => 'alert', 'tint' => 'text-offline'], + ]; + $b = $bannerMap[$worst]; + $stateLabel = [ + 'online' => __('common.status_online'), + 'warning' => __('common.status_warning'), + 'offline' => __('common.status_offline'), + ]; + @endphp -
- {{-- welcome --}} - -
-
- - + {{-- health banner — surfaces faults prominently --}} +
+ +
+ + + +
+

+ {{ $worst === 'online' + ? __('dashboard.health_ok_title') + : trans_choice('dashboard.health_problem_title', $problems, ['count' => $problems]) }} +

+

+ {{ $worst === 'online' ? __('dashboard.health_ok_body') : __('dashboard.health_problem_body') }} +

+
+
+ + +
+
+
+ + {{-- live service status — "was läuft gerade" --}} + +
+ @foreach ($services as $svc) +
+ + -
-

{{ __('dashboard.welcome_title') }}

-

{{ __('dashboard.welcome_body') }}

+
+
{{ $svc['label'] }}
+
{{ $svc['detail'] }}
-
-
- - {{ __('common.system_ok') }} + + + {{ $stateLabel[$svc['state']] }} - {{ __('dashboard.phase_label') }} · {{ __('dashboard.phase_value') }}
+ @endforeach +
+ + + {{-- compact system info (versions) — de-emphasized, own area later --}} + +
+
+
{{ __('dashboard.stack_framework') }}
+
Laravel {{ app()->version() }}
- - - {{-- design-system legend --}} - -
- {{-- status colors --}} -
- {{ __('dashboard.legend_status') }} -
- {{ __('common.status_online') }} - {{ __('common.status_warning') }} - {{ __('common.status_offline') }} -
-
- - {{-- interactive toggle (verifies Livewire) --}} -
- {{ __('dashboard.demo_toggle') }} - - {{ $demoOn ? __('common.status_online') : __('common.status_offline') }} -
- - {{-- device chips --}} -
- {{ __('dashboard.demo_chips') }} -
- Shelly Plus 1 - BLU Door - Plug S -
-
+
+
{{ __('dashboard.stack_database') }}
+
PostgreSQL 17 · TSDB
- -
+
+
{{ __('dashboard.stack_realtime') }}
+
Reverb
+
+
+
{{ __('dashboard.stack_queue') }}
+
Redis · Horizon
+
+
+