@tonyclaw/agent-inspector 2.1.3 → 2.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.output/nitro.json +1 -1
- package/.output/public/assets/{CompareDrawer-BLYcm6Dg.js → CompareDrawer-SAn2KFl2.js} +1 -1
- package/.output/public/assets/ProxyViewerContainer-DaiTFf7n.js +117 -0
- package/.output/public/assets/{ReplayDialog-_kCy9L9E.js → ReplayDialog-CCwz0YUV.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-DWp5pWsv.js → RequestAnatomy-DXcC4ydl.js} +1 -1
- package/.output/public/assets/{ResponseView-Ci92o6mt.js → ResponseView-Bh9t9aZF.js} +1 -1
- package/.output/public/assets/{StreamingChunkSequence-C-1ZQl_b.js → StreamingChunkSequence-B69M2rr-.js} +1 -1
- package/.output/public/assets/_sessionId-6fF5JpyQ.js +1 -0
- package/.output/public/assets/index-Bcq8bZoK.css +1 -0
- package/.output/public/assets/index-DBSbw8Gd.js +1 -0
- package/.output/public/assets/{main-RgLvmIQk.js → main-DZVKUeg6.js} +2 -2
- package/.output/server/_libs/lucide-react.mjs +204 -173
- package/.output/server/_libs/radix-ui__react-dialog.mjs +2 -0
- package/.output/server/{_sessionId-DnOYbKO7.mjs → _sessionId-COVUf_EG.mjs} +3 -3
- package/.output/server/_ssr/{CompareDrawer-D44_F13_.mjs → CompareDrawer-DqQHO4tk.mjs} +3 -3
- package/.output/server/_ssr/{ProxyViewerContainer-Cx4p_awW.mjs → ProxyViewerContainer-BEeF4V-C.mjs} +1006 -75
- package/.output/server/_ssr/{ReplayDialog-wScCF3Oc.mjs → ReplayDialog-DUZbsd5x.mjs} +4 -4
- package/.output/server/_ssr/{RequestAnatomy-Cf7R7PXS.mjs → RequestAnatomy-DKuIxoBj.mjs} +2 -2
- package/.output/server/_ssr/{ResponseView-7pVIZLL-.mjs → ResponseView-BB9CYmTJ.mjs} +3 -3
- package/.output/server/_ssr/{StreamingChunkSequence-Bs57_GbC.mjs → StreamingChunkSequence-CkY3uTjF.mjs} +3 -3
- package/.output/server/_ssr/{index-6eY__la9.mjs → index-CHTchkrO.mjs} +2 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{router-DRZPhZB6.mjs → router-C8DJ5pQZ.mjs} +1972 -221
- package/.output/server/_tanstack-start-manifest_v-Z1ffcxDr.mjs +4 -0
- package/.output/server/index.mjs +68 -68
- package/package.json +2 -1
- package/src/components/ProxyViewer.tsx +87 -1
- package/src/components/ProxyViewerContainer.tsx +26 -0
- package/src/components/alerts/AlertsDialog.tsx +610 -0
- package/src/components/groups/GroupsDialog.tsx +41 -13
- package/src/components/proxy-viewer/LogEntry.tsx +324 -51
- package/src/components/ui/dialog.tsx +1 -0
- package/src/contracts/index.ts +15 -2
- package/src/contracts/log.ts +18 -0
- package/src/lib/alertContract.ts +79 -0
- package/src/lib/apiClient.ts +39 -0
- package/src/lib/export-logs.ts +47 -9
- package/src/lib/groupContract.ts +1 -0
- package/src/lib/logImportContract.ts +12 -0
- package/src/lib/useAlerts.ts +82 -0
- package/src/lib/useGroupEvidence.ts +6 -2
- package/src/lib/useGroups.ts +6 -2
- package/src/proxy/alerts.ts +664 -0
- package/src/proxy/logBodyChunks.ts +91 -0
- package/src/proxy/logImporter.ts +491 -0
- package/src/proxy/logIndex.ts +63 -2
- package/src/proxy/sqliteLogIndex.ts +612 -0
- package/src/proxy/store.ts +32 -7
- package/src/routes/api/alerts.summary.ts +24 -0
- package/src/routes/api/alerts.ts +52 -0
- package/src/routes/api/logs.$id.body.ts +44 -0
- package/src/routes/api/logs.import.ts +50 -0
- package/src/services/alerts.ts +12 -0
- package/src/services/groups.ts +1 -0
- package/.output/public/assets/ProxyViewerContainer-Ba5wFn00.js +0 -117
- package/.output/public/assets/_sessionId-DKzAmSNP.js +0 -1
- package/.output/public/assets/index-B-MZ9lQM.css +0 -1
- package/.output/public/assets/index-ZZHwgkWJ.js +0 -1
- package/.output/server/_tanstack-start-manifest_v-DfAVFkZ5.mjs +0 -4
package/src/contracts/log.ts
CHANGED
|
@@ -21,6 +21,24 @@ const StreamingChunksArraySchema = z.object({
|
|
|
21
21
|
truncated: z.boolean().optional().default(false),
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
+
export const LogBodyPartSchema = z.enum(["request", "response"]);
|
|
25
|
+
export type LogBodyPart = z.infer<typeof LogBodyPartSchema>;
|
|
26
|
+
|
|
27
|
+
export const LogBodyChunkSchema = z.object({
|
|
28
|
+
logId: z.number().int().positive(),
|
|
29
|
+
part: LogBodyPartSchema,
|
|
30
|
+
text: z.string(),
|
|
31
|
+
offset: z.number().int().nonnegative(),
|
|
32
|
+
limit: z.number().int().positive(),
|
|
33
|
+
totalBytes: z.number().int().nonnegative(),
|
|
34
|
+
textBytes: z.number().int().nonnegative(),
|
|
35
|
+
nextOffset: z.number().int().nonnegative().nullable(),
|
|
36
|
+
hasMore: z.boolean(),
|
|
37
|
+
contentMode: z.enum(["empty", "partial", "full"]),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
export type LogBodyChunk = z.infer<typeof LogBodyChunkSchema>;
|
|
41
|
+
|
|
24
42
|
export const CapturedLogSchema = z.object({
|
|
25
43
|
id: z.number(),
|
|
26
44
|
timestamp: z.string(),
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { JsonValueSchema } from "../contracts";
|
|
3
|
+
|
|
4
|
+
export const AlertSeveritySchema = z.enum(["critical", "warning", "notice"]);
|
|
5
|
+
export const AlertStatusSchema = z.enum(["open"]);
|
|
6
|
+
export const AlertSourceSchema = z.enum(["proxy", "tool-schema", "run", "group"]);
|
|
7
|
+
export const AlertCategorySchema = z.enum([
|
|
8
|
+
"request-failure",
|
|
9
|
+
"request-timeout",
|
|
10
|
+
"slow-response",
|
|
11
|
+
"tool-schema",
|
|
12
|
+
"run-failure",
|
|
13
|
+
"group-failure",
|
|
14
|
+
"group-incomplete",
|
|
15
|
+
]);
|
|
16
|
+
export const AlertEvidenceKindSchema = z.enum(["log", "session", "run", "group"]);
|
|
17
|
+
export const AlertApiFormatSchema = z.enum(["anthropic", "openai", "unknown"]);
|
|
18
|
+
|
|
19
|
+
export const AlertEvidenceLinkSchema = z.object({
|
|
20
|
+
kind: AlertEvidenceKindSchema,
|
|
21
|
+
id: z.string(),
|
|
22
|
+
label: z.string(),
|
|
23
|
+
href: z.string().nullable(),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export const AlertSchema = z.object({
|
|
27
|
+
id: z.string(),
|
|
28
|
+
fingerprint: z.string(),
|
|
29
|
+
severity: AlertSeveritySchema,
|
|
30
|
+
category: AlertCategorySchema,
|
|
31
|
+
source: AlertSourceSchema,
|
|
32
|
+
status: AlertStatusSchema,
|
|
33
|
+
title: z.string(),
|
|
34
|
+
message: z.string(),
|
|
35
|
+
count: z.number().int().positive(),
|
|
36
|
+
firstSeenAt: z.string(),
|
|
37
|
+
lastSeenAt: z.string(),
|
|
38
|
+
logIds: z.array(z.number().int().positive()),
|
|
39
|
+
sessionIds: z.array(z.string()),
|
|
40
|
+
runIds: z.array(z.string()),
|
|
41
|
+
groupIds: z.array(z.string()),
|
|
42
|
+
provider: z.string().nullable(),
|
|
43
|
+
model: z.string().nullable(),
|
|
44
|
+
apiFormat: AlertApiFormatSchema.nullable(),
|
|
45
|
+
evidence: z.array(AlertEvidenceLinkSchema),
|
|
46
|
+
metadata: z.record(z.string(), JsonValueSchema),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
export const AlertCategoryCountSchema = z.object({
|
|
50
|
+
category: AlertCategorySchema,
|
|
51
|
+
count: z.number().int().nonnegative(),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export const AlertSummarySchema = z.object({
|
|
55
|
+
total: z.number().int().nonnegative(),
|
|
56
|
+
open: z.number().int().nonnegative(),
|
|
57
|
+
critical: z.number().int().nonnegative(),
|
|
58
|
+
warning: z.number().int().nonnegative(),
|
|
59
|
+
notice: z.number().int().nonnegative(),
|
|
60
|
+
byCategory: z.array(AlertCategoryCountSchema),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
export const AlertListResponseSchema = z.object({
|
|
64
|
+
alerts: z.array(AlertSchema),
|
|
65
|
+
total: z.number().int().nonnegative(),
|
|
66
|
+
limit: z.number().int().positive(),
|
|
67
|
+
summary: AlertSummarySchema,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
export type AlertSeverity = z.infer<typeof AlertSeveritySchema>;
|
|
71
|
+
export type AlertStatus = z.infer<typeof AlertStatusSchema>;
|
|
72
|
+
export type AlertSource = z.infer<typeof AlertSourceSchema>;
|
|
73
|
+
export type AlertCategory = z.infer<typeof AlertCategorySchema>;
|
|
74
|
+
export type AlertEvidenceKind = z.infer<typeof AlertEvidenceKindSchema>;
|
|
75
|
+
export type AlertEvidenceLink = z.infer<typeof AlertEvidenceLinkSchema>;
|
|
76
|
+
export type Alert = z.infer<typeof AlertSchema>;
|
|
77
|
+
export type AlertCategoryCount = z.infer<typeof AlertCategoryCountSchema>;
|
|
78
|
+
export type AlertSummary = z.infer<typeof AlertSummarySchema>;
|
|
79
|
+
export type AlertListResponse = z.infer<typeof AlertListResponseSchema>;
|
package/src/lib/apiClient.ts
CHANGED
|
@@ -47,3 +47,42 @@ export async function fetchJson<T>(
|
|
|
47
47
|
}
|
|
48
48
|
return parseJsonResponse(response, schema);
|
|
49
49
|
}
|
|
50
|
+
|
|
51
|
+
function formatTimeout(timeoutMs: number): string {
|
|
52
|
+
if (timeoutMs < 1000) return `${String(timeoutMs)}ms`;
|
|
53
|
+
return `${String(Math.round(timeoutMs / 1000))}s`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export class ApiTimeoutError extends Error {
|
|
57
|
+
constructor(timeoutMs: number) {
|
|
58
|
+
super(`Request timed out after ${formatTimeout(timeoutMs)}`);
|
|
59
|
+
this.name = "ApiTimeoutError";
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Fetch a JSON resource with a browser-side timeout. This prevents UI loading
|
|
65
|
+
* states from waiting forever when a tab keeps a stale connection across a
|
|
66
|
+
* local server force-restart.
|
|
67
|
+
*/
|
|
68
|
+
export async function fetchJsonWithTimeout<T>(
|
|
69
|
+
input: RequestInfo | URL,
|
|
70
|
+
schema: z.ZodType<T>,
|
|
71
|
+
timeoutMs: number,
|
|
72
|
+
init?: RequestInit,
|
|
73
|
+
errorFallback?: (response: Response) => string,
|
|
74
|
+
): Promise<T> {
|
|
75
|
+
const controller = new AbortController();
|
|
76
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
return await fetchJson(input, schema, { ...init, signal: controller.signal }, errorFallback);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
82
|
+
throw new ApiTimeoutError(timeoutMs);
|
|
83
|
+
}
|
|
84
|
+
throw error;
|
|
85
|
+
} finally {
|
|
86
|
+
clearTimeout(timeout);
|
|
87
|
+
}
|
|
88
|
+
}
|
package/src/lib/export-logs.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import JSZip from "jszip";
|
|
2
2
|
import type { CapturedLog } from "../contracts";
|
|
3
|
+
import packageJson from "../../package.json";
|
|
3
4
|
|
|
4
5
|
export type ExportMode = "redacted" | "raw";
|
|
5
6
|
export type ExportLogsResult =
|
|
@@ -23,6 +24,17 @@ const MAX_EXPORT_LOGS = 200;
|
|
|
23
24
|
const MAX_EXPORT_TEXT_CHARS = 25_000_000;
|
|
24
25
|
const MAX_STREAMING_CHUNK_EXPORTS = 50;
|
|
25
26
|
|
|
27
|
+
export type ExportManifest = {
|
|
28
|
+
version: string;
|
|
29
|
+
exportedAt: string;
|
|
30
|
+
mode: ExportMode;
|
|
31
|
+
redacted: boolean;
|
|
32
|
+
logCount: number;
|
|
33
|
+
logIds: number[];
|
|
34
|
+
streamingChunkExportLimit: number;
|
|
35
|
+
streamingChunkLogIds: number[];
|
|
36
|
+
};
|
|
37
|
+
|
|
26
38
|
function formatApproxSize(chars: number): string {
|
|
27
39
|
if (chars < 1024) return `${chars} chars`;
|
|
28
40
|
if (chars < 1024 * 1024) return `${Math.ceil(chars / 1024)} KB`;
|
|
@@ -100,6 +112,36 @@ function exportText(text: string, mode: ExportMode): string {
|
|
|
100
112
|
return mode === "redacted" ? redactJsonText(text) : text;
|
|
101
113
|
}
|
|
102
114
|
|
|
115
|
+
export function buildExportLogMetadata(log: CapturedLog, mode: ExportMode): CapturedLog {
|
|
116
|
+
return {
|
|
117
|
+
...log,
|
|
118
|
+
rawRequestBody: null,
|
|
119
|
+
responseText: null,
|
|
120
|
+
rawHeaders: mode === "raw" ? log.rawHeaders : undefined,
|
|
121
|
+
headers: mode === "raw" ? log.headers : undefined,
|
|
122
|
+
streamingChunks: undefined,
|
|
123
|
+
bodyContentMode: "compact",
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function buildExportManifest(
|
|
128
|
+
logs: readonly CapturedLog[],
|
|
129
|
+
mode: ExportMode,
|
|
130
|
+
streamingLogs: readonly CapturedLog[],
|
|
131
|
+
exportedAt: string = new Date().toISOString(),
|
|
132
|
+
): ExportManifest {
|
|
133
|
+
return {
|
|
134
|
+
version: packageJson.version,
|
|
135
|
+
exportedAt,
|
|
136
|
+
mode,
|
|
137
|
+
redacted: mode === "redacted",
|
|
138
|
+
logCount: logs.length,
|
|
139
|
+
logIds: logs.map((log) => log.id),
|
|
140
|
+
streamingChunkExportLimit: MAX_STREAMING_CHUNK_EXPORTS,
|
|
141
|
+
streamingChunkLogIds: streamingLogs.map((log) => log.id),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
103
145
|
export async function exportLogsAsZip(
|
|
104
146
|
logs: CapturedLog[],
|
|
105
147
|
mode: ExportMode = "redacted",
|
|
@@ -112,16 +154,12 @@ export async function exportLogsAsZip(
|
|
|
112
154
|
const streamingLogs = logs.filter((log) => log.streaming).slice(0, MAX_STREAMING_CHUNK_EXPORTS);
|
|
113
155
|
zip.file(
|
|
114
156
|
"manifest.json",
|
|
157
|
+
JSON.stringify(buildExportManifest(logs, mode, streamingLogs), null, 2),
|
|
158
|
+
);
|
|
159
|
+
zip.file(
|
|
160
|
+
"logs.json",
|
|
115
161
|
JSON.stringify(
|
|
116
|
-
|
|
117
|
-
exportedAt: new Date().toISOString(),
|
|
118
|
-
mode,
|
|
119
|
-
redacted: mode === "redacted",
|
|
120
|
-
logCount: logs.length,
|
|
121
|
-
logIds: logs.map((log) => log.id),
|
|
122
|
-
streamingChunkExportLimit: MAX_STREAMING_CHUNK_EXPORTS,
|
|
123
|
-
streamingChunkLogIds: streamingLogs.map((log) => log.id),
|
|
124
|
-
},
|
|
162
|
+
logs.map((log) => buildExportLogMetadata(log, mode)),
|
|
125
163
|
null,
|
|
126
164
|
2,
|
|
127
165
|
),
|
package/src/lib/groupContract.ts
CHANGED
|
@@ -126,6 +126,7 @@ export const InspectorGroupEvidenceSummarySchema = z.object({
|
|
|
126
126
|
export const GroupEvidenceReadResponseSchema = z.object({
|
|
127
127
|
group: InspectorGroupSchema,
|
|
128
128
|
evidence: InspectorGroupEvidenceSchema,
|
|
129
|
+
members: z.array(InspectorGroupExportMemberSchema).nullable(),
|
|
129
130
|
markdown: z.string(),
|
|
130
131
|
summary: InspectorGroupEvidenceSummarySchema.nullable(),
|
|
131
132
|
report: EvidenceReportSchema.nullable(),
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { CapturedLogSchema } from "../contracts";
|
|
3
|
+
|
|
4
|
+
export const ImportLogsResponseSchema = z.object({
|
|
5
|
+
sessionId: z.string(),
|
|
6
|
+
imported: z.number().int().nonnegative(),
|
|
7
|
+
skipped: z.number().int().nonnegative(),
|
|
8
|
+
warnings: z.array(z.string()),
|
|
9
|
+
logs: z.array(CapturedLogSchema),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export type ImportLogsResponse = z.infer<typeof ImportLogsResponseSchema>;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import useSWR, { type SWRResponse } from "swr";
|
|
2
|
+
import {
|
|
3
|
+
AlertListResponseSchema,
|
|
4
|
+
AlertSummarySchema,
|
|
5
|
+
type AlertCategory,
|
|
6
|
+
type AlertListResponse,
|
|
7
|
+
type AlertSeverity,
|
|
8
|
+
type AlertSource,
|
|
9
|
+
type AlertSummary,
|
|
10
|
+
} from "./alertContract";
|
|
11
|
+
import { fetchJsonWithTimeout } from "./apiClient";
|
|
12
|
+
|
|
13
|
+
const ALERT_SUMMARY_FETCH_TIMEOUT_MS = 8_000;
|
|
14
|
+
const ALERT_LIST_FETCH_TIMEOUT_MS = 10_000;
|
|
15
|
+
|
|
16
|
+
export type AlertListFilters = {
|
|
17
|
+
severity?: AlertSeverity;
|
|
18
|
+
category?: AlertCategory;
|
|
19
|
+
source?: AlertSource;
|
|
20
|
+
limit?: number;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type UseAlertsResult = {
|
|
24
|
+
alerts: AlertListResponse["alerts"];
|
|
25
|
+
total: number;
|
|
26
|
+
summary: AlertSummary | null;
|
|
27
|
+
isLoading: boolean;
|
|
28
|
+
error: Error | undefined;
|
|
29
|
+
mutate: SWRResponse<AlertListResponse, Error>["mutate"];
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
type UseAlertSummaryResult = {
|
|
33
|
+
summary: AlertSummary | null;
|
|
34
|
+
isLoading: boolean;
|
|
35
|
+
error: Error | undefined;
|
|
36
|
+
mutate: SWRResponse<AlertSummary, Error>["mutate"];
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
function alertsPath(filters: AlertListFilters): string {
|
|
40
|
+
const params = new URLSearchParams();
|
|
41
|
+
if (filters.severity !== undefined) params.set("severity", filters.severity);
|
|
42
|
+
if (filters.category !== undefined) params.set("category", filters.category);
|
|
43
|
+
if (filters.source !== undefined) params.set("source", filters.source);
|
|
44
|
+
if (filters.limit !== undefined) params.set("limit", String(filters.limit));
|
|
45
|
+
const query = params.toString();
|
|
46
|
+
return query.length > 0 ? `/api/alerts?${query}` : "/api/alerts";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function useAlerts(filters: AlertListFilters, enabled: boolean): UseAlertsResult {
|
|
50
|
+
const response: SWRResponse<AlertListResponse, Error> = useSWR<AlertListResponse, Error>(
|
|
51
|
+
enabled ? alertsPath(filters) : null,
|
|
52
|
+
(url: string) =>
|
|
53
|
+
fetchJsonWithTimeout(url, AlertListResponseSchema, ALERT_LIST_FETCH_TIMEOUT_MS),
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
alerts: response.data?.alerts ?? [],
|
|
58
|
+
total: response.data?.total ?? 0,
|
|
59
|
+
summary: response.data?.summary ?? null,
|
|
60
|
+
isLoading: response.isLoading,
|
|
61
|
+
error: response.error,
|
|
62
|
+
mutate: response.mutate,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function useAlertSummary(): UseAlertSummaryResult {
|
|
67
|
+
const response: SWRResponse<AlertSummary, Error> = useSWR<AlertSummary, Error>(
|
|
68
|
+
"/api/alerts/summary",
|
|
69
|
+
(url: string) => fetchJsonWithTimeout(url, AlertSummarySchema, ALERT_SUMMARY_FETCH_TIMEOUT_MS),
|
|
70
|
+
{
|
|
71
|
+
refreshInterval: 5_000,
|
|
72
|
+
revalidateOnFocus: true,
|
|
73
|
+
},
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
summary: response.data ?? null,
|
|
78
|
+
isLoading: response.isLoading,
|
|
79
|
+
error: response.error,
|
|
80
|
+
mutate: response.mutate,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
@@ -4,7 +4,9 @@ import {
|
|
|
4
4
|
type GroupEvidenceReadResponse,
|
|
5
5
|
type InspectorGroup,
|
|
6
6
|
} from "./groupContract";
|
|
7
|
-
import {
|
|
7
|
+
import { fetchJsonWithTimeout } from "./apiClient";
|
|
8
|
+
|
|
9
|
+
const GROUP_EVIDENCE_FETCH_TIMEOUT_MS = 12_000;
|
|
8
10
|
|
|
9
11
|
type UseGroupEvidenceResult = {
|
|
10
12
|
evidenceResponse: GroupEvidenceReadResponse | null;
|
|
@@ -22,7 +24,9 @@ export function useGroupEvidence(group: InspectorGroup | null): UseGroupEvidence
|
|
|
22
24
|
const response: SWRResponse<GroupEvidenceReadResponse, Error> = useSWR<
|
|
23
25
|
GroupEvidenceReadResponse,
|
|
24
26
|
Error
|
|
25
|
-
>(evidenceKey(group), (url: string) =>
|
|
27
|
+
>(evidenceKey(group), (url: string) =>
|
|
28
|
+
fetchJsonWithTimeout(url, GroupEvidenceReadResponseSchema, GROUP_EVIDENCE_FETCH_TIMEOUT_MS),
|
|
29
|
+
);
|
|
26
30
|
|
|
27
31
|
return {
|
|
28
32
|
evidenceResponse: response.data ?? null,
|
package/src/lib/useGroups.ts
CHANGED
|
@@ -4,7 +4,9 @@ import {
|
|
|
4
4
|
type InspectorGroup,
|
|
5
5
|
type InspectorGroupsListResponse,
|
|
6
6
|
} from "./groupContract";
|
|
7
|
-
import {
|
|
7
|
+
import { fetchJsonWithTimeout } from "./apiClient";
|
|
8
|
+
|
|
9
|
+
const GROUPS_FETCH_TIMEOUT_MS = 8_000;
|
|
8
10
|
|
|
9
11
|
type UseGroupsResult = {
|
|
10
12
|
groups: InspectorGroup[];
|
|
@@ -17,7 +19,9 @@ export function useGroups(): UseGroupsResult {
|
|
|
17
19
|
const response: SWRResponse<InspectorGroupsListResponse, Error> = useSWR<
|
|
18
20
|
InspectorGroupsListResponse,
|
|
19
21
|
Error
|
|
20
|
-
>("/api/groups", (url: string) =>
|
|
22
|
+
>("/api/groups", (url: string) =>
|
|
23
|
+
fetchJsonWithTimeout(url, InspectorGroupsListResponseSchema, GROUPS_FETCH_TIMEOUT_MS),
|
|
24
|
+
);
|
|
21
25
|
|
|
22
26
|
return {
|
|
23
27
|
groups: response.data?.groups ?? [],
|