diff --git a/surfsense_web/contracts/types/log.types.ts b/surfsense_web/contracts/types/log.types.ts index c3e2ec9f5..81b1ee3a4 100644 --- a/surfsense_web/contracts/types/log.types.ts +++ b/surfsense_web/contracts/types/log.types.ts @@ -3,32 +3,22 @@ import { z } from "zod"; /** * ENUMS */ -export const logLevelEnum = z.enum([ - "DEBUG", - "INFO", - "WARNING", - "ERROR", - "CRITICAL" -]); +export const logLevelEnum = z.enum(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]); -export const logStatusEnum = z.enum([ - "IN_PROGRESS", - "SUCCESS", - "FAILED" -]); +export const logStatusEnum = z.enum(["IN_PROGRESS", "SUCCESS", "FAILED"]); /** * Base log schema */ export const log = z.object({ - id: z.number(), - level: logLevelEnum, - status: logStatusEnum, - message: z.string(), - source: z.string().nullable().optional(), - log_metadata: z.record(z.string(), z.any()).nullable().optional(), - created_at: z.string(), - search_space_id: z.number(), + id: z.number(), + level: logLevelEnum, + status: logStatusEnum, + message: z.string(), + source: z.string().nullable().optional(), + log_metadata: z.record(z.string(), z.any()).nullable().optional(), + created_at: z.string(), + search_space_id: z.number(), }); export const logBase = log.omit({ id: true, created_at: true }); @@ -49,22 +39,24 @@ export const updateLogResponse = log; * Delete log */ export const deleteLogRequest = z.object({ id: z.number() }); -export const deleteLogResponse = z.object({ message: z.string().default("Log deleted successfully") }); +export const deleteLogResponse = z.object({ + message: z.string().default("Log deleted successfully"), +}); /** * Get logs (list) */ export const logFilters = z.object({ - search_space_id: z.number().optional(), - level: logLevelEnum.optional(), - status: logStatusEnum.optional(), - source: z.string().optional(), - start_date: z.string().optional(), - end_date: z.string().optional(), + search_space_id: z.number().optional(), + level: logLevelEnum.optional(), + status: logStatusEnum.optional(), + source: z.string().optional(), + start_date: z.string().optional(), + end_date: z.string().optional(), }); export const getLogsRequest = logFilters.extend({ - skip: z.number().optional(), - limit: z.number().optional() + skip: z.number().optional(), + limit: z.number().optional(), }); export const getLogsResponse = z.array(log); @@ -78,32 +70,32 @@ export const getLogResponse = log; * Log summary (used for summary dashboard) */ export const logActiveTask = z.object({ - id: z.number(), - task_name: z.string(), - message: z.string(), - started_at: z.string(), - source: z.string().nullable().optional() + id: z.number(), + task_name: z.string(), + message: z.string(), + started_at: z.string(), + source: z.string().nullable().optional(), }); export const logFailure = z.object({ - id: z.number(), - task_name: z.string(), - message: z.string(), - failed_at: z.string(), - source: z.string().nullable().optional(), - error_details: z.string().nullable().optional(), + id: z.number(), + task_name: z.string(), + message: z.string(), + failed_at: z.string(), + source: z.string().nullable().optional(), + error_details: z.string().nullable().optional(), }); export const logSummary = z.object({ - total_logs: z.number(), - time_window_hours: z.number(), - by_status: z.record(z.string(), z.number()), - by_level: z.record(z.string(), z.number()), - by_source: z.record(z.string(), z.number()), - active_tasks: z.array(logActiveTask), - recent_failures: z.array(logFailure), + total_logs: z.number(), + time_window_hours: z.number(), + by_status: z.record(z.string(), z.number()), + by_level: z.record(z.string(), z.number()), + by_source: z.record(z.string(), z.number()), + active_tasks: z.array(logActiveTask), + recent_failures: z.array(logFailure), }); export const getLogSummaryRequest = z.object({ - search_space_id: z.number(), - hours: z.number().optional(), + search_space_id: z.number(), + hours: z.number().optional(), }); export const getLogSummaryResponse = logSummary; diff --git a/surfsense_web/lib/apis/logs-api.service.ts b/surfsense_web/lib/apis/logs-api.service.ts new file mode 100644 index 000000000..a4fbb3221 --- /dev/null +++ b/surfsense_web/lib/apis/logs-api.service.ts @@ -0,0 +1,119 @@ +import { + type CreateLogRequest, + createLogRequest, + createLogResponse, + type DeleteLogRequest, + deleteLogRequest, + deleteLogResponse, + type GetLogRequest, + type GetLogSummaryRequest, + type GetLogsRequest, + getLogRequest, + getLogResponse, + getLogSummaryRequest, + getLogSummaryResponse, + getLogsRequest, + getLogsResponse, + type Log, + log, + type UpdateLogRequest, + updateLogRequest, + updateLogResponse, +} from "@/contracts/types/log.types"; +import { ValidationError } from "../error"; +import { baseApiService } from "./base-api.service"; + +class LogsApiService { + /** + * Get a list of logs with optional filtering and pagination + */ + getLogs = async (request: GetLogsRequest) => { + const parsedRequest = getLogsRequest.safeParse(request); + if (!parsedRequest.success) { + console.error("Invalid request:", parsedRequest.error); + const errorMessage = parsedRequest.error.issues.map((issue) => issue.message).join(", "); + throw new ValidationError(`Invalid request: ${errorMessage}`); + } + const transformedQueryParams = Object.fromEntries( + Object.entries(parsedRequest.data).map(([k, v]) => [ + k, + Array.isArray(v) ? v.join(",") : String(v), + ]) + ); + const queryParams = new URLSearchParams(transformedQueryParams).toString(); + return baseApiService.get(`/api/v1/logs?${queryParams}`, getLogsResponse); + }; + + /** + * Get a single log by ID + */ + getLog = async (request: GetLogRequest) => { + const parsedRequest = getLogRequest.safeParse(request); + if (!parsedRequest.success) { + console.error("Invalid request:", parsedRequest.error); + const errorMessage = parsedRequest.error.issues.map((issue) => issue.message).join(", "); + throw new ValidationError(`Invalid request: ${errorMessage}`); + } + return baseApiService.get(`/api/v1/logs/${request.id}`, getLogResponse); + }; + + /** + * Create a log entry + */ + createLog = async (request: CreateLogRequest) => { + const parsedRequest = createLogRequest.safeParse(request); + if (!parsedRequest.success) { + console.error("Invalid request:", parsedRequest.error); + const errorMessage = parsedRequest.error.issues.map((issue) => issue.message).join(", "); + throw new ValidationError(`Invalid request: ${errorMessage}`); + } + return baseApiService.post(`/api/v1/logs`, createLogResponse, { + body: parsedRequest.data, + }); + }; + + /** + * Update a log entry + */ + updateLog = async (logId: number, request: UpdateLogRequest) => { + const parsedRequest = updateLogRequest.safeParse(request); + if (!parsedRequest.success) { + console.error("Invalid request:", parsedRequest.error); + const errorMessage = parsedRequest.error.issues.map((issue) => issue.message).join(", "); + throw new ValidationError(`Invalid request: ${errorMessage}`); + } + return baseApiService.put(`/api/v1/logs/${logId}`, updateLogResponse, { + body: parsedRequest.data, + }); + }; + + /** + * Delete a log entry + */ + deleteLog = async (request: DeleteLogRequest) => { + const parsedRequest = deleteLogRequest.safeParse(request); + if (!parsedRequest.success) { + console.error("Invalid request:", parsedRequest.error); + const errorMessage = parsedRequest.error.issues.map((issue) => issue.message).join(", "); + throw new ValidationError(`Invalid request: ${errorMessage}`); + } + return baseApiService.delete(`/api/v1/logs/${parsedRequest.data.id}`, deleteLogResponse); + }; + + /** + * Get summary for logs by search space + */ + getLogSummary = async (request: GetLogSummaryRequest) => { + const parsedRequest = getLogSummaryRequest.safeParse(request); + if (!parsedRequest.success) { + console.error("Invalid request:", parsedRequest.error); + const errorMessage = parsedRequest.error.issues.map((issue) => issue.message).join(", "); + throw new ValidationError(`Invalid request: ${errorMessage}`); + } + const { search_space_id, hours } = parsedRequest.data; + const url = `/api/v1/logs/search-space/${search_space_id}/summary${hours ? `?hours=${hours}` : ""}`; + return baseApiService.get(url, getLogSummaryResponse); + }; +} + +export const logsApiService = new LogsApiService();