diff --git a/resources/js/app.js b/resources/js/app.js index 3ca10a3..c10d4b1 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -409,6 +409,35 @@ document.addEventListener('alpine:init', () => { }, })); + // Live sidebar badge chips (alerts / threats / update). The sidebar is static Blade (a Livewire + // poll would break its request()->is() active states), so this tiny island polls the cached + // badges endpoint and updates the [data-nav-badge] chips in place. Re-initialised by Alpine on + // every wire:navigate (body swap), so the interval never piles up across navigations. + window.Alpine.data('navBadges', () => ({ + timer: null, + init() { + this.refresh(); + this.timer = setInterval(() => this.refresh(), 15000); + }, + destroy() { + clearInterval(this.timer); + }, + async refresh() { + try { + const res = await fetch('/shell/badges.json', { headers: { Accept: 'application/json' } }); + if (!res.ok) return; + const counts = await res.json(); + for (const el of this.$el.querySelectorAll('[data-nav-badge]')) { + const n = Number(counts[el.dataset.navBadge] ?? 0); + el.hidden = n <= 0; + el.textContent = n > 0 ? String(n) : ''; + } + } catch { + // transient network error — keep the current chips, next tick retries + } + }, + })); + // Interactive terminal (xterm.js). xterm + its CSS are dynamically imported so they only load on // the terminal page (code-split chunk). On a 'terminal-open' event (from the Livewire rail) it // opens a same-origin WebSocket to /terminal/ws?token=… — the sidecar holds the PTY. A real PTY diff --git a/resources/views/components/nav-item.blade.php b/resources/views/components/nav-item.blade.php index d681835..4b8d388 100644 --- a/resources/views/components/nav-item.blade.php +++ b/resources/views/components/nav-item.blade.php @@ -1,4 +1,4 @@ -@props(['icon', 'href' => '#', 'active' => false, 'badge' => null, 'badgeTitle' => null]) +@props(['icon', 'href' => '#', 'active' => false, 'badge' => null, 'badgeTitle' => null, 'badgeKey' => null]) class([ 'group flex min-h-11 items-center gap-3 rounded-md border px-3 py-2 text-sm transition-colors', 'border-accent/25 bg-accent/10 text-ink shadow-[inset_2px_0_0_var(--color-accent)]' => $active, @@ -7,7 +7,14 @@ @if ($active) aria-current="page" @endif> $active]) /> {{ $slot }} - @if ($badge !== null && $badge !== '') + @if ($badgeKey !== null) + {{-- Live badge: always in the DOM (hidden at zero) so the navBadges poller can update it + in place without a page re-render. --}} + + @elseif ($badge !== null && $badge !== '') {{ $badge }} @endif diff --git a/resources/views/components/sidebar.blade.php b/resources/views/components/sidebar.blade.php index a0fe7f4..ee11a75 100644 --- a/resources/views/components/sidebar.blade.php +++ b/resources/views/components/sidebar.blade.php @@ -30,8 +30,8 @@ - {{-- Nav --}} -