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