diff --git a/ui/src/components/layout/AppSidebar.module.css b/ui/src/components/layout/AppSidebar.module.css
index eba3e514..0261d18c 100644
--- a/ui/src/components/layout/AppSidebar.module.css
+++ b/ui/src/components/layout/AppSidebar.module.css
@@ -209,6 +209,44 @@
text-align: center;
}
+/* ─── Admin Sub-Menu ─── */
+.adminChevron {
+ margin-left: 6px;
+ font-size: 8px;
+ color: var(--text-muted);
+}
+
+.adminSubMenu {
+ display: flex;
+ flex-direction: column;
+}
+
+.adminSubItem {
+ display: block;
+ padding: 6px 16px 6px 42px;
+ font-size: 12px;
+ color: var(--text-muted);
+ text-decoration: none;
+ transition: all 0.1s;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.adminSubItem:hover {
+ background: var(--bg-hover);
+ color: var(--text-primary);
+}
+
+.adminSubItemActive {
+ color: var(--amber);
+ background: var(--amber-glow);
+}
+
+.sidebarCollapsed .adminSubMenu {
+ display: none;
+}
+
/* ─── Responsive ─── */
@media (max-width: 1024px) {
.sidebar {
@@ -242,4 +280,8 @@
.sidebar .bottomLabel {
display: none;
}
+
+ .sidebar .adminSubMenu {
+ display: none;
+ }
}
diff --git a/ui/src/components/layout/AppSidebar.tsx b/ui/src/components/layout/AppSidebar.tsx
index 1ebfa9f8..6426004a 100644
--- a/ui/src/components/layout/AppSidebar.tsx
+++ b/ui/src/components/layout/AppSidebar.tsx
@@ -1,5 +1,5 @@
import { useMemo, useState } from 'react';
-import { NavLink, useParams } from 'react-router';
+import { NavLink, useParams, useLocation } from 'react-router';
import { useAgents } from '../../api/queries/agents';
import { useAuthStore } from '../../auth/auth-store';
import type { AgentInstance } from '../../api/types';
@@ -112,18 +112,73 @@ export function AppSidebar({ collapsed }: AppSidebarProps) {
{/* Bottom: Admin */}
{roles.includes('ADMIN') && (
-
- `${styles.bottomItem} ${isActive ? styles.bottomItemActive : ''}`
- }
- title="Admin"
- >
- ⚙
- Admin
-
+
)}
);
}
+
+const ADMIN_LINKS = [
+ { to: '/admin/database', label: 'Database' },
+ { to: '/admin/opensearch', label: 'OpenSearch' },
+ { to: '/admin/audit', label: 'Audit Log' },
+ { to: '/admin/oidc', label: 'OIDC' },
+];
+
+function AdminSubMenu({ collapsed: sidebarCollapsed }: { collapsed: boolean }) {
+ const location = useLocation();
+ const isAdminActive = location.pathname.startsWith('/admin');
+
+ const [open, setOpen] = useState(() => {
+ try {
+ return localStorage.getItem('cameleer-admin-sidebar-open') === 'true';
+ } catch {
+ return false;
+ }
+ });
+
+ function toggle() {
+ const next = !open;
+ setOpen(next);
+ try {
+ localStorage.setItem('cameleer-admin-sidebar-open', String(next));
+ } catch { /* ignore */ }
+ }
+
+ return (
+ <>
+
+ {open && !sidebarCollapsed && (
+
+ {ADMIN_LINKS.map((link) => (
+
+ `${styles.adminSubItem} ${isActive ? styles.adminSubItemActive : ''}`
+ }
+ >
+ {link.label}
+
+ ))}
+
+ )}
+ >
+ );
+}
diff --git a/ui/src/router.tsx b/ui/src/router.tsx
index 406bbbcb..e808603f 100644
--- a/ui/src/router.tsx
+++ b/ui/src/router.tsx
@@ -10,6 +10,9 @@ import { RoutePage } from './pages/routes/RoutePage';
import { AppScopedView } from './pages/dashboard/AppScopedView';
const SwaggerPage = lazy(() => import('./pages/swagger/SwaggerPage').then(m => ({ default: m.SwaggerPage })));
+const DatabaseAdminPage = lazy(() => import('./pages/admin/DatabaseAdminPage').then(m => ({ default: m.DatabaseAdminPage })));
+const OpenSearchAdminPage = lazy(() => import('./pages/admin/OpenSearchAdminPage').then(m => ({ default: m.OpenSearchAdminPage })));
+const AuditLogPage = lazy(() => import('./pages/admin/AuditLogPage').then(m => ({ default: m.AuditLogPage })));
export const router = createBrowserRouter([
{
@@ -30,6 +33,10 @@ export const router = createBrowserRouter([
{ path: 'executions', element: },
{ path: 'apps/:group', element: },
{ path: 'apps/:group/routes/:routeId', element: },
+ { path: 'admin', element: },
+ { path: 'admin/database', element: },
+ { path: 'admin/opensearch', element: },
+ { path: 'admin/audit', element: },
{ path: 'admin/oidc', element: },
{ path: 'swagger', element: },
],