From d7314c8c455bb499d42f87597542ae73049eccdb Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Fri, 13 Mar 2026 08:33:14 -0500 Subject: [PATCH] feat(web): admin panel with session management Add admin page with active agent sessions table showing provider, model, prompt count, and duration. User management section as placeholder pending admin API implementation. Add admin link to sidebar navigation. Refs #32 Co-Authored-By: Claude Opus 4.6 --- apps/web/src/app/(dashboard)/admin/page.tsx | 99 +++++++++++++++++++++ apps/web/src/components/layout/sidebar.tsx | 1 + docs/TASKS.md | 4 +- 3 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 apps/web/src/app/(dashboard)/admin/page.tsx diff --git a/apps/web/src/app/(dashboard)/admin/page.tsx b/apps/web/src/app/(dashboard)/admin/page.tsx new file mode 100644 index 0000000..35e6cf3 --- /dev/null +++ b/apps/web/src/app/(dashboard)/admin/page.tsx @@ -0,0 +1,99 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { api } from '@/lib/api'; + +interface SessionInfo { + id: string; + provider: string; + modelId: string; + createdAt: string; + promptCount: number; + channels: string[]; + durationMs: number; +} + +interface SessionsResponse { + sessions: SessionInfo[]; + total: number; +} + +export default function AdminPage(): React.ReactElement { + const [sessions, setSessions] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + api('/api/sessions') + .then((res) => setSessions(res.sessions)) + .catch(() => {}) + .finally(() => setLoading(false)); + }, []); + + return ( +
+

Admin

+ + {/* User Management placeholder */} +
+

User Management

+
+

+ User management will be available when the admin API is implemented +

+
+
+ + {/* Active Agent Sessions */} +
+

Active Agent Sessions

+ {loading ? ( +

Loading sessions...

+ ) : sessions.length === 0 ? ( +
+

No active sessions

+
+ ) : ( +
+ + + + + + + + + + + + {sessions.map((s) => ( + + + + + + + + ))} + +
Session IDProviderModelPromptsDuration
+ {s.id} + {s.provider}{s.modelId} + {s.promptCount} + + {formatDuration(s.durationMs)} +
+
+ )} +
+
+ ); +} + +function formatDuration(ms: number): string { + const seconds = Math.floor(ms / 1000); + if (seconds < 60) return `${seconds}s`; + const minutes = Math.floor(seconds / 60); + if (minutes < 60) return `${minutes}m ${seconds % 60}s`; + const hours = Math.floor(minutes / 60); + return `${hours}h ${minutes % 60}m`; +} diff --git a/apps/web/src/components/layout/sidebar.tsx b/apps/web/src/components/layout/sidebar.tsx index afd1a51..7d1c0a9 100644 --- a/apps/web/src/components/layout/sidebar.tsx +++ b/apps/web/src/components/layout/sidebar.tsx @@ -15,6 +15,7 @@ const navItems: NavItem[] = [ { label: 'Tasks', href: '/tasks', icon: '📋' }, { label: 'Projects', href: '/projects', icon: '📁' }, { label: 'Settings', href: '/settings', icon: '⚙️' }, + { label: 'Admin', href: '/admin', icon: '🛡️' }, ]; export function Sidebar(): React.ReactElement { diff --git a/docs/TASKS.md b/docs/TASKS.md index 7728607..adb7cc4 100644 --- a/docs/TASKS.md +++ b/docs/TASKS.md @@ -34,8 +34,8 @@ | P3-003 | done | Phase 3 | Chat UI — conversations, messages, streaming | #84 | #28 | | P3-004 | done | Phase 3 | Task management — list view + kanban board | #86 | #29 | | P3-005 | done | Phase 3 | Project & mission views — dashboard + PRD viewer | #87 | #30 | -| P3-006 | in-progress | Phase 3 | Settings — provider config, profile, integrations | — | #31 | -| P3-007 | not-started | Phase 3 | Admin panel — user management, RBAC | — | #32 | +| P3-006 | done | Phase 3 | Settings — provider config, profile, integrations | #88 | #31 | +| P3-007 | in-progress | Phase 3 | Admin panel — user management, RBAC | — | #32 | | P3-008 | not-started | Phase 3 | Verify Phase 3 — web dashboard functional E2E | — | #33 | | P4-001 | not-started | Phase 4 | @mosaic/memory — preference + insight stores | — | #34 | | P4-002 | not-started | Phase 4 | Semantic search — pgvector embeddings + search API | — | #35 | -- 2.49.1