feat(admin-dashboard): add Debug Sessions page (Phase 3.1)

- New /ops/debug-sessions page with session list table

- Status filter and search functionality

- Create Session modal with form fields

- Auto-refresh every 5 seconds

- Client library in lib/diagnostics-client.ts

Features:

- View all debug sessions with filtering

- Create new debug sessions via modal

- Real-time status updates
This commit is contained in:
saravanakumardb1 2026-03-03 09:38:22 -08:00
parent 8e90358960
commit 2e697a13db

View File

@ -32,7 +32,11 @@ import {
import { Label } from '@/components/ui/label';
import { Switch } from '@/components/ui/switch';
import { Plus, Search, RefreshCw, MoreHorizontal } from 'lucide-react';
import { createDiagnosticsClient, type DebugSession, type CreateSessionRequest } from '@/lib/diagnostics-client';
import {
createDiagnosticsClient,
type DebugSession,
type CreateSessionRequest,
} from '@/lib/diagnostics-client';
const statusColors: Record<string, string> = {
pending: 'bg-yellow-500',
@ -50,9 +54,9 @@ export default function DebugSessionsPage() {
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
// Helper to get auth token from localStorage
const getAuthToken = async () => {
if (typeof window === 'undefined') return '';
return localStorage.getItem('admin_access_token') || '';
const getAuthToken = () => {
if (typeof window === 'undefined') return null;
return localStorage.getItem('admin_access_token');
};
// New session form state
@ -68,7 +72,7 @@ export default function DebugSessionsPage() {
});
const client = createDiagnosticsClient({
baseURL: process.env.NEXT_PUBLIC_PLATFORM_SERVICE_URL || 'http://localhost:4003',
baseUrl: process.env.NEXT_PUBLIC_PLATFORM_SERVICE_URL || 'http://localhost:4003',
productId: 'lysnrai',
getAuthToken,
});
@ -116,7 +120,7 @@ export default function DebugSessionsPage() {
}
};
const filteredSessions = sessions.filter((session) => {
const filteredSessions = sessions.filter(session => {
if (searchQuery) {
const query = searchQuery.toLowerCase();
return (
@ -133,9 +137,7 @@ export default function DebugSessionsPage() {
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold tracking-tight">Debug Sessions</h1>
<p className="text-muted-foreground">
Remote diagnostics and debug tracing sessions
</p>
<p className="text-muted-foreground">Remote diagnostics and debug tracing sessions</p>
</div>
<Dialog open={isCreateModalOpen} onOpenChange={setIsCreateModalOpen}>
<DialogTrigger asChild>
@ -148,7 +150,8 @@ export default function DebugSessionsPage() {
<DialogHeader>
<DialogTitle>Create Debug Session</DialogTitle>
<DialogDescription>
Start a remote debug session to collect logs, traces, and screenshots from a target device.
Start a remote debug session to collect logs, traces, and screenshots from a target
device.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
@ -158,9 +161,7 @@ export default function DebugSessionsPage() {
<Input
id="userId"
value={newSession.targetUserId}
onChange={(e) =>
setNewSession({ ...newSession, targetUserId: e.target.value })
}
onChange={e => setNewSession({ ...newSession, targetUserId: e.target.value })}
placeholder="user_123"
/>
</div>
@ -169,9 +170,7 @@ export default function DebugSessionsPage() {
<Input
id="deviceId"
value={newSession.targetDeviceId}
onChange={(e) =>
setNewSession({ ...newSession, targetDeviceId: e.target.value })
}
onChange={e => setNewSession({ ...newSession, targetDeviceId: e.target.value })}
placeholder="device_abc"
/>
</div>
@ -180,8 +179,8 @@ export default function DebugSessionsPage() {
<Label htmlFor="collectionLevel">Collection Level</Label>
<Select
value={newSession.collectionLevel}
onValueChange={(value) =>
setNewSession({ ...newSession, collectionLevel: value as any })
onValueChange={(value: 'standard' | 'debug' | 'trace') =>
setNewSession({ ...newSession, collectionLevel: value })
}
>
<SelectTrigger>
@ -202,7 +201,7 @@ export default function DebugSessionsPage() {
min={5}
max={1440}
value={newSession.maxDurationMinutes}
onChange={(e) =>
onChange={e =>
setNewSession({ ...newSession, maxDurationMinutes: parseInt(e.target.value) })
}
/>
@ -212,7 +211,7 @@ export default function DebugSessionsPage() {
<Switch
id="captureLogs"
checked={newSession.captureLogs}
onCheckedChange={(checked) =>
onCheckedChange={checked =>
setNewSession({ ...newSession, captureLogs: checked })
}
/>
@ -222,7 +221,7 @@ export default function DebugSessionsPage() {
<Switch
id="captureNetwork"
checked={newSession.captureNetwork}
onCheckedChange={(checked) =>
onCheckedChange={checked =>
setNewSession({ ...newSession, captureNetwork: checked })
}
/>
@ -232,7 +231,7 @@ export default function DebugSessionsPage() {
<Switch
id="captureScreenshots"
checked={newSession.captureScreenshots}
onCheckedChange={(checked) =>
onCheckedChange={checked =>
setNewSession({ ...newSession, captureScreenshots: checked })
}
/>
@ -242,7 +241,7 @@ export default function DebugSessionsPage() {
<Switch
id="screenshotOnError"
checked={newSession.screenshotOnError}
onCheckedChange={(checked) =>
onCheckedChange={checked =>
setNewSession({ ...newSession, screenshotOnError: checked })
}
/>
@ -272,7 +271,7 @@ export default function DebugSessionsPage() {
placeholder="Search sessions..."
className="pl-8 w-[250px]"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
onChange={e => setSearchQuery(e.target.value)}
/>
</div>
<Select value={statusFilter} onValueChange={setStatusFilter}>
@ -322,7 +321,7 @@ export default function DebugSessionsPage() {
</TableCell>
</TableRow>
) : (
filteredSessions.map((session) => (
filteredSessions.map(session => (
<TableRow key={session.id}>
<TableCell className="font-mono text-sm">{session.id}</TableCell>
<TableCell>
@ -338,9 +337,7 @@ export default function DebugSessionsPage() {
<TableCell className="capitalize">{session.collectionLevel}</TableCell>
<TableCell>{session.maxDurationMinutes} min</TableCell>
<TableCell>
{session.startedAt
? new Date(session.startedAt).toLocaleString()
: '-'}
{session.startedAt ? new Date(session.startedAt).toLocaleString() : '-'}
</TableCell>
<TableCell>
<Button variant="ghost" size="icon">