2026-06-18 20:08:30 +00:00
|
|
|
import { useEffect, useState } from 'react'
|
2026-06-18 16:15:34 +00:00
|
|
|
import { useLocation, Link } from 'react-router-dom'
|
2026-06-18 08:14:00 -04:00
|
|
|
import {
|
|
|
|
|
LayoutGrid,
|
|
|
|
|
Server,
|
|
|
|
|
Bookmark,
|
|
|
|
|
Terminal,
|
2026-06-19 11:40:59 +00:00
|
|
|
Waypoints,
|
2026-06-19 11:56:04 +00:00
|
|
|
FolderOpen,
|
2026-06-19 12:28:30 +00:00
|
|
|
Box,
|
2026-06-19 15:25:10 +00:00
|
|
|
MonitorSmartphone,
|
2026-06-18 08:14:00 -04:00
|
|
|
Settings,
|
|
|
|
|
ChevronLeft,
|
|
|
|
|
ChevronRight,
|
|
|
|
|
} from 'lucide-react'
|
2026-06-18 20:08:30 +00:00
|
|
|
import { api, type Integration } from '../lib/api'
|
2026-06-18 08:14:00 -04:00
|
|
|
|
|
|
|
|
interface SidebarProps {
|
|
|
|
|
collapsed: boolean
|
|
|
|
|
onToggle: () => void
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const navItems = [
|
2026-06-18 16:15:34 +00:00
|
|
|
{ icon: LayoutGrid, label: 'Glance', route: '/' },
|
|
|
|
|
{ icon: Server, label: 'Infrastructure', route: '/infrastructure' },
|
|
|
|
|
{ icon: Bookmark, label: 'BookNest', route: '/booknest' },
|
|
|
|
|
{ icon: Terminal, label: 'Terminal', route: '/terminal' },
|
2026-06-19 11:40:59 +00:00
|
|
|
{ icon: Waypoints, label: 'Tunnels', route: '/tunnels' },
|
2026-06-19 11:56:04 +00:00
|
|
|
{ icon: FolderOpen, label: 'Files', route: '/files' },
|
2026-06-19 12:28:30 +00:00
|
|
|
{ icon: Box, label: 'Containers', route: '/containers' },
|
2026-06-19 15:25:10 +00:00
|
|
|
{ icon: MonitorSmartphone, label: 'Remote Desktop', route: '/remote-desktop' },
|
2026-06-18 16:15:34 +00:00
|
|
|
{ icon: Settings, label: 'Settings', route: '/settings' },
|
2026-06-18 08:14:00 -04:00
|
|
|
]
|
|
|
|
|
|
|
|
|
|
export default function Sidebar({ collapsed, onToggle }: SidebarProps) {
|
2026-06-18 14:45:00 +00:00
|
|
|
const width = collapsed ? 64 : 200
|
2026-06-18 16:15:34 +00:00
|
|
|
const location = useLocation()
|
2026-06-18 20:08:30 +00:00
|
|
|
const [integrations, setIntegrations] = useState<Integration[] | null>(null)
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
api.listIntegrations().then(({ integrations }) => setIntegrations(integrations))
|
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
|
|
const errored = integrations?.filter((i) => i.status === 'error').length ?? 0
|
|
|
|
|
const statusOk = errored === 0
|
|
|
|
|
const statusColor = statusOk ? '#2ECC71' : '#E74C3C'
|
|
|
|
|
const statusLabel = integrations === null ? 'Checking…' : statusOk ? 'All Systems Operational' : `${errored} Issue${errored > 1 ? 's' : ''} Detected`
|
2026-06-18 08:14:00 -04:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<aside
|
2026-06-18 14:53:34 +00:00
|
|
|
className="fixed left-0 top-0 z-50 h-screen overflow-hidden flex flex-col py-6"
|
2026-06-18 08:14:00 -04:00
|
|
|
style={{ width: `${width}px`, backgroundColor: '#0A0B0D' }}
|
|
|
|
|
>
|
2026-06-18 16:02:42 +00:00
|
|
|
{/* Collapse Toggle — floating on the sidebar/content edge, vertically centered */}
|
|
|
|
|
<button
|
|
|
|
|
onClick={onToggle}
|
|
|
|
|
className="fixed top-1/2 z-50 flex items-center justify-center rounded-full cursor-pointer transition-colors"
|
|
|
|
|
style={{
|
|
|
|
|
left: `${width - 11}px`,
|
|
|
|
|
transform: 'translateY(-50%)',
|
|
|
|
|
width: '22px',
|
|
|
|
|
height: '22px',
|
|
|
|
|
border: '1px solid rgba(200,164,52,0.25)',
|
|
|
|
|
backgroundColor: '#15161A',
|
|
|
|
|
color: '#7A7D85',
|
|
|
|
|
boxShadow: '0 0 8px rgba(0,0,0,0.4)',
|
|
|
|
|
}}
|
|
|
|
|
onMouseEnter={(e) => { e.currentTarget.style.color = '#C8A434'; e.currentTarget.style.borderColor = 'rgba(200,164,52,0.5)' }}
|
|
|
|
|
onMouseLeave={(e) => { e.currentTarget.style.color = '#7A7D85'; e.currentTarget.style.borderColor = 'rgba(200,164,52,0.25)' }}
|
|
|
|
|
>
|
|
|
|
|
{collapsed ? <ChevronRight size={12} /> : <ChevronLeft size={12} />}
|
|
|
|
|
</button>
|
|
|
|
|
|
2026-06-18 14:53:34 +00:00
|
|
|
{/* Logo — prominent, centered at top. Blend mode hides the baked-in
|
|
|
|
|
dark background of the source PNG so only the gold arc shows. */}
|
|
|
|
|
<div className="flex flex-col items-center mb-10" style={{ paddingTop: '4px' }}>
|
2026-06-18 08:14:00 -04:00
|
|
|
<img
|
2026-06-18 14:53:34 +00:00
|
|
|
src="/archnest-logo-clean.png"
|
2026-06-18 08:14:00 -04:00
|
|
|
alt="ArchNest"
|
|
|
|
|
style={{
|
2026-06-18 14:53:34 +00:00
|
|
|
width: collapsed ? '56px' : '168px',
|
|
|
|
|
height: 'auto',
|
|
|
|
|
filter: 'drop-shadow(0 0 10px rgba(200,164,52,0.3))',
|
2026-06-18 08:14:00 -04:00
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Nav Items */}
|
2026-06-18 14:53:34 +00:00
|
|
|
<nav className="flex-1 flex flex-col gap-2 w-full" style={{ padding: collapsed ? '0 8px' : '0 12px' }}>
|
2026-06-18 08:14:00 -04:00
|
|
|
{navItems.map((item) => {
|
|
|
|
|
const Icon = item.icon
|
2026-06-18 16:15:34 +00:00
|
|
|
const active = location.pathname === item.route
|
2026-06-18 08:14:00 -04:00
|
|
|
return (
|
2026-06-18 16:15:34 +00:00
|
|
|
<Link
|
2026-06-18 08:14:00 -04:00
|
|
|
key={item.label}
|
2026-06-18 16:15:34 +00:00
|
|
|
to={item.route}
|
2026-06-18 14:53:34 +00:00
|
|
|
className={`relative flex items-center no-underline transition-all duration-200 ${collapsed ? 'justify-center' : ''}`}
|
|
|
|
|
style={{
|
2026-06-18 16:15:34 +00:00
|
|
|
color: active ? '#C8A434' : '#7A7D85',
|
2026-06-18 14:53:34 +00:00
|
|
|
gap: collapsed ? '0' : '12px',
|
|
|
|
|
padding: collapsed ? '12px 0' : '12px 14px',
|
|
|
|
|
borderRadius: '10px',
|
2026-06-18 16:15:34 +00:00
|
|
|
backgroundColor: active ? 'rgba(200,164,52,0.1)' : 'transparent',
|
|
|
|
|
border: active ? '1px solid rgba(200,164,52,0.18)' : '1px solid transparent',
|
|
|
|
|
boxShadow: active ? '0 0 14px rgba(200,164,52,0.06)' : 'none',
|
2026-06-18 14:53:34 +00:00
|
|
|
}}
|
2026-06-18 08:14:00 -04:00
|
|
|
title={collapsed ? item.label : undefined}
|
2026-06-18 16:15:34 +00:00
|
|
|
onMouseEnter={(e) => { if (!active) { e.currentTarget.style.backgroundColor = 'rgba(200,164,52,0.05)'; e.currentTarget.style.color = '#C8A434' } }}
|
|
|
|
|
onMouseLeave={(e) => { if (!active) { e.currentTarget.style.backgroundColor = 'transparent'; e.currentTarget.style.color = '#7A7D85' } }}
|
2026-06-18 08:14:00 -04:00
|
|
|
>
|
2026-06-18 16:15:34 +00:00
|
|
|
{active && (
|
2026-06-18 08:14:00 -04:00
|
|
|
<div
|
2026-06-18 14:53:34 +00:00
|
|
|
className="absolute left-0 top-1/2 -translate-y-1/2"
|
|
|
|
|
style={{ width: '3px', height: '22px', backgroundColor: '#C8A434', borderRadius: '0 3px 3px 0', boxShadow: '0 0 6px rgba(200,164,52,0.5)' }}
|
2026-06-18 08:14:00 -04:00
|
|
|
/>
|
|
|
|
|
)}
|
2026-06-18 16:15:34 +00:00
|
|
|
<Icon className="h-5 w-5 shrink-0" strokeWidth={active ? 2 : 1.5} />
|
2026-06-18 08:14:00 -04:00
|
|
|
{!collapsed && (
|
2026-06-18 14:53:34 +00:00
|
|
|
<span className="truncate leading-tight font-medium" style={{ fontSize: '13px' }}>
|
2026-06-18 08:14:00 -04:00
|
|
|
{item.label}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
2026-06-18 16:15:34 +00:00
|
|
|
</Link>
|
2026-06-18 08:14:00 -04:00
|
|
|
)
|
|
|
|
|
})}
|
|
|
|
|
</nav>
|
|
|
|
|
|
|
|
|
|
{/* System Status — rounded block, fits inside nav with breathing room */}
|
|
|
|
|
<div style={{ width: '100%', padding: '0 12px 16px 12px' }}>
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
border: '1px solid rgba(122, 125, 133, 0.2)',
|
|
|
|
|
borderRadius: '10px',
|
|
|
|
|
padding: '10px 12px',
|
|
|
|
|
backgroundColor: 'rgba(20, 21, 24, 0.5)',
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<div className="flex items-center gap-2">
|
2026-06-18 20:08:30 +00:00
|
|
|
<div style={{ width: '12px', height: '12px', borderRadius: '50%', border: `2px solid ${statusColor}`, flexShrink: 0 }} />
|
2026-06-18 08:14:00 -04:00
|
|
|
{!collapsed && (
|
|
|
|
|
<div>
|
|
|
|
|
<span style={{ fontSize: '9px', color: '#E8E6E0', display: 'block', lineHeight: 1.3, fontWeight: 500 }}>System Status</span>
|
2026-06-18 20:08:30 +00:00
|
|
|
<span style={{ fontSize: '8px', color: statusColor, display: 'block', lineHeight: 1.3 }}>{statusLabel}</span>
|
2026-06-18 08:14:00 -04:00
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</aside>
|
|
|
|
|
)
|
|
|
|
|
}
|