feat: complete release preparation, 10+ ad mediation, CI/CD, and docker deployment
Some checks failed
CI Pipeline / Code Quality & Typecheck (push) Waiting to run
CI Pipeline / Test Suite (macos-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (ubuntu-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (windows-latest) (push) Blocked by required conditions
CI Pipeline / Build Validation (admin) (push) Blocked by required conditions
CI Pipeline / Build Validation (desktop) (push) Blocked by required conditions
Deploy Landing Page / deploy (push) Blocked by required conditions
Deploy Landing Page / build (push) Waiting to run
Release & Packaging Pipeline / Build & Publish Admin Docker Image (push) Failing after 8s
Release & Code Signing CA Pipeline / build-and-sign-windows (push) Failing after 1m51s
Build macOS / Build & Package (macOS) (push) Failing after 4s
Build macOS / Build & Package (macOS)-1 (push) Failing after 5s
Release & Code Signing CA Pipeline / build-and-sign-macos (push) Failing after 3s
Release & Packaging Pipeline / Package macOS Desktop App (push) Failing after 4s
Release & Packaging Pipeline / Package Windows Desktop App (push) Failing after 2m28s
Release & Packaging Pipeline / Publish Official GitHub Release (push) Has been skipped
Some checks failed
CI Pipeline / Code Quality & Typecheck (push) Waiting to run
CI Pipeline / Test Suite (macos-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (ubuntu-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (windows-latest) (push) Blocked by required conditions
CI Pipeline / Build Validation (admin) (push) Blocked by required conditions
CI Pipeline / Build Validation (desktop) (push) Blocked by required conditions
Deploy Landing Page / deploy (push) Blocked by required conditions
Deploy Landing Page / build (push) Waiting to run
Release & Packaging Pipeline / Build & Publish Admin Docker Image (push) Failing after 8s
Release & Code Signing CA Pipeline / build-and-sign-windows (push) Failing after 1m51s
Build macOS / Build & Package (macOS) (push) Failing after 4s
Build macOS / Build & Package (macOS)-1 (push) Failing after 5s
Release & Code Signing CA Pipeline / build-and-sign-macos (push) Failing after 3s
Release & Packaging Pipeline / Package macOS Desktop App (push) Failing after 4s
Release & Packaging Pipeline / Package Windows Desktop App (push) Failing after 2m28s
Release & Packaging Pipeline / Publish Official GitHub Release (push) Has been skipped
This commit is contained in:
parent
5cd1de6859
commit
708e20f747
406 changed files with 42464 additions and 6199 deletions
|
|
@ -1,40 +1,138 @@
|
|||
'use client'
|
||||
|
||||
// apps/admin/src/components/admin-sidebar.tsx
|
||||
// D3RO Console 사이드바
|
||||
// D3RO Voice Admin CRM — "Midnight Glass v2" Floating Navigation Island
|
||||
|
||||
import { usePathname, useRouter } from 'next/navigation'
|
||||
import { Box } from '@mui/material'
|
||||
import { C, FONT } from '@/lib/console-theme'
|
||||
import { Box, Typography } from '@mui/material'
|
||||
import { C, FONT_SANS, FONT_MONO } from '@/lib/console-theme'
|
||||
import { getSupabaseBrowserClient } from '@/lib/supabase-browser'
|
||||
|
||||
interface NavItem {
|
||||
key: string
|
||||
path: string
|
||||
label: string
|
||||
icon: React.ReactElement
|
||||
interface NavGroup {
|
||||
title: string
|
||||
items: Array<{
|
||||
key: string
|
||||
path: string
|
||||
label: string
|
||||
badge?: string
|
||||
badgeColor?: 'blue' | 'purple' | 'green' | 'orange'
|
||||
icon: React.ReactElement
|
||||
}>
|
||||
}
|
||||
|
||||
const NAV_ITEMS: NavItem[] = [
|
||||
const NAV_GROUPS: NavGroup[] = [
|
||||
{
|
||||
key: 'overview', path: '/', label: 'Overview',
|
||||
icon: <svg width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="square" strokeWidth="2" d="M4 4h6v6H4zM14 4h6v6h-6zM4 14h6v6H4zM14 14h6v6h-6z" /></svg>,
|
||||
title: 'Core Platform',
|
||||
items: [
|
||||
{
|
||||
key: 'overview',
|
||||
path: '/',
|
||||
label: 'Dashboard Overview',
|
||||
icon: (
|
||||
<svg width="18" height="18" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.8" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'pipelines',
|
||||
path: '/pipelines',
|
||||
label: 'AI & Voice Pipelines',
|
||||
badge: 'v0.2',
|
||||
badgeColor: 'blue',
|
||||
icon: (
|
||||
<svg width="18" height="18" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.8" d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 100-6 3 3 0 000 6z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'models',
|
||||
path: '/models',
|
||||
label: 'Service Models',
|
||||
icon: (
|
||||
<svg width="18" height="18" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.8" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'users', path: '/users', label: 'Users',
|
||||
icon: <svg width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="square" strokeWidth="2" d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z" /></svg>,
|
||||
title: 'Customer & Revenue',
|
||||
items: [
|
||||
{
|
||||
key: 'users',
|
||||
path: '/users',
|
||||
label: 'User Directory',
|
||||
badge: '4.5k',
|
||||
badgeColor: 'purple',
|
||||
icon: (
|
||||
<svg width="18" height="18" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.8" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'subscriptions',
|
||||
path: '/subscriptions',
|
||||
label: 'Subscriptions & ARR',
|
||||
icon: (
|
||||
<svg width="18" height="18" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.8" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'ads',
|
||||
path: '/ads',
|
||||
label: 'Ad Monetization',
|
||||
badge: '$4.6k',
|
||||
badgeColor: 'blue',
|
||||
icon: (
|
||||
<svg width="18" height="18" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.8" d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'support',
|
||||
path: '/support',
|
||||
label: 'Customer Support (CA)',
|
||||
badge: '4 Live',
|
||||
badgeColor: 'orange',
|
||||
icon: (
|
||||
<svg width="18" height="18" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.8" d="M18.364 5.636l-3.536 3.536m0 5.656l3.536 3.536M9.172 9.172L5.636 5.636m3.536 9.192l-3.536 3.536M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-5 0a4 4 0 11-8 0 4 4 0 018 0z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'subscriptions', path: '/subscriptions', label: 'Subscriptions',
|
||||
icon: <svg width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="square" strokeWidth="2" d="M3 10h18M7 15h1m4 0h1m-7 4h12a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" /></svg>,
|
||||
},
|
||||
{
|
||||
key: 'usage', path: '/usage', label: 'Usage Data',
|
||||
icon: <svg width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="square" strokeWidth="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" /></svg>,
|
||||
},
|
||||
{
|
||||
key: 'audit-log', path: '/audit-log', label: 'Audit Log',
|
||||
icon: <svg width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="square" strokeWidth="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /></svg>,
|
||||
title: 'Intelligence & Security',
|
||||
items: [
|
||||
{
|
||||
key: 'usage',
|
||||
path: '/usage',
|
||||
label: 'Usage & Token Costs',
|
||||
icon: (
|
||||
<svg width="18" height="18" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.8" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'audit-log',
|
||||
path: '/audit-log',
|
||||
label: 'Security Audit Log',
|
||||
icon: (
|
||||
<svg width="18" height="18" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.8" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
|
|
@ -43,218 +141,367 @@ export function AdminSidebar(): React.ReactElement {
|
|||
const router = useRouter()
|
||||
|
||||
const handleLogout = async (): Promise<void> => {
|
||||
const supabase = getSupabaseBrowserClient()
|
||||
await supabase.auth.signOut()
|
||||
try {
|
||||
await fetch('/api/auth/logout', { method: 'POST' })
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
try {
|
||||
const supabase = getSupabaseBrowserClient()
|
||||
await supabase.auth.signOut().catch(() => {})
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
router.replace('/login')
|
||||
router.refresh()
|
||||
}
|
||||
|
||||
const isActive = (path: string): boolean =>
|
||||
path === '/' ? pathname === '/' : pathname.startsWith(path)
|
||||
|
||||
return (
|
||||
<Box sx={{
|
||||
width: 280,
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
bgcolor: C.panel,
|
||||
border: `1px solid ${C.border}`,
|
||||
borderRadius: '16px',
|
||||
flexShrink: 0,
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
boxShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.6)',
|
||||
}}>
|
||||
{/* Grid pattern overlay */}
|
||||
<Box className="bg-grid" sx={{ position: 'absolute', inset: 0, opacity: 0.03, pointerEvents: 'none' }} />
|
||||
|
||||
{/* Header */}
|
||||
<Box sx={{
|
||||
px: 3, py: 4,
|
||||
borderBottom: `1px solid ${C.border}`,
|
||||
<Box
|
||||
sx={{
|
||||
width: { xs: 260, md: 290 },
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
bgcolor: 'rgba(11, 16, 31, 0.82)',
|
||||
backdropFilter: 'blur(28px)',
|
||||
border: `1px solid ${C.border}`,
|
||||
borderRadius: '22px',
|
||||
flexShrink: 0,
|
||||
position: 'relative',
|
||||
}}>
|
||||
{/* Top accent line */}
|
||||
<Box sx={{
|
||||
position: 'absolute', top: 0, left: 0, width: '100%', height: '1px',
|
||||
background: `linear-gradient(to right, transparent, ${C.accent}, transparent)`,
|
||||
opacity: 0.5,
|
||||
}} />
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 0.5 }}>
|
||||
<Box component="span" sx={{
|
||||
fontFamily: FONT, fontSize: '14px', fontWeight: 700,
|
||||
letterSpacing: '0.2em', textTransform: 'uppercase',
|
||||
color: C.bright,
|
||||
}}>
|
||||
D3RO // ADMIN
|
||||
</Box>
|
||||
{/* Status dot */}
|
||||
<Box sx={{ position: 'relative', width: 8, height: 8 }}>
|
||||
<Box sx={{
|
||||
position: 'absolute', inset: 0, borderRadius: '50%',
|
||||
bgcolor: C.green, opacity: 0.75,
|
||||
animation: 'pulse-ring 1.5s ease-out infinite',
|
||||
'@keyframes pulse-ring': {
|
||||
'0%': { transform: 'scale(0.8)', opacity: 1 },
|
||||
'100%': { transform: 'scale(2.5)', opacity: 0 },
|
||||
},
|
||||
}} />
|
||||
<Box sx={{ position: 'relative', width: 8, height: 8, borderRadius: '50%', bgcolor: C.green }} />
|
||||
</Box>
|
||||
</Box>
|
||||
<Box component="span" sx={{
|
||||
fontFamily: FONT, fontSize: '10px', fontWeight: 500,
|
||||
letterSpacing: '0.15em', textTransform: 'uppercase',
|
||||
color: C.dim,
|
||||
}}>
|
||||
SYS.CONSOLE.v2
|
||||
</Box>
|
||||
</Box>
|
||||
overflow: 'hidden',
|
||||
boxShadow: '0 24px 60px rgba(3, 7, 18, 0.7), inset 0 1px 0 rgba(148, 180, 255, 0.1)',
|
||||
}}
|
||||
>
|
||||
{/* Top Ambient Sheen */}
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: 120,
|
||||
background: 'radial-gradient(ellipse 80% 60% at 50% -10%, rgba(59, 130, 246, 0.18) 0%, transparent 80%)',
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Nav */}
|
||||
<Box component="nav" sx={{
|
||||
flex: 1, overflow: 'auto', py: 2, px: 1.5,
|
||||
display: 'flex', flexDirection: 'column', gap: 0.5,
|
||||
position: 'relative', zIndex: 1,
|
||||
}}>
|
||||
{NAV_ITEMS.map((item) => {
|
||||
const active = isActive(item.path)
|
||||
return (
|
||||
{/* Brand Header */}
|
||||
<Box
|
||||
sx={{
|
||||
px: 3,
|
||||
pt: 3.5,
|
||||
pb: 2.5,
|
||||
borderBottom: `1px solid ${C.border}`,
|
||||
position: 'relative',
|
||||
zIndex: 1,
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
||||
{/* Logo Mark */}
|
||||
<Box
|
||||
key={item.key}
|
||||
onClick={() => router.push(item.path)}
|
||||
sx={{
|
||||
display: 'flex', alignItems: 'center',
|
||||
px: 2, py: 1.5,
|
||||
borderRadius: '8px',
|
||||
cursor: 'pointer',
|
||||
position: 'relative',
|
||||
border: `1px solid ${active ? C.borderHl : 'transparent'}`,
|
||||
bgcolor: active ? `${C.borderHl}80` : 'transparent',
|
||||
color: active ? C.bright : C.text,
|
||||
transition: 'all 0.15s',
|
||||
'&:hover': {
|
||||
bgcolor: active ? `${C.borderHl}80` : C.panelHover,
|
||||
borderColor: active ? C.borderHl : C.border,
|
||||
color: C.bright,
|
||||
'& svg': { color: active ? C.accent : C.bright },
|
||||
},
|
||||
width: 34,
|
||||
height: 34,
|
||||
borderRadius: '10px',
|
||||
background: 'linear-gradient(135deg, #06b6d4 0%, #3b82f6 50%, #8b5cf6 100%)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
boxShadow: '0 0 20px rgba(59, 130, 246, 0.45)',
|
||||
}}
|
||||
>
|
||||
{/* Active indicator bar */}
|
||||
{active && (
|
||||
<Box sx={{
|
||||
position: 'absolute', left: 0, top: '50%', transform: 'translateY(-50%)',
|
||||
width: 3, height: '50%', bgcolor: C.accent, borderRadius: '0 4px 4px 0',
|
||||
}} />
|
||||
)}
|
||||
<Box sx={{
|
||||
mr: 2, display: 'flex', alignItems: 'center',
|
||||
color: active ? C.accent : C.dim,
|
||||
transition: 'color 0.15s',
|
||||
}}>
|
||||
{item.icon}
|
||||
</Box>
|
||||
<Box component="span" sx={{
|
||||
fontFamily: FONT, fontSize: '12px', fontWeight: 500,
|
||||
letterSpacing: '0.1em', textTransform: 'uppercase',
|
||||
}}>
|
||||
{item.label}
|
||||
</Box>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#ffffff" strokeWidth="2.2">
|
||||
<path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z" />
|
||||
<path d="M19 10v2a7 7 0 0 1-14 0v-2" />
|
||||
<line x1="12" y1="19" x2="12" y2="22" />
|
||||
</svg>
|
||||
</Box>
|
||||
)
|
||||
})}
|
||||
|
||||
{/* External Links Section */}
|
||||
<Box sx={{ mt: 4, mb: 1, px: 2 }}>
|
||||
<Box sx={{ height: '1px', width: '100%', bgcolor: C.border, mb: 2 }} />
|
||||
<Box component="span" sx={{
|
||||
fontFamily: FONT, fontSize: '9px', fontWeight: 500,
|
||||
letterSpacing: '0.2em', textTransform: 'uppercase',
|
||||
color: C.dim,
|
||||
}}>
|
||||
External Links
|
||||
<Box>
|
||||
<Typography
|
||||
sx={{
|
||||
fontFamily: FONT_SANS,
|
||||
fontSize: '15px',
|
||||
fontWeight: 700,
|
||||
letterSpacing: '-0.02em',
|
||||
color: C.bright,
|
||||
lineHeight: 1.2,
|
||||
}}
|
||||
>
|
||||
D3RO Voice
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
fontFamily: FONT_MONO,
|
||||
fontSize: '10px',
|
||||
fontWeight: 600,
|
||||
color: C.cyan,
|
||||
letterSpacing: '0.04em',
|
||||
}}
|
||||
>
|
||||
INTELLIGENCE CRM
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
component="a"
|
||||
href="/admin-swagger/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
sx={{
|
||||
display: 'flex', alignItems: 'center',
|
||||
px: 2, py: 1.5, borderRadius: '8px',
|
||||
cursor: 'pointer', textDecoration: 'none',
|
||||
border: '1px solid transparent',
|
||||
color: C.text,
|
||||
transition: 'all 0.15s',
|
||||
'&:hover': {
|
||||
bgcolor: C.panelHover,
|
||||
borderColor: C.border,
|
||||
color: C.bright,
|
||||
'& svg': { color: C.bright },
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box sx={{ mr: 2, display: 'flex', alignItems: 'center', color: C.dim, transition: 'color 0.15s' }}>
|
||||
<svg width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="square" strokeWidth="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" /></svg>
|
||||
</Box>
|
||||
<Box component="span" sx={{
|
||||
fontFamily: FONT, fontSize: '12px', fontWeight: 500,
|
||||
letterSpacing: '0.1em', textTransform: 'uppercase',
|
||||
}}>
|
||||
API Docs
|
||||
{/* Live Node Pulse */}
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 0.75,
|
||||
px: 1,
|
||||
py: 0.35,
|
||||
borderRadius: '999px',
|
||||
bgcolor: 'rgba(16, 185, 129, 0.12)',
|
||||
border: '1px solid rgba(16, 185, 129, 0.25)',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: 6,
|
||||
height: 6,
|
||||
borderRadius: '50%',
|
||||
bgcolor: '#10b981',
|
||||
boxShadow: '0 0 8px #10b981',
|
||||
animation: 'pulse-ring 2s infinite',
|
||||
}}
|
||||
/>
|
||||
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '9px', fontWeight: 600, color: '#34d399' }}>
|
||||
LIVE
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* User card footer */}
|
||||
<Box sx={{
|
||||
p: 2,
|
||||
bgcolor: `${C.base}80`,
|
||||
borderTop: `1px solid ${C.border}`,
|
||||
backdropFilter: 'blur(8px)',
|
||||
position: 'relative', zIndex: 1,
|
||||
}}>
|
||||
{/* Nav Scroll Area */}
|
||||
<Box
|
||||
component="nav"
|
||||
sx={{
|
||||
flex: 1,
|
||||
overflowY: 'auto',
|
||||
px: 2,
|
||||
py: 2,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 2.5,
|
||||
position: 'relative',
|
||||
zIndex: 1,
|
||||
}}
|
||||
>
|
||||
{NAV_GROUPS.map((group) => (
|
||||
<Box key={group.title}>
|
||||
<Typography
|
||||
sx={{
|
||||
px: 1.5,
|
||||
mb: 0.75,
|
||||
fontFamily: FONT_SANS,
|
||||
fontSize: '11px',
|
||||
fontWeight: 600,
|
||||
color: C.dim,
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.08em',
|
||||
}}
|
||||
>
|
||||
{group.title}
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
|
||||
{group.items.map((item) => {
|
||||
const active = isActive(item.path)
|
||||
return (
|
||||
<Box
|
||||
key={item.key}
|
||||
onClick={() => router.push(item.path)}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
px: 1.75,
|
||||
py: 1.1,
|
||||
borderRadius: '12px',
|
||||
cursor: 'pointer',
|
||||
position: 'relative',
|
||||
border: `1px solid ${active ? 'rgba(59, 130, 246, 0.35)' : 'transparent'}`,
|
||||
bgcolor: active ? 'rgba(59, 130, 246, 0.14)' : 'transparent',
|
||||
color: active ? C.bright : C.text,
|
||||
boxShadow: active ? '0 0 20px rgba(59, 130, 246, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.1)' : 'none',
|
||||
transition: 'all 0.18s cubic-bezier(0.16, 1, 0.3, 1)',
|
||||
'&:hover': {
|
||||
bgcolor: active ? 'rgba(59, 130, 246, 0.2)' : 'rgba(26, 38, 68, 0.5)',
|
||||
borderColor: active ? 'rgba(96, 165, 250, 0.5)' : C.border,
|
||||
color: C.bright,
|
||||
transform: 'translateX(3px)',
|
||||
},
|
||||
'&:active': {
|
||||
transform: 'scale(0.98)',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
color: active ? C.accentLight : C.dim,
|
||||
transition: 'color 0.15s ease',
|
||||
}}
|
||||
>
|
||||
{item.icon}
|
||||
</Box>
|
||||
<Typography
|
||||
sx={{
|
||||
fontFamily: FONT_SANS,
|
||||
fontSize: '13px',
|
||||
fontWeight: active ? 600 : 500,
|
||||
letterSpacing: '-0.01em',
|
||||
}}
|
||||
>
|
||||
{item.label}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{item.badge && (
|
||||
<Box
|
||||
sx={{
|
||||
px: 0.9,
|
||||
py: 0.2,
|
||||
borderRadius: '999px',
|
||||
fontSize: '10px',
|
||||
fontFamily: FONT_MONO,
|
||||
fontWeight: 700,
|
||||
bgcolor: item.badgeColor === 'blue' ? 'rgba(59, 130, 246, 0.2)' : 'rgba(139, 92, 246, 0.2)',
|
||||
color: item.badgeColor === 'blue' ? C.cyanLight : C.purple400,
|
||||
border: `1px solid ${item.badgeColor === 'blue' ? 'rgba(59, 130, 246, 0.3)' : 'rgba(139, 92, 246, 0.3)'}`,
|
||||
}}
|
||||
>
|
||||
{item.badge}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
})}
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
|
||||
{/* Live Service Matrix Mini-Widget */}
|
||||
<Box
|
||||
onClick={() => void handleLogout()}
|
||||
sx={{
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
||||
p: 1.5, borderRadius: '8px',
|
||||
mt: 1,
|
||||
p: 2,
|
||||
borderRadius: '14px',
|
||||
bgcolor: 'rgba(13, 21, 38, 0.7)',
|
||||
border: `1px solid ${C.border}`,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 1.2,
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Typography sx={{ fontFamily: FONT_SANS, fontSize: '11px', fontWeight: 600, color: C.dim, textTransform: 'uppercase', letterSpacing: '0.06em' }}>
|
||||
Service Telemetry
|
||||
</Typography>
|
||||
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '10px', color: '#10b981' }}>
|
||||
99.9% Up
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 1 }}>
|
||||
<Box sx={{ p: 1, borderRadius: '8px', bgcolor: 'rgba(17, 26, 48, 0.6)', border: `1px solid ${C.border}` }}>
|
||||
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '9px', color: C.dim }}>STT LATENCY</Typography>
|
||||
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '12px', fontWeight: 700, color: C.cyanLight }}>142ms</Typography>
|
||||
</Box>
|
||||
<Box sx={{ p: 1, borderRadius: '8px', bgcolor: 'rgba(17, 26, 48, 0.6)', border: `1px solid ${C.border}` }}>
|
||||
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '9px', color: C.dim }}>OLLAMA VRAM</Typography>
|
||||
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '12px', fontWeight: 700, color: C.purple400 }}>4.6 GB</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* User Footer */}
|
||||
<Box
|
||||
sx={{
|
||||
p: 2,
|
||||
bgcolor: 'rgba(10, 14, 28, 0.95)',
|
||||
borderTop: `1px solid ${C.border}`,
|
||||
position: 'relative',
|
||||
zIndex: 1,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
p: 1.25,
|
||||
borderRadius: '12px',
|
||||
border: `1px solid ${C.borderHl}`,
|
||||
bgcolor: C.panel,
|
||||
cursor: 'pointer',
|
||||
transition: 'border-color 0.15s',
|
||||
bgcolor: 'rgba(17, 26, 48, 0.5)',
|
||||
transition: 'border-color 0.15s ease',
|
||||
'&:hover': {
|
||||
borderColor: C.dim,
|
||||
'& .logout-icon': { color: C.accent },
|
||||
borderColor: C.accentLight,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
||||
<Box sx={{
|
||||
width: 32, height: 32, borderRadius: '4px',
|
||||
bgcolor: C.borderHl,
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
border: `1px solid ${C.dim}4D`,
|
||||
}}>
|
||||
<Box component="span" sx={{ fontFamily: FONT, fontSize: '12px', fontWeight: 700, color: C.bright }}>A</Box>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.25 }}>
|
||||
<Box
|
||||
sx={{
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: '8px',
|
||||
background: 'linear-gradient(135deg, #1d4ed8 0%, #3b82f6 100%)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontWeight: 700,
|
||||
fontSize: '13px',
|
||||
color: '#ffffff',
|
||||
boxShadow: '0 0 12px rgba(59, 130, 246, 0.4)',
|
||||
}}
|
||||
>
|
||||
A
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<Box component="span" sx={{ fontFamily: FONT, fontSize: '11px', fontWeight: 500, letterSpacing: '0.05em', color: C.bright }}>
|
||||
<Box>
|
||||
<Typography sx={{ fontFamily: FONT_SANS, fontSize: '12px', fontWeight: 600, color: C.bright, lineHeight: 1.2 }}>
|
||||
Admin User
|
||||
</Box>
|
||||
<Box component="span" sx={{ fontFamily: FONT, fontSize: '9px', letterSpacing: '0.15em', textTransform: 'uppercase', color: C.dim }}>
|
||||
</Typography>
|
||||
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '9px', fontWeight: 600, color: C.purple400, letterSpacing: '0.04em' }}>
|
||||
SUPER_ADMIN
|
||||
</Box>
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box className="logout-icon" sx={{ color: C.dim, display: 'flex', transition: 'color 0.15s' }}>
|
||||
<svg width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="square" strokeWidth="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" /></svg>
|
||||
|
||||
<Box
|
||||
onClick={() => void handleLogout()}
|
||||
title="Sign Out"
|
||||
sx={{
|
||||
p: 0.75,
|
||||
borderRadius: '8px',
|
||||
color: C.dim,
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
transition: 'all 0.15s ease',
|
||||
'&:hover': {
|
||||
color: C.red400,
|
||||
bgcolor: 'rgba(239, 68, 68, 0.12)',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<svg width="18" height="18" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.8" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
|
||||
</svg>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue