diff --git a/app/frontend/index.html b/app/frontend/index.html index 5f26d19..c80b50e 100644 --- a/app/frontend/index.html +++ b/app/frontend/index.html @@ -5,6 +5,12 @@ containr +
diff --git a/app/frontend/public/containr.svg b/app/frontend/public/containr.svg index 256a12a..f1ef974 100644 --- a/app/frontend/public/containr.svg +++ b/app/frontend/public/containr.svg @@ -1,12 +1,12 @@ - - + + - + diff --git a/app/frontend/src/app/App.tsx b/app/frontend/src/app/App.tsx index 7930e2e..0812169 100644 --- a/app/frontend/src/app/App.tsx +++ b/app/frontend/src/app/App.tsx @@ -14,6 +14,7 @@ import { OperationsPage } from '@/features/operations/pages/OperationsPage'; import { ActivityPage } from '@/features/activity/pages/ActivityPage'; import { AdminPage } from '@/features/admin/pages/AdminPage'; import { LandingPage } from '@/features/landing/LandingPage'; +import { DashboardPage } from '@/features/dashboard/pages/DashboardPage'; import { useBranding } from '@/lib/use-branding'; import { DocsPage, @@ -92,7 +93,7 @@ function AdminRequired() {
-

Admin access required

+

Admin access required

This area is restricted to the platform administrator.

@@ -115,7 +116,7 @@ function NotFoundPage() {
-

Page not found

+

Page not found

The page you are looking for does not exist or was moved.

@@ -135,7 +136,7 @@ export default function App() { return ( - } /> + } /> } /> } /> } /> @@ -147,6 +148,7 @@ export default function App() { } /> }> + } /> } /> } /> } /> diff --git a/app/frontend/src/app/layout/PlatformShell.tsx b/app/frontend/src/app/layout/PlatformShell.tsx index 35e0a62..4a47aab 100644 --- a/app/frontend/src/app/layout/PlatformShell.tsx +++ b/app/frontend/src/app/layout/PlatformShell.tsx @@ -9,11 +9,13 @@ import { ChartBar, CheckCircle2, ChevronsLeft, + ChevronsUpDown, Clock, Container, Database, FileText, FolderKanban, + LayoutDashboard, LayoutTemplate, LogIn, LogOut, @@ -41,22 +43,26 @@ import { markAllNotificationsRead, markNotificationRead, pullUpgradeImage, + listProjects, } from '@/lib/api-client'; import { signOutAuthSession } from '@/lib/auth-client'; import { isDemoSearch } from '@/lib/demo-mode'; +import { demoProjects } from '@/lib/demo-data'; import { useAuthSession } from '@/lib/use-auth-session'; -import { BannerBar, BrandWordmark, useToast } from '@/shared/components'; +import { BannerBar, BrandWordmark, CommandPalette, useToast } from '@/shared/components'; type NavItem = { label: string; href: string; icon: typeof FolderKanban; hint: string }; type NavSection = { title: string; items: NavItem[] }; const navSections: NavSection[] = [ { - title: 'Build', + title: 'Platform', items: [ + { label: 'Dashboard', href: '/', icon: LayoutDashboard, hint: 'Fleet overview' }, { label: 'Projects', href: '/projects', icon: FolderKanban, hint: 'Services & deployments' }, { label: 'Templates', href: '/templates', icon: LayoutTemplate, hint: 'Deployable presets' }, { label: 'Builds', href: '/builds', icon: FileText, hint: 'Build history' }, + { label: 'Databases', href: '/databases', icon: Database, hint: 'Managed data services' }, ], }, { @@ -64,7 +70,6 @@ const navSections: NavSection[] = [ items: [ { label: 'Operations', href: '/operations', icon: Activity, hint: 'Live jobs & failures' }, { label: 'Activity', href: '/activity', icon: Clock, hint: 'Platform event feed' }, - { label: 'Databases', href: '/databases', icon: Database, hint: 'Managed data services' }, { label: 'High Availability', href: '/ha', icon: ShieldCheck, hint: 'Failover & health' }, { label: 'Security', href: '/security', icon: Shield, hint: 'Scans & compliance' }, { label: 'Usage', href: '/usage', icon: ChartBar, hint: 'Resource consumption' }, @@ -164,6 +169,18 @@ export function PlatformShell() { const [theme, setTheme] = useState(() => getInitialTheme()); const [sidebarExpanded, setSidebarExpanded] = useState(() => getInitialSidebar()); const [notificationsOpen, setNotificationsOpen] = useState(false); + const [paletteOpen, setPaletteOpen] = useState(false); + + useEffect(() => { + const down = (e: KeyboardEvent) => { + if (e.key === 'k' && (e.metaKey || e.ctrlKey)) { + e.preventDefault(); + setPaletteOpen((open) => !open); + } + }; + document.addEventListener('keydown', down); + return () => document.removeEventListener('keydown', down); + }, []); const sessionQuery = useAuthSession({ enabled: !isDemoMode }); const signedIn = isDemoMode || Boolean(sessionQuery.data); @@ -234,8 +251,19 @@ export function PlatformShell() { } }; - const isActiveRoute = (itemHref: string) => - location.pathname === itemHref || location.pathname.startsWith(`${itemHref}/`); + // Longest matching href wins so /settings/audit-logs lights only + // "Audit Logs", not "Settings" as well. + const activeHref = location.pathname === '/' + ? '/' + : visibleNavSections + .flatMap((section) => section.items.map((item) => item.href)) + .filter( + (itemHref) => + itemHref !== '/' && + (location.pathname === itemHref || location.pathname.startsWith(`${itemHref}/`)), + ) + .sort((a, b) => b.length - a.length)[0] ?? null; + const isActiveRoute = (itemHref: string) => itemHref === activeHref; const userName = isDemoMode ? 'Demo workspace' : profileQuery.data?.name ?? sessionQuery.data?.user.name ?? 'Account'; const userEmail = isDemoMode ? 'Sample data · read-only' : profileQuery.data?.email ?? sessionQuery.data?.user.email ?? 'Local session'; @@ -246,14 +274,20 @@ export function PlatformShell() { .slice(0, 2) .toUpperCase() || 'C'; - const avatar = ( -
- {initials} -
- ); + const paletteNavigate = (target: string) => { + navigate(href(target)); + }; + + // Project deep-links for the palette — only fetched once it opens. + const paletteProjectsQuery = useQuery({ + queryKey: ['palette-projects'], + queryFn: () => listProjects(), + enabled: paletteOpen && !isDemoMode, + staleTime: 60_000, + }); + const paletteProjects = isDemoMode + ? demoProjects + : paletteProjectsQuery.data ?? []; if (!isDemoMode && signedIn && setupQuery.data?.needs_setup) { return ; @@ -261,40 +295,46 @@ export function PlatformShell() { return (
-
-
+ {/* ── Sidebar — fixed rail per design/23-sentry ── */}