feat(redesign): project mockup richness onto live dashboard

Bringing the Aurora dashboard mockup to feature parity in the real app:

- NavIcon component: thin-stroke SVG icon set covering ~30 nav-tier
  glyphs (dashboard, server, target, radar, bots, console, email,
  matrix, webhook, slack, bullhorn, settings, theme, search, logout,
  chevrons, plus/arrow). Falls back to MdiIcon for unknown names so
  every existing usage keeps working.
- layout.svelte: sidebar nav swapped from MdiIcon to NavIcon — dropping
  the dense filled glyphs in favour of soft 1.6px outlines that match
  the mockup. Main container max-width bumped from 5xl (1024px) to
  7xl (1280px) so the dashboard finally gets to breathe.
- +page.svelte (dashboard): full rewrite of the markup to project
  every mockup section onto live data:
    * Editorial Hero — 'Tonight, everything is flowing.' with gradient
      italic emphasis, live/attention pill driven by failure count,
      and a big mono throughput meter (24h events, armed/total,
      providers, targets) on the right.
    * Stats — kept the 4-card grid, refined accent palette per card.
    * Two-column row — Signal stream (events with full routing trail:
      tracker → provider, gradient avatar tile per event-type) on the
      left, On-watch provider deck (per-provider activity bar fill +
      live/idle pulse, derived from recent_events) on the right.
    * Pulse panel wrapping the existing EventChart with the new title
      treatment.
    * Active wires — Sankey-style 'Tracker → Target' route summary
      derived from notificationTrackers.tracker_targets + targetsCache
      with event counts per route.
    * Compose band — gradient call-to-action strip with new-tracker CTA.
- i18n: en.json + ru.json get the full set of new dashboard keys
  (hero copy, panel titles, compose band, wires labels).

