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:
parent
8e90358960
commit
2e697a13db
@ -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">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user