From a5d68decdbda808c1d763fe5855a28e540c846ce Mon Sep 17 00:00:00 2001 From: saravanakumardb1 Date: Tue, 3 Mar 2026 09:35:41 -0800 Subject: [PATCH] feat(diagnostics): Admin dashboard client library (Phase 3.4) - querySessions, createSession, getSession, updateSession, cancelSession - getTraces, getLogs, getScreenshots - Full TypeScript types for all diagnostics entities --- .../admin-web/src/lib/diagnostics-client.ts | 299 ++++++++++++++++++ 1 file changed, 299 insertions(+) create mode 100644 dashboards/admin-web/src/lib/diagnostics-client.ts diff --git a/dashboards/admin-web/src/lib/diagnostics-client.ts b/dashboards/admin-web/src/lib/diagnostics-client.ts new file mode 100644 index 00000000..e8606fe9 --- /dev/null +++ b/dashboards/admin-web/src/lib/diagnostics-client.ts @@ -0,0 +1,299 @@ +/** + * Diagnostics Client Library + * Admin dashboard client for debug sessions, traces, and logs + */ + +import { createApiClient } from '@bytelyst/api-client'; +import type { ApiClient } from '@bytelyst/api-client'; + +// ============================================================================= +// Types +// ============================================================================= + +export interface DebugSession { + id: string; + productId: string; + targetUserId?: string; + targetAnonymousId?: string; + targetDeviceId?: string; + targetSessionId?: string; + status: 'pending' | 'active' | 'paused' | 'completed' | 'cancelled'; + collectionLevel: 'standard' | 'debug' | 'trace'; + captureLogs: boolean; + captureNetwork: boolean; + captureScreenshots: boolean; + screenshotOnError: boolean; + maxDurationMinutes: number; + createdAt: string; + updatedAt: string; + startedAt?: string; + endedAt?: string; + expiresAt: string; + logCount: number; + traceCount: number; + screenshotCount: number; + createdBy: string; + updatedBy?: string; + userConsent?: { + consentedAt: string; + consentMethod: 'prompt' | 'pre_consent' | 'auto'; + }; +} + +export interface CreateSessionRequest { + targetUserId?: string; + targetAnonymousId?: string; + targetDeviceId?: string; + targetSessionId?: string; + collectionLevel: 'standard' | 'debug' | 'trace'; + captureLogs?: boolean; + captureNetwork?: boolean; + captureScreenshots?: boolean; + screenshotOnError?: boolean; + maxDurationMinutes?: number; +} + +export interface UpdateSessionRequest { + status?: 'active' | 'paused' | 'completed' | 'cancelled'; + collectionLevel?: 'standard' | 'debug' | 'trace'; + captureLogs?: boolean; + captureNetwork?: boolean; + captureScreenshots?: boolean; + screenshotOnError?: boolean; + maxDurationMinutes?: number; +} + +export interface QuerySessionsOptions { + productId: string; + status?: string; + targetUserId?: string; + targetDeviceId?: string; + startDate?: string; + endDate?: string; + limit?: number; + offset?: number; +} + +export interface QuerySessionsResult { + sessions: DebugSession[]; + total: number; + limit: number; + offset: number; +} + +export interface TraceSpan { + id: string; + sessionId: string; + productId: string; + traceId: string; + parentId?: string; + spanId: string; + name: string; + kind?: 'internal' | 'server' | 'client' | 'producer' | 'consumer'; + startTime: string; + endTime?: string; + durationMs?: number; + attributes: Record; + status: 'ok' | 'error' | 'unset'; + statusMessage?: string; + events?: Array<{ + name: string; + timestamp: string; + attributes?: Record; + }>; +} + +export interface LogEntry { + id: string; + sessionId: string; + productId: string; + level: 'debug' | 'info' | 'warn' | 'error' | 'fatal'; + message: string; + messageHash?: string; + timestamp: string; + receivedAt?: string; + module: string; + file?: string; + line?: number; + function?: string; + threadId?: string; + correlationId?: string; + context: Record; + redaction?: { + fieldsRedacted: string[]; + patternsMatched: string[]; + }; +} + +export interface QueryTracesOptions { + sessionId: string; + productId: string; + startTime?: string; + endTime?: string; + name?: string; + status?: string; + limit?: number; + offset?: number; +} + +export interface QueryLogsOptions { + sessionId: string; + productId: string; + levels?: ('debug' | 'info' | 'warn' | 'error' | 'fatal')[]; + module?: string; + search?: string; + startTime?: string; + endTime?: string; + limit?: number; + offset?: number; +} + +// ============================================================================= +// Client Factory +// ============================================================================= + +export interface DiagnosticsClientConfig { + baseUrl: string; + productId: string; + getAuthToken: () => string | null; +} + +export function createDiagnosticsClient(config: DiagnosticsClientConfig) { + const client = createApiClient({ + baseUrl: config.baseUrl, + getToken: config.getAuthToken, + }) as ApiClient; + + return { + // ------------------------------------------------------------------------- + // Sessions + // ------------------------------------------------------------------------- + + async querySessions(options: QuerySessionsOptions): Promise { + const params = new URLSearchParams(); + params.set('productId', options.productId); + if (options.status) params.set('status', options.status); + if (options.targetUserId) params.set('targetUserId', options.targetUserId); + if (options.targetDeviceId) params.set('targetDeviceId', options.targetDeviceId); + if (options.startDate) params.set('startDate', options.startDate); + if (options.endDate) params.set('endDate', options.endDate); + if (options.limit) params.set('limit', options.limit.toString()); + if (options.offset) params.set('offset', options.offset.toString()); + + const result = await client.safeFetch(`/api/diagnostics/sessions?${params.toString()}`); + if (result.error) throw new Error(result.error); + return result.data!; + }, + + async createSession(request: CreateSessionRequest): Promise { + const result = await client.safeFetch('/api/diagnostics/sessions', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(request), + }); + if (result.error) throw new Error(result.error); + return result.data!; + }, + + async getSession(sessionId: string): Promise { + const result = await client.safeFetch(`/api/diagnostics/sessions/${sessionId}`); + if (result.error) throw new Error(result.error); + return result.data!; + }, + + async updateSession(sessionId: string, request: UpdateSessionRequest): Promise { + const result = await client.safeFetch(`/api/diagnostics/sessions/${sessionId}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(request), + }); + if (result.error) throw new Error(result.error); + return result.data!; + }, + + async cancelSession(sessionId: string, reason?: string): Promise { + const result = await client.safeFetch(`/api/diagnostics/sessions/${sessionId}`, { + method: 'DELETE', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ reason }), + }); + if (result.error) throw new Error(result.error); + }, + + // ------------------------------------------------------------------------- + // Traces + // ------------------------------------------------------------------------- + + async getTraces(options: QueryTracesOptions): Promise<{ traces: TraceSpan[]; total: number }> { + const params = new URLSearchParams(); + params.set('productId', options.productId); + if (options.startTime) params.set('startTime', options.startTime); + if (options.endTime) params.set('endTime', options.endTime); + if (options.name) params.set('name', options.name); + if (options.status) params.set('status', options.status); + if (options.limit) params.set('limit', options.limit.toString()); + if (options.offset) params.set('offset', options.offset.toString()); + + const result = await client.safeFetch<{ traces: TraceSpan[]; total: number }>( + `/api/diagnostics/sessions/${options.sessionId}/traces?${params.toString()}` + ); + if (result.error) throw new Error(result.error); + return result.data!; + }, + + // ------------------------------------------------------------------------- + // Logs + // ------------------------------------------------------------------------- + + async getLogs(options: QueryLogsOptions): Promise<{ logs: LogEntry[]; total: number }> { + const params = new URLSearchParams(); + params.set('productId', options.productId); + if (options.levels) params.set('levels', options.levels.join(',')); + if (options.module) params.set('module', options.module); + if (options.search) params.set('search', options.search); + if (options.startTime) params.set('startTime', options.startTime); + if (options.endTime) params.set('endTime', options.endTime); + if (options.limit) params.set('limit', options.limit.toString()); + if (options.offset) params.set('offset', options.offset.toString()); + + const result = await client.safeFetch<{ logs: LogEntry[]; total: number }>( + `/api/diagnostics/sessions/${options.sessionId}/logs?${params.toString()}` + ); + if (result.error) throw new Error(result.error); + return result.data!; + }, + + // ------------------------------------------------------------------------- + // Screenshots + // ------------------------------------------------------------------------- + + async getScreenshots(sessionId: string, productId: string): Promise< + Array<{ + id: string; + blobUrl: string; + capturedAt: string; + trigger: string; + width: number; + height: number; + }> + > { + const params = new URLSearchParams(); + params.set('productId', productId); + + const result = await client.safeFetch< + Array<{ + id: string; + blobUrl: string; + capturedAt: string; + trigger: string; + width: number; + height: number; + }> + >(`/api/diagnostics/sessions/${sessionId}/screenshots?${params.toString()}`); + if (result.error) throw new Error(result.error); + return result.data!; + }, + }; +} + +export type DiagnosticsClient = ReturnType;