feat: MLOps external services, AI stack planning, admin MLOps hub
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>
This commit is contained in:
@@ -3,14 +3,21 @@
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
const NAV = [
|
||||
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: 'Experiments' },
|
||||
{ href: '/experiments', label: 'MLOps' },
|
||||
{ href: '/simulations', label: 'Simulations' },
|
||||
{ href: '/models', label: 'Models' },
|
||||
{ href: '/data-quality', label: 'Data quality' },
|
||||
@@ -21,6 +28,11 @@ const NAV = [
|
||||
{ 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 (
|
||||
@@ -33,7 +45,7 @@ export function AdminShell({ children }: { children: React.ReactNode }) {
|
||||
Admin
|
||||
</span>
|
||||
</div>
|
||||
<nav className="flex-1 px-2 py-3 space-y-0.5">
|
||||
<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 (
|
||||
@@ -50,6 +62,20 @@ export function AdminShell({ children }: { children: React.ReactNode }) {
|
||||
</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 */}
|
||||
|
||||
Reference in New Issue
Block a user