639 lines
23 KiB
TypeScript
639 lines
23 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useEffect } from 'react'
|
|
import { useMissionControl } from '@/store'
|
|
import { useNavigateToPanel } from '@/lib/navigation'
|
|
|
|
interface NavItem {
|
|
id: string
|
|
label: string
|
|
icon: React.ReactNode
|
|
priority: boolean // Show in mobile bottom bar
|
|
requiresGateway?: boolean
|
|
}
|
|
|
|
interface NavGroup {
|
|
id: string
|
|
label?: string // undefined = no header (core group)
|
|
items: NavItem[]
|
|
}
|
|
|
|
const navGroups: NavGroup[] = [
|
|
{
|
|
id: 'core',
|
|
items: [
|
|
{ id: 'overview', label: 'Overview', icon: <OverviewIcon />, priority: true },
|
|
{ id: 'agents', label: 'Agents', icon: <AgentsIcon />, priority: true, requiresGateway: true },
|
|
{ id: 'tasks', label: 'Tasks', icon: <TasksIcon />, priority: true },
|
|
{ id: 'sessions', label: 'Sessions', icon: <SessionsIcon />, priority: false },
|
|
{ id: 'office', label: 'Office', icon: <OfficeIcon />, priority: false },
|
|
],
|
|
},
|
|
{
|
|
id: 'observe',
|
|
label: 'OBSERVE',
|
|
items: [
|
|
{ id: 'activity', label: 'Activity', icon: <ActivityIcon />, priority: true },
|
|
{ id: 'logs', label: 'Logs', icon: <LogsIcon />, priority: false },
|
|
{ id: 'tokens', label: 'Tokens', icon: <TokensIcon />, priority: false },
|
|
{ id: 'agent-costs', label: 'Agent Costs', icon: <AgentCostsIcon />, priority: false },
|
|
{ id: 'memory', label: 'Memory', icon: <MemoryIcon />, priority: false },
|
|
],
|
|
},
|
|
{
|
|
id: 'automate',
|
|
label: 'AUTOMATE',
|
|
items: [
|
|
{ id: 'cron', label: 'Cron', icon: <CronIcon />, priority: false },
|
|
{ id: 'spawn', label: 'Spawn', icon: <SpawnIcon />, priority: false, requiresGateway: true },
|
|
{ id: 'webhooks', label: 'Webhooks', icon: <WebhookIcon />, priority: false },
|
|
{ id: 'alerts', label: 'Alerts', icon: <AlertIcon />, priority: false },
|
|
{ id: 'github', label: 'GitHub', icon: <GitHubIcon />, priority: false },
|
|
],
|
|
},
|
|
{
|
|
id: 'admin',
|
|
label: 'ADMIN',
|
|
items: [
|
|
{ id: 'users', label: 'Users', icon: <UsersIcon />, priority: false },
|
|
{ id: 'audit', label: 'Audit', icon: <AuditIcon />, priority: false },
|
|
{ id: 'history', label: 'History', icon: <HistoryIcon />, priority: false },
|
|
{ id: 'gateways', label: 'Gateways', icon: <GatewaysIcon />, priority: false },
|
|
{ id: 'gateway-config', label: 'Config', icon: <GatewayConfigIcon />, priority: false, requiresGateway: true },
|
|
{ id: 'integrations', label: 'Integrations', icon: <IntegrationsIcon />, priority: false },
|
|
{ id: 'super-admin', label: 'Super Admin', icon: <SuperAdminIcon />, priority: false },
|
|
{ id: 'settings', label: 'Settings', icon: <SettingsIcon />, priority: false },
|
|
],
|
|
},
|
|
]
|
|
|
|
// Flat list for mobile bar
|
|
const allNavItems = navGroups.flatMap(g => g.items)
|
|
|
|
export function NavRail() {
|
|
const { activeTab, connection, dashboardMode, sidebarExpanded, collapsedGroups, toggleSidebar, toggleGroup } = useMissionControl()
|
|
const navigateToPanel = useNavigateToPanel()
|
|
const isLocal = dashboardMode === 'local'
|
|
|
|
// Keyboard shortcut: [ to toggle sidebar
|
|
useEffect(() => {
|
|
function handleKey(e: KeyboardEvent) {
|
|
if (e.key === '[' && !(e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement || (e.target as HTMLElement)?.isContentEditable)) {
|
|
e.preventDefault()
|
|
toggleSidebar()
|
|
}
|
|
}
|
|
window.addEventListener('keydown', handleKey)
|
|
return () => window.removeEventListener('keydown', handleKey)
|
|
}, [toggleSidebar])
|
|
|
|
return (
|
|
<>
|
|
{/* Desktop: Grouped sidebar */}
|
|
<nav
|
|
role="navigation"
|
|
aria-label="Main navigation"
|
|
className={`hidden md:flex flex-col bg-card border-r border-border shrink-0 transition-all duration-200 ease-in-out ${
|
|
sidebarExpanded ? 'w-[220px]' : 'w-14'
|
|
}`}
|
|
>
|
|
{/* Header: Logo + toggle */}
|
|
<div className={`flex items-center shrink-0 ${sidebarExpanded ? 'px-3 py-3 gap-2.5' : 'flex-col py-3 gap-2'}`}>
|
|
<div className="w-9 h-9 rounded-lg bg-primary flex items-center justify-center shrink-0">
|
|
<span className="text-primary-foreground font-bold text-xs">MC</span>
|
|
</div>
|
|
{sidebarExpanded && (
|
|
<span className="text-sm font-semibold text-foreground truncate flex-1">Mission Control</span>
|
|
)}
|
|
<button
|
|
onClick={toggleSidebar}
|
|
title={sidebarExpanded ? 'Collapse sidebar' : 'Expand sidebar'}
|
|
className="w-7 h-7 rounded-md flex items-center justify-center text-muted-foreground hover:text-foreground hover:bg-secondary transition-smooth shrink-0"
|
|
>
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" className="w-4 h-4">
|
|
{sidebarExpanded ? (
|
|
<polyline points="10,3 5,8 10,13" />
|
|
) : (
|
|
<polyline points="6,3 11,8 6,13" />
|
|
)}
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
{/* Nav groups */}
|
|
<div className="flex-1 overflow-y-auto overflow-x-hidden py-1">
|
|
{navGroups.map((group, groupIndex) => (
|
|
<div key={group.id}>
|
|
{/* Divider between groups (not before first) */}
|
|
{groupIndex > 0 && (
|
|
<div className={`my-1.5 border-t border-border ${sidebarExpanded ? 'mx-3' : 'mx-2'}`} />
|
|
)}
|
|
|
|
{/* Group header (expanded mode, only for groups with labels) */}
|
|
{sidebarExpanded && group.label && (
|
|
<button
|
|
onClick={() => toggleGroup(group.id)}
|
|
className="w-full flex items-center justify-between px-3 mt-3 mb-1 group/header"
|
|
>
|
|
<span className="text-[10px] tracking-wider text-muted-foreground/60 font-semibold select-none">
|
|
{group.label}
|
|
</span>
|
|
<svg
|
|
viewBox="0 0 16 16"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
strokeWidth="1.5"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
className={`w-3 h-3 text-muted-foreground/40 group-hover/header:text-muted-foreground transition-transform duration-150 ${
|
|
collapsedGroups.includes(group.id) ? '-rotate-90' : ''
|
|
}`}
|
|
>
|
|
<polyline points="4,6 8,10 12,6" />
|
|
</svg>
|
|
</button>
|
|
)}
|
|
|
|
{/* Group items */}
|
|
<div
|
|
className={`overflow-hidden transition-all duration-150 ease-in-out ${
|
|
sidebarExpanded && collapsedGroups.includes(group.id) ? 'max-h-0 opacity-0' : 'max-h-[500px] opacity-100'
|
|
}`}
|
|
>
|
|
<div className={`flex flex-col ${sidebarExpanded ? 'gap-0.5 px-2' : 'items-center gap-1'}`}>
|
|
{group.items.map((item) => {
|
|
const disabled = isLocal && item.requiresGateway
|
|
return (
|
|
<NavButton
|
|
key={item.id}
|
|
item={item}
|
|
active={activeTab === item.id}
|
|
expanded={sidebarExpanded}
|
|
disabled={disabled}
|
|
onClick={() => { if (!disabled) navigateToPanel(item.id) }}
|
|
/>
|
|
)
|
|
})}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Connection indicator */}
|
|
<div className={`shrink-0 py-3 flex ${sidebarExpanded ? 'px-3 items-center gap-2' : 'flex-col items-center'}`}>
|
|
<div
|
|
className={`w-2.5 h-2.5 rounded-full shrink-0 ${
|
|
isLocal
|
|
? 'bg-blue-500'
|
|
: connection.isConnected ? 'bg-green-500 pulse-dot' : 'bg-red-500'
|
|
}`}
|
|
title={isLocal ? 'Local Mode' : connection.isConnected ? 'Gateway connected' : 'Gateway disconnected'}
|
|
/>
|
|
{sidebarExpanded && (
|
|
<span className="text-xs text-muted-foreground truncate">
|
|
{isLocal ? 'Local Mode' : connection.isConnected ? 'Connected' : 'Disconnected'}
|
|
</span>
|
|
)}
|
|
</div>
|
|
</nav>
|
|
|
|
{/* Mobile: Bottom tab bar */}
|
|
<MobileBottomBar activeTab={activeTab} navigateToPanel={navigateToPanel} />
|
|
</>
|
|
)
|
|
}
|
|
|
|
function NavButton({ item, active, expanded, disabled, onClick }: {
|
|
item: NavItem
|
|
active: boolean
|
|
expanded: boolean
|
|
disabled?: boolean
|
|
onClick: () => void
|
|
}) {
|
|
const disabledClass = disabled ? 'opacity-40 pointer-events-none' : ''
|
|
const tooltipLabel = disabled ? `${item.label} (Requires gateway)` : item.label
|
|
|
|
if (expanded) {
|
|
return (
|
|
<button
|
|
onClick={onClick}
|
|
aria-current={active ? 'page' : undefined}
|
|
aria-disabled={disabled || undefined}
|
|
className={`w-full flex items-center gap-2 px-2 py-1.5 rounded-lg text-left transition-smooth relative ${disabledClass} ${
|
|
active
|
|
? 'bg-primary/15 text-primary'
|
|
: 'text-muted-foreground hover:text-foreground hover:bg-secondary'
|
|
}`}
|
|
>
|
|
{active && (
|
|
<span className="absolute left-0 w-0.5 h-5 bg-primary rounded-r" />
|
|
)}
|
|
<div className="w-5 h-5 shrink-0">{item.icon}</div>
|
|
<span className="text-sm truncate">{item.label}</span>
|
|
</button>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<button
|
|
onClick={onClick}
|
|
title={tooltipLabel}
|
|
aria-current={active ? 'page' : undefined}
|
|
aria-disabled={disabled || undefined}
|
|
className={`w-10 h-10 rounded-lg flex items-center justify-center transition-smooth group relative ${disabledClass} ${
|
|
active
|
|
? 'bg-primary/15 text-primary'
|
|
: 'text-muted-foreground hover:text-foreground hover:bg-secondary'
|
|
}`}
|
|
>
|
|
<div className="w-5 h-5">{item.icon}</div>
|
|
{/* Tooltip */}
|
|
<span className="absolute left-full ml-2 px-2 py-1 text-xs font-medium bg-popover text-popover-foreground border border-border rounded-md opacity-0 group-hover:opacity-100 pointer-events-none whitespace-nowrap z-50 transition-opacity">
|
|
{tooltipLabel}
|
|
</span>
|
|
{/* Active indicator */}
|
|
{active && (
|
|
<span className="absolute left-0 w-0.5 h-5 bg-primary rounded-r" />
|
|
)}
|
|
</button>
|
|
)
|
|
}
|
|
|
|
function MobileBottomBar({ activeTab, navigateToPanel }: {
|
|
activeTab: string
|
|
navigateToPanel: (tab: string) => void
|
|
}) {
|
|
const [sheetOpen, setSheetOpen] = useState(false)
|
|
const priorityItems = allNavItems.filter(i => i.priority)
|
|
const nonPriorityIds = new Set(allNavItems.filter(i => !i.priority).map(i => i.id))
|
|
const moreIsActive = nonPriorityIds.has(activeTab)
|
|
|
|
return (
|
|
<>
|
|
<nav className="md:hidden fixed bottom-0 left-0 right-0 z-50 bg-card/95 backdrop-blur-lg border-t border-border safe-area-bottom">
|
|
<div className="flex items-center justify-around px-1 h-14">
|
|
{priorityItems.map((item) => (
|
|
<button
|
|
key={item.id}
|
|
onClick={() => navigateToPanel(item.id)}
|
|
className={`flex flex-col items-center justify-center gap-0.5 px-2 py-2 rounded-lg transition-smooth min-w-[48px] min-h-[48px] ${
|
|
activeTab === item.id
|
|
? 'text-primary'
|
|
: 'text-muted-foreground'
|
|
}`}
|
|
>
|
|
<div className="w-5 h-5">{item.icon}</div>
|
|
<span className="text-[10px] font-medium truncate">{item.label}</span>
|
|
</button>
|
|
))}
|
|
{/* More button */}
|
|
<button
|
|
onClick={() => setSheetOpen(true)}
|
|
className={`flex flex-col items-center justify-center gap-0.5 px-2 py-2 rounded-lg transition-smooth min-w-[48px] min-h-[48px] relative ${
|
|
moreIsActive ? 'text-primary' : 'text-muted-foreground'
|
|
}`}
|
|
>
|
|
<div className="w-5 h-5">
|
|
<svg viewBox="0 0 16 16" fill="currentColor">
|
|
<circle cx="4" cy="8" r="1.5" />
|
|
<circle cx="8" cy="8" r="1.5" />
|
|
<circle cx="12" cy="8" r="1.5" />
|
|
</svg>
|
|
</div>
|
|
<span className="text-[10px] font-medium">More</span>
|
|
{moreIsActive && (
|
|
<span className="absolute top-1.5 right-2.5 w-1.5 h-1.5 rounded-full bg-primary" />
|
|
)}
|
|
</button>
|
|
</div>
|
|
</nav>
|
|
|
|
{/* Bottom sheet */}
|
|
<MobileBottomSheet
|
|
open={sheetOpen}
|
|
onClose={() => setSheetOpen(false)}
|
|
activeTab={activeTab}
|
|
navigateToPanel={navigateToPanel}
|
|
/>
|
|
</>
|
|
)
|
|
}
|
|
|
|
function MobileBottomSheet({ open, onClose, activeTab, navigateToPanel }: {
|
|
open: boolean
|
|
onClose: () => void
|
|
activeTab: string
|
|
navigateToPanel: (tab: string) => void
|
|
}) {
|
|
// Track mount state for animation
|
|
const [visible, setVisible] = useState(false)
|
|
|
|
useEffect(() => {
|
|
if (open) {
|
|
// Mount first, then animate in on next frame
|
|
requestAnimationFrame(() => {
|
|
requestAnimationFrame(() => setVisible(true))
|
|
})
|
|
} else {
|
|
setVisible(false)
|
|
}
|
|
}, [open])
|
|
|
|
// Handle close with animation
|
|
function handleClose() {
|
|
setVisible(false)
|
|
setTimeout(onClose, 200) // match transition duration
|
|
}
|
|
|
|
if (!open) return null
|
|
|
|
return (
|
|
<div className="md:hidden fixed inset-0 z-[60]">
|
|
{/* Backdrop */}
|
|
<div
|
|
className={`absolute inset-0 bg-black/40 transition-opacity duration-200 ${
|
|
visible ? 'opacity-100' : 'opacity-0'
|
|
}`}
|
|
onClick={handleClose}
|
|
/>
|
|
|
|
{/* Sheet */}
|
|
<div
|
|
className={`absolute bottom-0 left-0 right-0 bg-card rounded-t-2xl max-h-[70vh] overflow-y-auto safe-area-bottom transition-transform duration-200 ease-out ${
|
|
visible ? 'translate-y-0' : 'translate-y-full'
|
|
}`}
|
|
>
|
|
{/* Drag handle */}
|
|
<div className="flex justify-center pt-3 pb-2">
|
|
<div className="w-10 h-1 rounded-full bg-muted-foreground/30" />
|
|
</div>
|
|
|
|
{/* Grouped navigation */}
|
|
<div className="px-4 pb-6">
|
|
{navGroups.map((group, groupIndex) => (
|
|
<div key={group.id}>
|
|
{groupIndex > 0 && <div className="my-3 border-t border-border" />}
|
|
|
|
{/* Group header */}
|
|
<div className="px-1 pt-1 pb-2">
|
|
<span className="text-[10px] tracking-wider text-muted-foreground/60 font-semibold">
|
|
{group.label || 'CORE'}
|
|
</span>
|
|
</div>
|
|
|
|
{/* 2-column grid */}
|
|
<div className="grid grid-cols-2 gap-1.5">
|
|
{group.items.map((item) => (
|
|
<button
|
|
key={item.id}
|
|
onClick={() => {
|
|
navigateToPanel(item.id)
|
|
handleClose()
|
|
}}
|
|
className={`flex items-center gap-2.5 px-3 min-h-[48px] rounded-xl transition-smooth ${
|
|
activeTab === item.id
|
|
? 'bg-primary/15 text-primary'
|
|
: 'text-foreground hover:bg-secondary'
|
|
}`}
|
|
>
|
|
<div className="w-5 h-5 shrink-0">{item.icon}</div>
|
|
<span className="text-xs font-medium truncate">{item.label}</span>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// SVG Icons (16x16 viewbox, stroke-based)
|
|
function OverviewIcon() {
|
|
return (
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
<rect x="1" y="1" width="6" height="6" rx="1" />
|
|
<rect x="9" y="1" width="6" height="6" rx="1" />
|
|
<rect x="1" y="9" width="6" height="6" rx="1" />
|
|
<rect x="9" y="9" width="6" height="6" rx="1" />
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
function AgentsIcon() {
|
|
return (
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
<circle cx="8" cy="5" r="3" />
|
|
<path d="M2 14c0-3.3 2.7-6 6-6s6 2.7 6 6" />
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
function TasksIcon() {
|
|
return (
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
<rect x="2" y="1" width="12" height="14" rx="1.5" />
|
|
<path d="M5 5h6M5 8h6M5 11h3" />
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
function SessionsIcon() {
|
|
return (
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
<path d="M2 3h12v9H2zM5 12v2M11 12v2M4 14h8" />
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
function ActivityIcon() {
|
|
return (
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
<polyline points="1,8 4,8 6,3 8,13 10,6 12,8 15,8" />
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
function LogsIcon() {
|
|
return (
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
<path d="M3 2h10a1 1 0 011 1v10a1 1 0 01-1 1H3a1 1 0 01-1-1V3a1 1 0 011-1z" />
|
|
<path d="M5 5h6M5 8h6M5 11h3" />
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
function SpawnIcon() {
|
|
return (
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
<path d="M8 2v12M8 2l-3 3M8 2l3 3" />
|
|
<path d="M3 10h10" />
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
function CronIcon() {
|
|
return (
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
<circle cx="8" cy="8" r="6.5" />
|
|
<path d="M8 4v4l2.5 2.5" />
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
function MemoryIcon() {
|
|
return (
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
<ellipse cx="8" cy="8" rx="6" ry="3" />
|
|
<path d="M2 8v3c0 1.7 2.7 3 6 3s6-1.3 6-3V8" />
|
|
<path d="M2 5v3c0 1.7 2.7 3 6 3s6-1.3 6-3V5" />
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
function TokensIcon() {
|
|
return (
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
<circle cx="8" cy="8" r="6.5" />
|
|
<path d="M8 4v8M5.5 6h5a1.5 1.5 0 010 3H6" />
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
function UsersIcon() {
|
|
return (
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
<circle cx="6" cy="5" r="2.5" />
|
|
<path d="M1.5 14c0-2.5 2-4.5 4.5-4.5s4.5 2 4.5 4.5" />
|
|
<circle cx="11.5" cy="5.5" r="2" />
|
|
<path d="M14.5 14c0-2 -1.5-3.5-3-3.5" />
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
function HistoryIcon() {
|
|
return (
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
<path d="M1 8a7 7 0 1114 0A7 7 0 011 8z" />
|
|
<path d="M8 4v4l3 2" />
|
|
<path d="M1 8h2" />
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
function AuditIcon() {
|
|
return (
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
<path d="M8 1L2 4v4c0 4 2.5 6 6 7 3.5-1 6-3 6-7V4L8 1z" />
|
|
<path d="M6 8l2 2 3-3" />
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
function WebhookIcon() {
|
|
return (
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
<circle cx="5" cy="5" r="2.5" />
|
|
<circle cx="11" cy="5" r="2.5" />
|
|
<circle cx="8" cy="12" r="2.5" />
|
|
<path d="M5 7.5v1c0 1.1.4 2 1.2 2.7" />
|
|
<path d="M11 7.5v1c0 1.1-.4 2-1.2 2.7" />
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
function GatewayConfigIcon() {
|
|
return (
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
<rect x="2" y="3" width="12" height="10" rx="1.5" />
|
|
<circle cx="5.5" cy="8" r="1" />
|
|
<circle cx="10.5" cy="8" r="1" />
|
|
<path d="M6.5 8h3" />
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
function GatewaysIcon() {
|
|
return (
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
<rect x="1" y="2" width="14" height="5" rx="1" />
|
|
<rect x="1" y="9" width="14" height="5" rx="1" />
|
|
<circle cx="4" cy="4.5" r="0.75" fill="currentColor" stroke="none" />
|
|
<circle cx="4" cy="11.5" r="0.75" fill="currentColor" stroke="none" />
|
|
<path d="M7 4.5h5M7 11.5h5" />
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
function AlertIcon() {
|
|
return (
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
<path d="M6 13h4M3.5 10c0-1-1-2-1-4a5.5 5.5 0 0111 0c0 2-1 3-1 4H3.5z" />
|
|
<path d="M8 1v1" />
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
function SuperAdminIcon() {
|
|
return (
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
<path d="M8 1.5l1.4 2.8 3.1.5-2.2 2.2.5 3.1L8 8.8 5.2 10l.5-3.1L3.5 4.8l3.1-.5L8 1.5z" />
|
|
<path d="M2 13.5h12" />
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
function IntegrationsIcon() {
|
|
return (
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
<circle cx="4" cy="4" r="2" />
|
|
<circle cx="12" cy="4" r="2" />
|
|
<circle cx="4" cy="12" r="2" />
|
|
<circle cx="12" cy="12" r="2" />
|
|
<path d="M6 4h4M4 6v4M12 6v4M6 12h4" />
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
function AgentCostsIcon() {
|
|
return (
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
<circle cx="6" cy="5" r="3" />
|
|
<path d="M1 14c0-2.8 2.2-5 5-5" />
|
|
<circle cx="12" cy="10" r="3.5" />
|
|
<path d="M12 8.5v3M10.8 10h2.4" />
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
function GitHubIcon() {
|
|
return (
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
<path d="M6 12.5c-3 1-3-1.5-4-2m8 4v-2.2a2.1 2.1 0 00-.6-1.6c2-.2 4.1-1 4.1-4.5a3.5 3.5 0 00-1-2.4 3.2 3.2 0 00-.1-2.4s-.8-.2-2.5 1a8.7 8.7 0 00-4.6 0C3.7 3.4 2.9 3.6 2.9 3.6a3.2 3.2 0 00-.1 2.4 3.5 3.5 0 00-1 2.4c0 3.5 2.1 4.3 4.1 4.5a2.1 2.1 0 00-.6 1.6v2.2" />
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
function SettingsIcon() {
|
|
return (
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
<circle cx="8" cy="8" r="2" />
|
|
<path d="M8 1v2M8 13v2M1 8h2M13 8h2M3.05 3.05l1.4 1.4M11.55 11.55l1.4 1.4M3.05 12.95l1.4-1.4M11.55 4.45l1.4-1.4" />
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
function OfficeIcon() {
|
|
return (
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
<rect x="2" y="4" width="12" height="10" rx="1" />
|
|
<path d="M2 7h12" />
|
|
<path d="M5 1v3M11 1v3" />
|
|
<rect x="4" y="9" width="3" height="3" rx="0.5" />
|
|
<rect x="9" y="9" width="3" height="3" rx="0.5" />
|
|
</svg>
|
|
)
|
|
}
|