Infrastructure: - Add `mlops` compose profile: MLflow (basic-auth, /mlflow path) + Airflow (LocalExecutor, /airflow path) + airflow-db - infra/mlflow/basic_auth.ini for MLflow auth config - Caddy routes /mlflow* and /airflow* inside existing o.alogins.net block (see agap_git) - Dockerfile.admin: NEXT_PUBLIC_MLFLOW_URL / NEXT_PUBLIC_AIRFLOW_URL build args (default /mlflow, /airflow) Admin panel: - /admin/models: replace MLflow iframe with external link cards - /admin/experiments: replace LinUCB stats with MLOps hub (links to MLflow experiments/models + Airflow DAGs/datasets) - AdminShell: external nav links for MLflow ↗ and Airflow ↗ under MLOps section Docs & planning: - README: new AI stack section (Ollama/LiteLLM/OpenWebUI three-tier, tip generation pipeline, model aliases) - README: Phase 2 expanded with AI infra issues (#86-#93) and granular pipeline breakdown - README: Phase 4 expanded with LLM MLOps items (#94-#97) - CLAUDE.md: AI stack section, updated current phase (M1 shipped / M2 in progress), compose profiles, updated What NOT to do - docs/architecture/overview.md: AI stack section, updated decision flow diagram for Phase 2 LLM pipeline - ADR-0006: updated to reflect external services (path-based, not embedded) - Gitea issues #86-#97 created (M2: AI infra + pipeline; M4: LLM MLOps) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
86 lines
3.0 KiB
TypeScript
86 lines
3.0 KiB
TypeScript
'use client';
|
|
|
|
import Link from 'next/link';
|
|
import { usePathname } from 'next/navigation';
|
|
|
|
const mlflowUrl = process.env.NEXT_PUBLIC_MLFLOW_URL ?? '/mlflow';
|
|
const airflowUrl = process.env.NEXT_PUBLIC_AIRFLOW_URL ?? '/airflow';
|
|
|
|
type NavItem =
|
|
| { href: string; label: string; external?: false }
|
|
| { href: string; label: string; external: true };
|
|
|
|
const NAV: NavItem[] = [
|
|
{ href: '/', label: 'Overview' },
|
|
{ href: '/users', label: 'Users' },
|
|
{ href: '/events', label: 'Events' },
|
|
{ href: '/features', label: 'Features' },
|
|
{ href: '/tips', label: 'Rec log' },
|
|
{ href: '/reward-analytics', label: 'Rewards' },
|
|
{ href: '/experiments', label: 'MLOps' },
|
|
{ href: '/simulations', label: 'Simulations' },
|
|
{ href: '/models', label: 'Models' },
|
|
{ href: '/data-quality', label: 'Data quality' },
|
|
{ href: '/ops', label: 'Ops' },
|
|
{ href: '/sql', label: 'SQL runner' },
|
|
{ href: '/health', label: 'Health' },
|
|
{ href: '/audit', label: 'Audit log' },
|
|
{ href: '/docs', label: 'Docs' },
|
|
];
|
|
|
|
const NAV_EXTERNAL: NavItem[] = [
|
|
{ href: mlflowUrl, label: 'MLflow ↗', external: true },
|
|
{ href: airflowUrl, label: 'Airflow ↗', external: true },
|
|
];
|
|
|
|
export function AdminShell({ children }: { children: React.ReactNode }) {
|
|
const pathname = usePathname();
|
|
return (
|
|
<div className="flex min-h-screen">
|
|
{/* Sidebar */}
|
|
<aside className="w-52 flex-shrink-0 border-r border-gray-800 bg-gray-950 flex flex-col">
|
|
<div className="px-5 py-4 border-b border-gray-800">
|
|
<span className="text-lg font-bold tracking-tight">oO</span>
|
|
<span className="ml-2 text-xs text-gray-500 font-medium uppercase tracking-widest">
|
|
Admin
|
|
</span>
|
|
</div>
|
|
<nav className="flex-1 px-2 py-3 space-y-0.5 overflow-y-auto">
|
|
{NAV.map(({ href, label }) => {
|
|
const active = href === '/' ? pathname === '/' : pathname.startsWith(href);
|
|
return (
|
|
<Link
|
|
key={href}
|
|
href={href}
|
|
className={`flex items-center px-3 py-2 rounded text-sm transition-colors ${
|
|
active
|
|
? 'bg-gray-800 text-white font-medium'
|
|
: 'text-gray-400 hover:text-white hover:bg-gray-900'
|
|
}`}
|
|
>
|
|
{label}
|
|
</Link>
|
|
);
|
|
})}
|
|
<div className="pt-3 pb-1 px-3">
|
|
<span className="text-xs text-gray-600 uppercase tracking-wider font-medium">MLOps</span>
|
|
</div>
|
|
{NAV_EXTERNAL.map(({ href, label }) => (
|
|
<a
|
|
key={href}
|
|
href={href}
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
className="flex items-center px-3 py-2 rounded text-sm text-gray-500 hover:text-white hover:bg-gray-900 transition-colors"
|
|
>
|
|
{label}
|
|
</a>
|
|
))}
|
|
</nav>
|
|
</aside>
|
|
{/* Main content */}
|
|
<main className="flex-1 overflow-auto p-6">{children}</main>
|
|
</div>
|
|
);
|
|
}
|