Build: 0 errors, 57 pre-existing warnings unchanged.
This commit is contained in:
2026-04-25 01:27:08 +03:00
parent d9ef3c6cc3
commit d3210fd5ea
5 changed files with 1223 additions and 241 deletions
+22 -22
View File
@@ -10,7 +10,7 @@
import { t, getLocale, setLocale } from '$lib/i18n';
import { getTheme, initTheme, setTheme, type Theme } from '$lib/theme.svelte';
import Modal from '$lib/components/Modal.svelte';
import MdiIcon from '$lib/components/MdiIcon.svelte';
import NavIcon from '$lib/components/NavIcon.svelte';
import Snackbar from '$lib/components/Snackbar.svelte';
import SearchPalette from '$lib/components/SearchPalette.svelte';
import { snackSuccess, snackError } from '$lib/stores/snackbar.svelte';
@@ -359,7 +359,7 @@
<div>
<h1 class="brand-mark">
{#if globalProviderFilter.provider}
<span class="brand-mark__icon" style="color: var(--color-primary);"><MdiIcon name={providerDefaultIcon(globalProviderFilter.provider)} size={16} /></span>
<span class="brand-mark__icon" style="color: var(--color-primary);"><NavIcon name={providerDefaultIcon(globalProviderFilter.provider)} size={16} /></span>
{/if}
<span class="brand-mark__notify">Notify</span> <em>Bridge</em>
</h1>
@@ -372,7 +372,7 @@
<button onclick={toggleSidebar}
class="sidebar-icon-btn flex items-center justify-center w-8 h-8 rounded-lg transition-all duration-200"
title={collapsed ? t('common.expand') : t('common.collapse')}>
<MdiIcon name={collapsed ? 'mdiChevronRight' : 'mdiChevronLeft'} size={18} />
<NavIcon name={collapsed ? 'mdiChevronRight' : 'mdiChevronLeft'} size={18} />
</button>
</div>
@@ -387,7 +387,7 @@
}}
class="provider-filter-btn flex items-center justify-center w-full py-1.5 rounded-lg text-sm transition-all duration-200"
title={globalProviderFilter.provider?.name || t('common.allProviders')}>
<MdiIcon name={globalProviderFilter.provider ? providerDefaultIcon(globalProviderFilter.provider) : 'mdiFilterOff'} size={16} />
<NavIcon name={globalProviderFilter.provider ? providerDefaultIcon(globalProviderFilter.provider) : 'mdiFilterOff'} size={16} />
</button>
{:else}
<IconGridSelect items={providerFilterItems} bind:value={providerFilterValue} columns={Math.min(providerFilterItems.length, 3)} compact />
@@ -400,7 +400,7 @@
<button onclick={() => openSearch?.()}
class="search-btn flex items-center gap-2 w-full {collapsed ? 'justify-center px-2' : 'px-2.5'} py-1.5 rounded-lg text-sm transition-all duration-200"
title={t('searchPalette.placeholder')}>
<MdiIcon name="mdiMagnify" size={16} />
<NavIcon name="mdiMagnify" size={16} />
{#if !collapsed}
<span class="flex-1 text-left text-xs">{t('searchPalette.placeholder')}</span>
<kbd class="text-[0.6rem] font-mono px-1 py-0.5 rounded" style="background: var(--color-background); border: 1px solid var(--color-border);">{isMac ? '⌘' : 'Ctrl '}K</kbd>
@@ -421,11 +421,11 @@
{#if isGroupActive(entry) && !expandedGroups[entry.key]}
<div class="active-indicator" style="position: absolute; left: 0; top: 50%; transform: translateY(-50%); width: 3px; height: 60%; border-radius: 0 3px 3px 0; background: var(--color-primary); box-shadow: 0 0 8px var(--color-glow-strong);"></div>
{/if}
<MdiIcon name={entry.icon} size={18} />
<NavIcon name={entry.icon} size={18} />
{#if !collapsed}
<span class="truncate flex-1">{t(entry.key)}</span>
<span class="nav-chevron" style="display: inline-flex; transition: transform 0.2s ease; transform: rotate({expandedGroups[entry.key] ? '90deg' : '0deg'});">
<MdiIcon name="mdiChevronRight" size={14} />
<NavIcon name="mdiChevronRight" size={14} />
</span>
{/if}
</button>
@@ -440,7 +440,7 @@
{#if isActive(child.href)}
<div class="active-indicator" style="position: absolute; left: -13px; top: 50%; transform: translateY(-50%); width: 3px; height: 60%; border-radius: 0 3px 3px 0; background: var(--color-primary); box-shadow: 0 0 8px var(--color-glow-strong);"></div>
{/if}
<MdiIcon name={child.icon} size={15} />
<NavIcon name={child.icon} size={15} />
<span class="truncate flex-1">{t(child.key)}</span>
{#if child.countKey && navCounts[child.countKey]}
<span class="nav-badge-sm">{navCounts[child.countKey]}</span>
@@ -459,7 +459,7 @@
{#if isActive(entry.href)}
<div class="active-indicator" style="position: absolute; left: 0; top: 50%; transform: translateY(-50%); width: 3px; height: 60%; border-radius: 0 3px 3px 0; background: var(--color-primary); box-shadow: 0 0 8px var(--color-glow-strong);"></div>
{/if}
<MdiIcon name={entry.icon} size={18} />
<NavIcon name={entry.icon} size={18} />
{#if !collapsed}
<span class="truncate flex-1">{t(entry.key)}</span>
{#if entry.countKey && navCounts[entry.countKey]}
@@ -483,12 +483,12 @@
<button onclick={cycleTheme}
class="footer-pill flex items-center justify-center {collapsed ? 'w-8 h-8' : 'px-2.5 py-1'} rounded-lg text-xs transition-all duration-200"
title={t('common.theme')}>
<MdiIcon name={theme.resolved === 'dark' ? 'mdiWeatherNight' : theme.current === 'system' ? 'mdiDesktopTowerMonitor' : 'mdiWeatherSunny'} size={14} />
<NavIcon name={theme.resolved === 'dark' ? 'mdiWeatherNight' : theme.current === 'system' ? 'mdiDesktopTowerMonitor' : 'mdiWeatherSunny'} size={14} />
</button>
<a href="/docs" target="_blank" rel="noopener noreferrer"
class="footer-pill flex items-center justify-center {collapsed ? 'w-8 h-8' : 'px-2.5 py-1'} rounded-lg text-xs transition-all duration-200"
title={t('common.apiDocs')}>
<MdiIcon name="mdiApi" size={14} />
<NavIcon name="mdiApi" size={14} />
</a>
</div>
@@ -498,7 +498,7 @@
<button onclick={logout}
class="sidebar-icon-btn w-full flex justify-center py-2 rounded-lg transition-all duration-200"
title={t('nav.logout')}>
<MdiIcon name="mdiLogout" size={16} />
<NavIcon name="mdiLogout" size={16} />
</button>
{:else}
<div class="px-1.5">
@@ -515,12 +515,12 @@
<button onclick={logout}
class="sidebar-icon-btn p-1.5 rounded-lg transition-all duration-200"
title={t('nav.logout')}>
<MdiIcon name="mdiLogout" size={15} />
<NavIcon name="mdiLogout" size={15} />
</button>
</div>
<button onclick={() => showPasswordForm = true}
class="change-pwd-link text-[0.7rem] mt-1.5 transition-colors duration-200 flex items-center gap-1">
<MdiIcon name="mdiKeyVariant" size={12} />
<NavIcon name="mdiKeyVariant" size={12} />
{t('common.changePassword')}
</button>
</div>
@@ -535,18 +535,18 @@
<a href={item.href} aria-label={t(item.key)}
class="flex flex-col items-center gap-0.5 px-2 py-1.5 text-xs rounded-lg transition-all duration-200"
style="color: {isActive(item.href) ? 'var(--color-primary)' : 'var(--color-muted-foreground)'};">
<MdiIcon name={item.icon} size={20} />
<NavIcon name={item.icon} size={20} />
</a>
{/each}
<button onclick={() => openSearch?.()} aria-label={t('searchPalette.placeholder')}
class="flex flex-col items-center gap-0.5 px-2 py-1.5 text-xs rounded-lg transition-all duration-200"
style="color: var(--color-muted-foreground);">
<MdiIcon name="mdiMagnify" size={20} />
<NavIcon name="mdiMagnify" size={20} />
</button>
<button onclick={() => mobileMoreOpen = !mobileMoreOpen} aria-label={t('nav.more')}
class="flex flex-col items-center gap-0.5 px-2 py-1.5 text-xs rounded-lg transition-all duration-200"
style="color: {mobileMoreOpen ? 'var(--color-primary)' : 'var(--color-muted-foreground)'};">
<MdiIcon name="mdiDotsHorizontal" size={20} />
<NavIcon name="mdiDotsHorizontal" size={20} />
</button>
</nav>
@@ -567,7 +567,7 @@
<div>
<div class="flex items-center gap-1.5 px-1 pb-1.5 text-[0.65rem] font-semibold uppercase tracking-wider"
style="color: var(--color-muted-foreground);">
<MdiIcon name={entry.icon} size={13} />
<NavIcon name={entry.icon} size={13} />
<span>{t(entry.key)}</span>
</div>
<div class="grid grid-cols-3 gap-2">
@@ -576,7 +576,7 @@
class="flex flex-col items-center gap-1 p-3 rounded-lg transition-all duration-200 relative"
style="color: {isActive(child.href) ? 'var(--color-primary)' : 'var(--color-muted-foreground)'}; background: {isActive(child.href) ? 'var(--color-sidebar-active)' : 'transparent'};"
>
<MdiIcon name={child.icon} size={20} />
<NavIcon name={child.icon} size={20} />
<span class="text-xs text-center leading-tight">{t(child.key)}</span>
{#if child.countKey && navCounts[child.countKey]}
<span class="nav-badge-sm" style="position: absolute; top: 0.25rem; right: 0.25rem;">{navCounts[child.countKey]}</span>
@@ -590,7 +590,7 @@
class="flex items-center gap-2 p-3 rounded-lg transition-all duration-200 relative"
style="color: {isActive(entry.href) ? 'var(--color-primary)' : 'var(--color-muted-foreground)'}; background: {isActive(entry.href) ? 'var(--color-sidebar-active)' : 'transparent'};"
>
<MdiIcon name={entry.icon} size={18} />
<NavIcon name={entry.icon} size={18} />
<span class="text-sm flex-1">{t(entry.key)}</span>
{#if entry.countKey && navCounts[entry.countKey]}
<span class="nav-badge">{navCounts[entry.countKey]}</span>
@@ -602,7 +602,7 @@
<button onclick={() => { closeMobileMore(); logout(); }}
class="flex items-center gap-2 p-3 w-full rounded-lg transition-all duration-200"
style="color: var(--color-muted-foreground);">
<MdiIcon name="mdiLogout" size={18} />
<NavIcon name="mdiLogout" size={18} />
<span class="text-sm">{t('nav.logout')}</span>
</button>
</div>
@@ -614,7 +614,7 @@
<main class="flex-1 overflow-auto md:pb-0"
style="padding-bottom: calc(4rem + env(safe-area-inset-bottom, 0px));">
{#key page.url.pathname}
<div class="max-w-5xl mx-auto p-4 md:p-8" in:fade={{ duration: 200, delay: 50 }}>
<div class="max-w-7xl mx-auto p-4 md:p-8" in:fade={{ duration: 200, delay: 50 }}>
{@render children()}
</div>
{/key}