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:
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user