mirror of
https://github.com/certctl-io/certctl.git
synced 2026-08-25 15:51:24 +02:00
Add SHA-256 API key authentication with constant-time comparison, configurable token bucket rate limiter, CORS origin allowlist middleware, and React auth context with login page. Auth info endpoint bootstraps GUI without credentials. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
77 lines
4.1 KiB
TypeScript
77 lines
4.1 KiB
TypeScript
import { NavLink, Outlet } from 'react-router-dom';
|
|
import { useAuth } from './AuthProvider';
|
|
|
|
const nav = [
|
|
{ to: '/', label: 'Dashboard', icon: 'M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-4 0h4' },
|
|
{ to: '/certificates', label: 'Certificates', icon: 'M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z' },
|
|
{ to: '/agents', label: 'Agents', icon: 'M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2' },
|
|
{ to: '/jobs', label: 'Jobs', icon: 'M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15' },
|
|
{ to: '/notifications', label: 'Notifications', icon: 'M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9' },
|
|
{ to: '/policies', label: 'Policies', icon: 'M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4' },
|
|
{ to: '/issuers', label: 'Issuers', icon: 'M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z' },
|
|
{ to: '/targets', label: 'Targets', icon: 'M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10' },
|
|
{ to: '/audit', label: 'Audit Trail', icon: 'M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z' },
|
|
];
|
|
|
|
function Icon({ d }: { d: string }) {
|
|
return (
|
|
<svg className="w-5 h-5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
<path strokeLinecap="round" strokeLinejoin="round" d={d} />
|
|
</svg>
|
|
);
|
|
}
|
|
|
|
export default function Layout() {
|
|
const { authRequired, logout } = useAuth();
|
|
|
|
return (
|
|
<div className="flex h-screen overflow-hidden">
|
|
{/* Sidebar */}
|
|
<aside className="w-64 bg-slate-800 border-r border-slate-700 flex flex-col">
|
|
<div className="p-6 border-b border-slate-700">
|
|
<h1 className="text-xl font-bold text-blue-400">certctl</h1>
|
|
<p className="text-xs text-slate-400 uppercase tracking-wider mt-1">Certificate Control Plane</p>
|
|
</div>
|
|
<nav className="flex-1 p-4 space-y-1 overflow-y-auto">
|
|
{nav.map(item => (
|
|
<NavLink
|
|
key={item.to}
|
|
to={item.to}
|
|
end={item.to === '/'}
|
|
className={({ isActive }) =>
|
|
`flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm transition-colors ${
|
|
isActive
|
|
? 'bg-blue-600 text-white'
|
|
: 'text-slate-400 hover:bg-slate-700 hover:text-slate-200'
|
|
}`
|
|
}
|
|
>
|
|
<Icon d={item.icon} />
|
|
{item.label}
|
|
</NavLink>
|
|
))}
|
|
</nav>
|
|
<div className="p-4 border-t border-slate-700 flex items-center justify-between">
|
|
<span className="text-xs text-slate-500">certctl v1.0-dev</span>
|
|
{authRequired && (
|
|
<button
|
|
onClick={logout}
|
|
className="text-xs text-slate-500 hover:text-slate-300 transition-colors"
|
|
title="Sign out"
|
|
>
|
|
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9" />
|
|
</svg>
|
|
</button>
|
|
)}
|
|
</div>
|
|
</aside>
|
|
|
|
{/* Main content */}
|
|
<main className="flex-1 flex flex-col overflow-hidden">
|
|
<Outlet />
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|