@spfn/monitor 0.1.0-beta.1

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.
@@ -0,0 +1,445 @@
1
+ 'use client';
2
+
3
+ // src/nextjs/components/monitor-dashboard.tsx
4
+ import { useState as useState5 } from "react";
5
+
6
+ // src/nextjs/components/stats-overview.tsx
7
+ import { useState, useEffect, useCallback } from "react";
8
+ import { monitorApi } from "@spfn/monitor";
9
+ import { jsx, jsxs } from "react/jsx-runtime";
10
+ function StatsOverview() {
11
+ const [stats, setStats] = useState(null);
12
+ const [isLoading, setIsLoading] = useState(true);
13
+ const fetchStats = useCallback(async () => {
14
+ try {
15
+ const data = await monitorApi.getStats.call({});
16
+ setStats(data);
17
+ } finally {
18
+ setIsLoading(false);
19
+ }
20
+ }, []);
21
+ useEffect(() => {
22
+ fetchStats();
23
+ const interval = setInterval(fetchStats, 3e4);
24
+ return () => clearInterval(interval);
25
+ }, [fetchStats]);
26
+ if (isLoading || !stats) {
27
+ return /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 md:grid-cols-4 gap-4", children: [...Array(4)].map((_, i) => /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-neutral-200 dark:border-neutral-800 p-4 animate-pulse", children: [
28
+ /* @__PURE__ */ jsx("div", { className: "h-4 w-20 bg-neutral-200 dark:bg-neutral-700 rounded mb-2" }),
29
+ /* @__PURE__ */ jsx("div", { className: "h-8 w-12 bg-neutral-200 dark:bg-neutral-700 rounded" })
30
+ ] }, i)) });
31
+ }
32
+ const cards = [
33
+ { label: "Active Errors", value: stats.errors.active, color: "text-red-600 dark:text-red-400" },
34
+ { label: "Resolved", value: stats.errors.resolved, color: "text-green-600 dark:text-green-400" },
35
+ { label: "Ignored", value: stats.errors.ignored, color: "text-neutral-500 dark:text-neutral-400" },
36
+ { label: "Errors (24h)", value: stats.trends.errorsLast24h, color: "text-orange-600 dark:text-orange-400" }
37
+ ];
38
+ return /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 md:grid-cols-4 gap-4", children: cards.map((card) => /* @__PURE__ */ jsxs(
39
+ "div",
40
+ {
41
+ className: "rounded-lg border border-neutral-200 dark:border-neutral-800 p-4",
42
+ children: [
43
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-neutral-500 dark:text-neutral-400", children: card.label }),
44
+ /* @__PURE__ */ jsx("p", { className: `text-2xl font-semibold mt-1 ${card.color}`, children: card.value })
45
+ ]
46
+ },
47
+ card.label
48
+ )) });
49
+ }
50
+
51
+ // src/nextjs/components/error-list-view.tsx
52
+ import { useState as useState2, useEffect as useEffect2 } from "react";
53
+ import { monitorApi as monitorApi2 } from "@spfn/monitor";
54
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
55
+ var STATUS_BADGE = {
56
+ active: "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400",
57
+ resolved: "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400",
58
+ ignored: "bg-neutral-100 text-neutral-600 dark:bg-neutral-800 dark:text-neutral-400"
59
+ };
60
+ function ErrorListView({ onSelect }) {
61
+ const [status, setStatus] = useState2("");
62
+ const [search, setSearch] = useState2("");
63
+ const [errors, setErrors] = useState2([]);
64
+ const [isLoading, setIsLoading] = useState2(true);
65
+ useEffect2(() => {
66
+ let cancelled = false;
67
+ setIsLoading(true);
68
+ monitorApi2.listErrors.call({
69
+ query: {
70
+ ...status ? { status } : {},
71
+ ...search ? { search } : {},
72
+ limit: 50
73
+ }
74
+ }).then((data) => {
75
+ if (!cancelled) {
76
+ setErrors(data);
77
+ setIsLoading(false);
78
+ }
79
+ }).catch(() => {
80
+ if (!cancelled) {
81
+ setIsLoading(false);
82
+ }
83
+ });
84
+ return () => {
85
+ cancelled = true;
86
+ };
87
+ }, [status, search]);
88
+ return /* @__PURE__ */ jsxs2("div", { className: "space-y-4", children: [
89
+ /* @__PURE__ */ jsxs2("div", { className: "flex gap-3", children: [
90
+ /* @__PURE__ */ jsxs2(
91
+ "select",
92
+ {
93
+ value: status,
94
+ onChange: (e) => setStatus(e.target.value),
95
+ className: "rounded border border-neutral-300 dark:border-neutral-700 bg-white dark:bg-neutral-900 px-3 py-1.5 text-sm",
96
+ children: [
97
+ /* @__PURE__ */ jsx2("option", { value: "", children: "All statuses" }),
98
+ /* @__PURE__ */ jsx2("option", { value: "active", children: "Active" }),
99
+ /* @__PURE__ */ jsx2("option", { value: "resolved", children: "Resolved" }),
100
+ /* @__PURE__ */ jsx2("option", { value: "ignored", children: "Ignored" })
101
+ ]
102
+ }
103
+ ),
104
+ /* @__PURE__ */ jsx2(
105
+ "input",
106
+ {
107
+ type: "text",
108
+ placeholder: "Search errors...",
109
+ value: search,
110
+ onChange: (e) => setSearch(e.target.value),
111
+ className: "rounded border border-neutral-300 dark:border-neutral-700 bg-white dark:bg-neutral-900 px-3 py-1.5 text-sm flex-1"
112
+ }
113
+ )
114
+ ] }),
115
+ isLoading ? /* @__PURE__ */ jsx2("div", { className: "text-sm text-neutral-500", children: "Loading..." }) : errors.length === 0 ? /* @__PURE__ */ jsx2("div", { className: "text-sm text-neutral-500 py-8 text-center", children: "No errors found" }) : /* @__PURE__ */ jsx2("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs2("table", { className: "w-full text-sm", children: [
116
+ /* @__PURE__ */ jsx2("thead", { children: /* @__PURE__ */ jsxs2("tr", { className: "border-b border-neutral-200 dark:border-neutral-800 text-left text-neutral-500", children: [
117
+ /* @__PURE__ */ jsx2("th", { className: "py-2 pr-4", children: "Status" }),
118
+ /* @__PURE__ */ jsx2("th", { className: "py-2 pr-4", children: "Error" }),
119
+ /* @__PURE__ */ jsx2("th", { className: "py-2 pr-4", children: "Path" }),
120
+ /* @__PURE__ */ jsx2("th", { className: "py-2 pr-4 text-right", children: "Count" }),
121
+ /* @__PURE__ */ jsx2("th", { className: "py-2 text-right", children: "Last Seen" })
122
+ ] }) }),
123
+ /* @__PURE__ */ jsx2("tbody", { children: errors.map((group) => /* @__PURE__ */ jsxs2(
124
+ "tr",
125
+ {
126
+ onClick: () => onSelect?.(group.id),
127
+ className: "border-b border-neutral-100 dark:border-neutral-800/50 hover:bg-neutral-50 dark:hover:bg-neutral-800/50 cursor-pointer",
128
+ children: [
129
+ /* @__PURE__ */ jsx2("td", { className: "py-2 pr-4", children: /* @__PURE__ */ jsx2("span", { className: `inline-block px-2 py-0.5 rounded text-xs font-medium ${STATUS_BADGE[group.status]}`, children: group.status }) }),
130
+ /* @__PURE__ */ jsxs2("td", { className: "py-2 pr-4", children: [
131
+ /* @__PURE__ */ jsx2("div", { className: "font-medium text-neutral-900 dark:text-neutral-100", children: group.name }),
132
+ /* @__PURE__ */ jsx2("div", { className: "text-neutral-500 truncate max-w-xs", children: group.message })
133
+ ] }),
134
+ /* @__PURE__ */ jsxs2("td", { className: "py-2 pr-4 font-mono text-xs text-neutral-600 dark:text-neutral-400", children: [
135
+ group.method,
136
+ " ",
137
+ group.path
138
+ ] }),
139
+ /* @__PURE__ */ jsx2("td", { className: "py-2 pr-4 text-right font-mono", children: group.count }),
140
+ /* @__PURE__ */ jsx2("td", { className: "py-2 text-right text-neutral-500", children: formatRelativeTime(group.lastSeenAt) })
141
+ ]
142
+ },
143
+ group.id
144
+ )) })
145
+ ] }) })
146
+ ] });
147
+ }
148
+ function formatRelativeTime(date) {
149
+ const d = typeof date === "string" ? new Date(date) : date;
150
+ const now = Date.now();
151
+ const diff = now - d.getTime();
152
+ const mins = Math.floor(diff / 6e4);
153
+ if (mins < 1) {
154
+ return "just now";
155
+ }
156
+ if (mins < 60) {
157
+ return `${mins}m ago`;
158
+ }
159
+ const hours = Math.floor(mins / 60);
160
+ if (hours < 24) {
161
+ return `${hours}h ago`;
162
+ }
163
+ const days = Math.floor(hours / 24);
164
+ return `${days}d ago`;
165
+ }
166
+
167
+ // src/nextjs/components/error-detail-view.tsx
168
+ import { useState as useState3, useEffect as useEffect3, useCallback as useCallback2 } from "react";
169
+ import { monitorApi as monitorApi3 } from "@spfn/monitor";
170
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
171
+ var STATUS_ACTIONS = {
172
+ active: [
173
+ { label: "Resolve", target: "resolved" },
174
+ { label: "Ignore", target: "ignored" }
175
+ ],
176
+ resolved: [
177
+ { label: "Reopen", target: "active" }
178
+ ],
179
+ ignored: [
180
+ { label: "Reopen", target: "active" },
181
+ { label: "Resolve", target: "resolved" }
182
+ ]
183
+ };
184
+ function ErrorDetailView({ errorId, onBack }) {
185
+ const [data, setData] = useState3(null);
186
+ const [isLoading, setIsLoading] = useState3(true);
187
+ const [isMutating, setIsMutating] = useState3(false);
188
+ const fetchDetail = useCallback2(async () => {
189
+ setIsLoading(true);
190
+ try {
191
+ const result = await monitorApi3.getErrorDetail.call({ params: { id: errorId } });
192
+ setData(result);
193
+ } finally {
194
+ setIsLoading(false);
195
+ }
196
+ }, [errorId]);
197
+ useEffect3(() => {
198
+ fetchDetail();
199
+ }, [fetchDetail]);
200
+ async function handleStatusChange(status) {
201
+ setIsMutating(true);
202
+ try {
203
+ await monitorApi3.updateErrorStatus.call({
204
+ params: { id: errorId },
205
+ body: { status }
206
+ });
207
+ await fetchDetail();
208
+ } finally {
209
+ setIsMutating(false);
210
+ }
211
+ }
212
+ if (isLoading || !data) {
213
+ return /* @__PURE__ */ jsx3("div", { className: "text-sm text-neutral-500", children: "Loading..." });
214
+ }
215
+ const { group, events } = data;
216
+ const actions = STATUS_ACTIONS[group.status] ?? [];
217
+ return /* @__PURE__ */ jsxs3("div", { className: "space-y-6", children: [
218
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-3", children: [
219
+ onBack && /* @__PURE__ */ jsx3(
220
+ "button",
221
+ {
222
+ onClick: onBack,
223
+ className: "text-sm text-neutral-500 hover:text-neutral-700 dark:hover:text-neutral-300",
224
+ children: "\u2190 Back"
225
+ }
226
+ ),
227
+ /* @__PURE__ */ jsxs3("h2", { className: "text-lg font-semibold text-neutral-900 dark:text-neutral-100", children: [
228
+ group.name,
229
+ " \u2014 ",
230
+ group.statusCode
231
+ ] })
232
+ ] }),
233
+ /* @__PURE__ */ jsxs3("div", { className: "rounded-lg border border-neutral-200 dark:border-neutral-800 p-4 space-y-3", children: [
234
+ /* @__PURE__ */ jsx3("p", { className: "text-neutral-700 dark:text-neutral-300", children: group.message }),
235
+ /* @__PURE__ */ jsxs3("div", { className: "grid grid-cols-2 md:grid-cols-4 gap-4 text-sm", children: [
236
+ /* @__PURE__ */ jsxs3("div", { children: [
237
+ /* @__PURE__ */ jsx3("span", { className: "text-neutral-500", children: "Method" }),
238
+ /* @__PURE__ */ jsx3("p", { className: "font-mono", children: group.method })
239
+ ] }),
240
+ /* @__PURE__ */ jsxs3("div", { children: [
241
+ /* @__PURE__ */ jsx3("span", { className: "text-neutral-500", children: "Path" }),
242
+ /* @__PURE__ */ jsx3("p", { className: "font-mono", children: group.path })
243
+ ] }),
244
+ /* @__PURE__ */ jsxs3("div", { children: [
245
+ /* @__PURE__ */ jsx3("span", { className: "text-neutral-500", children: "Count" }),
246
+ /* @__PURE__ */ jsx3("p", { className: "font-mono", children: group.count })
247
+ ] }),
248
+ /* @__PURE__ */ jsxs3("div", { children: [
249
+ /* @__PURE__ */ jsx3("span", { className: "text-neutral-500", children: "Status" }),
250
+ /* @__PURE__ */ jsx3("p", { className: "font-medium", children: group.status })
251
+ ] })
252
+ ] }),
253
+ actions.length > 0 && /* @__PURE__ */ jsx3("div", { className: "flex gap-2 pt-2", children: actions.map((action) => /* @__PURE__ */ jsx3(
254
+ "button",
255
+ {
256
+ onClick: () => handleStatusChange(action.target),
257
+ disabled: isMutating,
258
+ className: "px-3 py-1.5 text-sm rounded border border-neutral-300 dark:border-neutral-700 hover:bg-neutral-100 dark:hover:bg-neutral-800 disabled:opacity-50",
259
+ children: action.label
260
+ },
261
+ action.target
262
+ )) })
263
+ ] }),
264
+ /* @__PURE__ */ jsxs3("div", { children: [
265
+ /* @__PURE__ */ jsx3("h3", { className: "text-sm font-medium text-neutral-500 mb-3", children: "Recent Events" }),
266
+ /* @__PURE__ */ jsx3("div", { className: "space-y-2", children: events.map((event) => /* @__PURE__ */ jsx3(
267
+ "div",
268
+ {
269
+ className: "rounded border border-neutral-200 dark:border-neutral-800 p-3 text-sm",
270
+ children: /* @__PURE__ */ jsxs3("div", { className: "flex justify-between items-start", children: [
271
+ /* @__PURE__ */ jsxs3("div", { className: "space-y-1", children: [
272
+ /* @__PURE__ */ jsxs3("div", { className: "flex gap-3 text-neutral-500", children: [
273
+ /* @__PURE__ */ jsxs3("span", { children: [
274
+ "User: ",
275
+ event.userId ?? "(anonymous)"
276
+ ] }),
277
+ /* @__PURE__ */ jsxs3("span", { children: [
278
+ "Request: ",
279
+ event.requestId ?? "(none)"
280
+ ] })
281
+ ] }),
282
+ event.stackTrace && /* @__PURE__ */ jsx3("pre", { className: "text-xs text-neutral-600 dark:text-neutral-400 overflow-x-auto whitespace-pre-wrap mt-2 bg-neutral-50 dark:bg-neutral-900 p-2 rounded", children: event.stackTrace.split("\n").slice(0, 5).join("\n") })
283
+ ] }),
284
+ /* @__PURE__ */ jsx3("span", { className: "text-xs text-neutral-400 whitespace-nowrap ml-4", children: new Date(event.createdAt).toLocaleString() })
285
+ ] })
286
+ },
287
+ event.id
288
+ )) })
289
+ ] })
290
+ ] });
291
+ }
292
+
293
+ // src/nextjs/components/log-viewer.tsx
294
+ import { useState as useState4, useEffect as useEffect4 } from "react";
295
+ import { monitorApi as monitorApi4 } from "@spfn/monitor";
296
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
297
+ var LEVEL_BADGE = {
298
+ debug: "bg-neutral-100 text-neutral-600 dark:bg-neutral-800 dark:text-neutral-400",
299
+ info: "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400",
300
+ warn: "bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400",
301
+ error: "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400",
302
+ fatal: "bg-red-200 text-red-800 dark:bg-red-900/50 dark:text-red-300"
303
+ };
304
+ function LogViewer() {
305
+ const [level, setLevel] = useState4("");
306
+ const [search, setSearch] = useState4("");
307
+ const [logs, setLogs] = useState4([]);
308
+ const [isLoading, setIsLoading] = useState4(true);
309
+ const [expanded, setExpanded] = useState4(/* @__PURE__ */ new Set());
310
+ useEffect4(() => {
311
+ let cancelled = false;
312
+ setIsLoading(true);
313
+ monitorApi4.listLogs.call({
314
+ query: {
315
+ ...level ? { level } : {},
316
+ ...search ? { search } : {},
317
+ limit: 100
318
+ }
319
+ }).then((data) => {
320
+ if (!cancelled) {
321
+ setLogs(data);
322
+ setIsLoading(false);
323
+ }
324
+ }).catch(() => {
325
+ if (!cancelled) {
326
+ setIsLoading(false);
327
+ }
328
+ });
329
+ return () => {
330
+ cancelled = true;
331
+ };
332
+ }, [level, search]);
333
+ function toggleExpand(id) {
334
+ setExpanded((prev) => {
335
+ const next = new Set(prev);
336
+ if (next.has(id)) {
337
+ next.delete(id);
338
+ } else {
339
+ next.add(id);
340
+ }
341
+ return next;
342
+ });
343
+ }
344
+ return /* @__PURE__ */ jsxs4("div", { className: "space-y-4", children: [
345
+ /* @__PURE__ */ jsxs4("div", { className: "flex gap-3", children: [
346
+ /* @__PURE__ */ jsxs4(
347
+ "select",
348
+ {
349
+ value: level,
350
+ onChange: (e) => setLevel(e.target.value),
351
+ className: "rounded border border-neutral-300 dark:border-neutral-700 bg-white dark:bg-neutral-900 px-3 py-1.5 text-sm",
352
+ children: [
353
+ /* @__PURE__ */ jsx4("option", { value: "", children: "All levels" }),
354
+ /* @__PURE__ */ jsx4("option", { value: "debug", children: "Debug" }),
355
+ /* @__PURE__ */ jsx4("option", { value: "info", children: "Info" }),
356
+ /* @__PURE__ */ jsx4("option", { value: "warn", children: "Warn" }),
357
+ /* @__PURE__ */ jsx4("option", { value: "error", children: "Error" }),
358
+ /* @__PURE__ */ jsx4("option", { value: "fatal", children: "Fatal" })
359
+ ]
360
+ }
361
+ ),
362
+ /* @__PURE__ */ jsx4(
363
+ "input",
364
+ {
365
+ type: "text",
366
+ placeholder: "Search logs...",
367
+ value: search,
368
+ onChange: (e) => setSearch(e.target.value),
369
+ className: "rounded border border-neutral-300 dark:border-neutral-700 bg-white dark:bg-neutral-900 px-3 py-1.5 text-sm flex-1"
370
+ }
371
+ )
372
+ ] }),
373
+ isLoading ? /* @__PURE__ */ jsx4("div", { className: "text-sm text-neutral-500", children: "Loading..." }) : logs.length === 0 ? /* @__PURE__ */ jsx4("div", { className: "text-sm text-neutral-500 py-8 text-center", children: "No logs found" }) : /* @__PURE__ */ jsx4("div", { className: "space-y-1", children: logs.map((log) => /* @__PURE__ */ jsxs4(
374
+ "div",
375
+ {
376
+ className: "rounded border border-neutral-200 dark:border-neutral-800 text-sm",
377
+ children: [
378
+ /* @__PURE__ */ jsxs4(
379
+ "div",
380
+ {
381
+ onClick: () => log.metadata && toggleExpand(log.id),
382
+ className: `flex items-start gap-3 p-2 ${log.metadata ? "cursor-pointer hover:bg-neutral-50 dark:hover:bg-neutral-800/50" : ""}`,
383
+ children: [
384
+ /* @__PURE__ */ jsx4("span", { className: `inline-block px-1.5 py-0.5 rounded text-xs font-medium ${LEVEL_BADGE[log.level] ?? ""}`, children: log.level }),
385
+ /* @__PURE__ */ jsx4("span", { className: "flex-1 text-neutral-800 dark:text-neutral-200", children: log.message }),
386
+ log.source && /* @__PURE__ */ jsx4("span", { className: "text-xs text-neutral-400 font-mono", children: log.source }),
387
+ /* @__PURE__ */ jsx4("span", { className: "text-xs text-neutral-400 whitespace-nowrap", children: new Date(log.createdAt).toLocaleTimeString() })
388
+ ]
389
+ }
390
+ ),
391
+ expanded.has(log.id) && log.metadata && /* @__PURE__ */ jsx4("pre", { className: "px-3 py-2 text-xs bg-neutral-50 dark:bg-neutral-900 border-t border-neutral-200 dark:border-neutral-800 overflow-x-auto", children: JSON.stringify(log.metadata, null, 2) })
392
+ ]
393
+ },
394
+ log.id
395
+ )) })
396
+ ] });
397
+ }
398
+
399
+ // src/nextjs/components/monitor-dashboard.tsx
400
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
401
+ function MonitorDashboard() {
402
+ const [tab, setTab] = useState5("errors");
403
+ const [selectedErrorId, setSelectedErrorId] = useState5(null);
404
+ return /* @__PURE__ */ jsxs5("div", { className: "space-y-6", children: [
405
+ /* @__PURE__ */ jsx5(StatsOverview, {}),
406
+ /* @__PURE__ */ jsxs5("div", { className: "flex gap-1 border-b border-neutral-200 dark:border-neutral-800", children: [
407
+ /* @__PURE__ */ jsx5(TabButton, { active: tab === "errors", onClick: () => {
408
+ setTab("errors");
409
+ setSelectedErrorId(null);
410
+ }, children: "Errors" }),
411
+ /* @__PURE__ */ jsx5(TabButton, { active: tab === "logs", onClick: () => setTab("logs"), children: "Logs" })
412
+ ] }),
413
+ tab === "errors" && !selectedErrorId && /* @__PURE__ */ jsx5(ErrorListView, { onSelect: setSelectedErrorId }),
414
+ tab === "errors" && selectedErrorId && /* @__PURE__ */ jsx5(
415
+ ErrorDetailView,
416
+ {
417
+ errorId: selectedErrorId,
418
+ onBack: () => setSelectedErrorId(null)
419
+ }
420
+ ),
421
+ tab === "logs" && /* @__PURE__ */ jsx5(LogViewer, {})
422
+ ] });
423
+ }
424
+ function TabButton({
425
+ active,
426
+ onClick,
427
+ children
428
+ }) {
429
+ return /* @__PURE__ */ jsx5(
430
+ "button",
431
+ {
432
+ onClick,
433
+ className: `px-4 py-2 text-sm font-medium border-b-2 -mb-px transition-colors ${active ? "border-neutral-900 dark:border-neutral-100 text-neutral-900 dark:text-neutral-100" : "border-transparent text-neutral-500 hover:text-neutral-700 dark:hover:text-neutral-300"}`,
434
+ children
435
+ }
436
+ );
437
+ }
438
+ export {
439
+ ErrorDetailView,
440
+ ErrorListView,
441
+ LogViewer,
442
+ MonitorDashboard,
443
+ StatsOverview
444
+ };
445
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/nextjs/components/monitor-dashboard.tsx","../../src/nextjs/components/stats-overview.tsx","../../src/nextjs/components/error-list-view.tsx","../../src/nextjs/components/error-detail-view.tsx","../../src/nextjs/components/log-viewer.tsx"],"sourcesContent":["/**\n * @spfn/monitor - Monitor Dashboard Component\n *\n * Main entry point combining StatsOverview, ErrorListView, and LogViewer in tabs\n */\n\nimport { useState } from 'react';\nimport { StatsOverview } from './stats-overview';\nimport { ErrorListView } from './error-list-view';\nimport { ErrorDetailView } from './error-detail-view';\nimport { LogViewer } from './log-viewer';\n\ntype Tab = 'errors' | 'logs';\n\nexport function MonitorDashboard()\n{\n const [tab, setTab] = useState<Tab>('errors');\n const [selectedErrorId, setSelectedErrorId] = useState<number | null>(null);\n\n return (\n <div className=\"space-y-6\">\n {/* Stats */}\n <StatsOverview />\n\n {/* Tabs */}\n <div className=\"flex gap-1 border-b border-neutral-200 dark:border-neutral-800\">\n <TabButton active={tab === 'errors'} onClick={() => { setTab('errors'); setSelectedErrorId(null); }}>\n Errors\n </TabButton>\n <TabButton active={tab === 'logs'} onClick={() => setTab('logs')}>\n Logs\n </TabButton>\n </div>\n\n {/* Content */}\n {tab === 'errors' && !selectedErrorId && (\n <ErrorListView onSelect={setSelectedErrorId} />\n )}\n {tab === 'errors' && selectedErrorId && (\n <ErrorDetailView\n errorId={selectedErrorId}\n onBack={() => setSelectedErrorId(null)}\n />\n )}\n {tab === 'logs' && (\n <LogViewer />\n )}\n </div>\n );\n}\n\nfunction TabButton({\n active,\n onClick,\n children,\n}: {\n active: boolean;\n onClick: () => void;\n children: React.ReactNode;\n})\n{\n return (\n <button\n onClick={onClick}\n className={`px-4 py-2 text-sm font-medium border-b-2 -mb-px transition-colors ${\n active\n ? 'border-neutral-900 dark:border-neutral-100 text-neutral-900 dark:text-neutral-100'\n : 'border-transparent text-neutral-500 hover:text-neutral-700 dark:hover:text-neutral-300'\n }`}\n >\n {children}\n </button>\n );\n}\n","/**\n * @spfn/monitor - Stats Overview Component\n *\n * Displays error/log counts and trend indicators\n */\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { monitorApi } from '@spfn/monitor';\nimport type { MonitorStats } from '@spfn/monitor';\n\nexport function StatsOverview()\n{\n const [stats, setStats] = useState<MonitorStats | null>(null);\n const [isLoading, setIsLoading] = useState(true);\n\n const fetchStats = useCallback(async () =>\n {\n try\n {\n const data = await monitorApi.getStats.call({});\n setStats(data as MonitorStats);\n }\n finally\n {\n setIsLoading(false);\n }\n }, []);\n\n useEffect(() =>\n {\n fetchStats();\n const interval = setInterval(fetchStats, 30_000);\n return () => clearInterval(interval);\n }, [fetchStats]);\n\n if (isLoading || !stats)\n {\n return (\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-4\">\n {[...Array(4)].map((_, i) => (\n <div key={i} className=\"rounded-lg border border-neutral-200 dark:border-neutral-800 p-4 animate-pulse\">\n <div className=\"h-4 w-20 bg-neutral-200 dark:bg-neutral-700 rounded mb-2\" />\n <div className=\"h-8 w-12 bg-neutral-200 dark:bg-neutral-700 rounded\" />\n </div>\n ))}\n </div>\n );\n }\n\n const cards = [\n { label: 'Active Errors', value: stats.errors.active, color: 'text-red-600 dark:text-red-400' },\n { label: 'Resolved', value: stats.errors.resolved, color: 'text-green-600 dark:text-green-400' },\n { label: 'Ignored', value: stats.errors.ignored, color: 'text-neutral-500 dark:text-neutral-400' },\n { label: 'Errors (24h)', value: stats.trends.errorsLast24h, color: 'text-orange-600 dark:text-orange-400' },\n ];\n\n return (\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-4\">\n {cards.map((card) => (\n <div\n key={card.label}\n className=\"rounded-lg border border-neutral-200 dark:border-neutral-800 p-4\"\n >\n <p className=\"text-sm text-neutral-500 dark:text-neutral-400\">{card.label}</p>\n <p className={`text-2xl font-semibold mt-1 ${card.color}`}>{card.value}</p>\n </div>\n ))}\n </div>\n );\n}\n","/**\n * @spfn/monitor - Error List View Component\n *\n * Displays error groups in a filterable table with status badges\n */\n\nimport { useState, useEffect } from 'react';\nimport { monitorApi } from '@spfn/monitor';\nimport type { ErrorGroupStatus } from '@spfn/monitor';\n\ninterface ErrorListViewProps\n{\n onSelect?: (id: number) => void;\n}\n\nconst STATUS_BADGE: Record<ErrorGroupStatus, string> = {\n active: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400',\n resolved: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400',\n ignored: 'bg-neutral-100 text-neutral-600 dark:bg-neutral-800 dark:text-neutral-400',\n};\n\nexport function ErrorListView({ onSelect }: ErrorListViewProps)\n{\n const [status, setStatus] = useState<string>('');\n const [search, setSearch] = useState('');\n const [errors, setErrors] = useState<any[]>([]);\n const [isLoading, setIsLoading] = useState(true);\n\n useEffect(() =>\n {\n let cancelled = false;\n setIsLoading(true);\n\n monitorApi.listErrors.call({\n query: {\n ...(status ? { status } : {}),\n ...(search ? { search } : {}),\n limit: 50,\n },\n }).then((data) =>\n {\n if (!cancelled)\n {\n setErrors(data as any[]);\n setIsLoading(false);\n }\n }).catch(() =>\n {\n if (!cancelled)\n {\n setIsLoading(false);\n }\n });\n\n return () => { cancelled = true; };\n }, [status, search]);\n\n return (\n <div className=\"space-y-4\">\n {/* Filters */}\n <div className=\"flex gap-3\">\n <select\n value={status}\n onChange={(e) => setStatus(e.target.value)}\n className=\"rounded border border-neutral-300 dark:border-neutral-700 bg-white dark:bg-neutral-900 px-3 py-1.5 text-sm\"\n >\n <option value=\"\">All statuses</option>\n <option value=\"active\">Active</option>\n <option value=\"resolved\">Resolved</option>\n <option value=\"ignored\">Ignored</option>\n </select>\n <input\n type=\"text\"\n placeholder=\"Search errors...\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n className=\"rounded border border-neutral-300 dark:border-neutral-700 bg-white dark:bg-neutral-900 px-3 py-1.5 text-sm flex-1\"\n />\n </div>\n\n {/* Table */}\n {isLoading ? (\n <div className=\"text-sm text-neutral-500\">Loading...</div>\n ) : errors.length === 0 ? (\n <div className=\"text-sm text-neutral-500 py-8 text-center\">No errors found</div>\n ) : (\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b border-neutral-200 dark:border-neutral-800 text-left text-neutral-500\">\n <th className=\"py-2 pr-4\">Status</th>\n <th className=\"py-2 pr-4\">Error</th>\n <th className=\"py-2 pr-4\">Path</th>\n <th className=\"py-2 pr-4 text-right\">Count</th>\n <th className=\"py-2 text-right\">Last Seen</th>\n </tr>\n </thead>\n <tbody>\n {errors.map((group: any) => (\n <tr\n key={group.id}\n onClick={() => onSelect?.(group.id)}\n className=\"border-b border-neutral-100 dark:border-neutral-800/50 hover:bg-neutral-50 dark:hover:bg-neutral-800/50 cursor-pointer\"\n >\n <td className=\"py-2 pr-4\">\n <span className={`inline-block px-2 py-0.5 rounded text-xs font-medium ${STATUS_BADGE[group.status as ErrorGroupStatus]}`}>\n {group.status}\n </span>\n </td>\n <td className=\"py-2 pr-4\">\n <div className=\"font-medium text-neutral-900 dark:text-neutral-100\">{group.name}</div>\n <div className=\"text-neutral-500 truncate max-w-xs\">{group.message}</div>\n </td>\n <td className=\"py-2 pr-4 font-mono text-xs text-neutral-600 dark:text-neutral-400\">\n {group.method} {group.path}\n </td>\n <td className=\"py-2 pr-4 text-right font-mono\">{group.count}</td>\n <td className=\"py-2 text-right text-neutral-500\">\n {formatRelativeTime(group.lastSeenAt)}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </div>\n );\n}\n\nfunction formatRelativeTime(date: string | Date): string\n{\n const d = typeof date === 'string' ? new Date(date) : date;\n const now = Date.now();\n const diff = now - d.getTime();\n const mins = Math.floor(diff / 60_000);\n\n if (mins < 1)\n {\n return 'just now';\n }\n\n if (mins < 60)\n {\n return `${mins}m ago`;\n }\n\n const hours = Math.floor(mins / 60);\n if (hours < 24)\n {\n return `${hours}h ago`;\n }\n\n const days = Math.floor(hours / 24);\n return `${days}d ago`;\n}\n","/**\n * @spfn/monitor - Error Detail View Component\n *\n * Shows error group details with event timeline and status change buttons\n */\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { monitorApi } from '@spfn/monitor';\nimport type { ErrorGroupStatus } from '@spfn/monitor';\n\ninterface ErrorDetailViewProps\n{\n errorId: number;\n onBack?: () => void;\n}\n\nconst STATUS_ACTIONS: Record<ErrorGroupStatus, { label: string; target: ErrorGroupStatus }[]> = {\n active: [\n { label: 'Resolve', target: 'resolved' },\n { label: 'Ignore', target: 'ignored' },\n ],\n resolved: [\n { label: 'Reopen', target: 'active' },\n ],\n ignored: [\n { label: 'Reopen', target: 'active' },\n { label: 'Resolve', target: 'resolved' },\n ],\n};\n\nexport function ErrorDetailView({ errorId, onBack }: ErrorDetailViewProps)\n{\n const [data, setData] = useState<any>(null);\n const [isLoading, setIsLoading] = useState(true);\n const [isMutating, setIsMutating] = useState(false);\n\n const fetchDetail = useCallback(async () =>\n {\n setIsLoading(true);\n try\n {\n const result = await monitorApi.getErrorDetail.call({ params: { id: errorId } });\n setData(result);\n }\n finally\n {\n setIsLoading(false);\n }\n }, [errorId]);\n\n useEffect(() =>\n {\n fetchDetail();\n }, [fetchDetail]);\n\n async function handleStatusChange(status: string)\n {\n setIsMutating(true);\n try\n {\n await monitorApi.updateErrorStatus.call({\n params: { id: errorId },\n body: { status },\n });\n await fetchDetail();\n }\n finally\n {\n setIsMutating(false);\n }\n }\n\n if (isLoading || !data)\n {\n return <div className=\"text-sm text-neutral-500\">Loading...</div>;\n }\n\n const { group, events } = data;\n const actions = STATUS_ACTIONS[group.status as ErrorGroupStatus] ?? [];\n\n return (\n <div className=\"space-y-6\">\n {/* Header */}\n <div className=\"flex items-center gap-3\">\n {onBack && (\n <button\n onClick={onBack}\n className=\"text-sm text-neutral-500 hover:text-neutral-700 dark:hover:text-neutral-300\"\n >\n &larr; Back\n </button>\n )}\n <h2 className=\"text-lg font-semibold text-neutral-900 dark:text-neutral-100\">\n {group.name} — {group.statusCode}\n </h2>\n </div>\n\n {/* Error Info */}\n <div className=\"rounded-lg border border-neutral-200 dark:border-neutral-800 p-4 space-y-3\">\n <p className=\"text-neutral-700 dark:text-neutral-300\">{group.message}</p>\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-4 text-sm\">\n <div>\n <span className=\"text-neutral-500\">Method</span>\n <p className=\"font-mono\">{group.method}</p>\n </div>\n <div>\n <span className=\"text-neutral-500\">Path</span>\n <p className=\"font-mono\">{group.path}</p>\n </div>\n <div>\n <span className=\"text-neutral-500\">Count</span>\n <p className=\"font-mono\">{group.count}</p>\n </div>\n <div>\n <span className=\"text-neutral-500\">Status</span>\n <p className=\"font-medium\">{group.status}</p>\n </div>\n </div>\n\n {/* Status Actions */}\n {actions.length > 0 && (\n <div className=\"flex gap-2 pt-2\">\n {actions.map((action) => (\n <button\n key={action.target}\n onClick={() => handleStatusChange(action.target)}\n disabled={isMutating}\n className=\"px-3 py-1.5 text-sm rounded border border-neutral-300 dark:border-neutral-700 hover:bg-neutral-100 dark:hover:bg-neutral-800 disabled:opacity-50\"\n >\n {action.label}\n </button>\n ))}\n </div>\n )}\n </div>\n\n {/* Event Timeline */}\n <div>\n <h3 className=\"text-sm font-medium text-neutral-500 mb-3\">Recent Events</h3>\n <div className=\"space-y-2\">\n {(events as any[]).map((event: any) => (\n <div\n key={event.id}\n className=\"rounded border border-neutral-200 dark:border-neutral-800 p-3 text-sm\"\n >\n <div className=\"flex justify-between items-start\">\n <div className=\"space-y-1\">\n <div className=\"flex gap-3 text-neutral-500\">\n <span>User: {event.userId ?? '(anonymous)'}</span>\n <span>Request: {event.requestId ?? '(none)'}</span>\n </div>\n {event.stackTrace && (\n <pre className=\"text-xs text-neutral-600 dark:text-neutral-400 overflow-x-auto whitespace-pre-wrap mt-2 bg-neutral-50 dark:bg-neutral-900 p-2 rounded\">\n {event.stackTrace.split('\\n').slice(0, 5).join('\\n')}\n </pre>\n )}\n </div>\n <span className=\"text-xs text-neutral-400 whitespace-nowrap ml-4\">\n {new Date(event.createdAt).toLocaleString()}\n </span>\n </div>\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}\n","/**\n * @spfn/monitor - Log Viewer Component\n *\n * Searchable, filterable log list with expandable metadata\n */\n\nimport { useState, useEffect } from 'react';\nimport { monitorApi } from '@spfn/monitor';\n\nconst LEVEL_BADGE: Record<string, string> = {\n debug: 'bg-neutral-100 text-neutral-600 dark:bg-neutral-800 dark:text-neutral-400',\n info: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400',\n warn: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400',\n error: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400',\n fatal: 'bg-red-200 text-red-800 dark:bg-red-900/50 dark:text-red-300',\n};\n\nexport function LogViewer()\n{\n const [level, setLevel] = useState('');\n const [search, setSearch] = useState('');\n const [logs, setLogs] = useState<any[]>([]);\n const [isLoading, setIsLoading] = useState(true);\n const [expanded, setExpanded] = useState<Set<number>>(new Set());\n\n useEffect(() =>\n {\n let cancelled = false;\n setIsLoading(true);\n\n monitorApi.listLogs.call({\n query: {\n ...(level ? { level } : {}),\n ...(search ? { search } : {}),\n limit: 100,\n },\n }).then((data) =>\n {\n if (!cancelled)\n {\n setLogs(data as any[]);\n setIsLoading(false);\n }\n }).catch(() =>\n {\n if (!cancelled)\n {\n setIsLoading(false);\n }\n });\n\n return () => { cancelled = true; };\n }, [level, search]);\n\n function toggleExpand(id: number)\n {\n setExpanded((prev) =>\n {\n const next = new Set(prev);\n if (next.has(id))\n {\n next.delete(id);\n }\n else\n {\n next.add(id);\n }\n\n return next;\n });\n }\n\n return (\n <div className=\"space-y-4\">\n {/* Filters */}\n <div className=\"flex gap-3\">\n <select\n value={level}\n onChange={(e) => setLevel(e.target.value)}\n className=\"rounded border border-neutral-300 dark:border-neutral-700 bg-white dark:bg-neutral-900 px-3 py-1.5 text-sm\"\n >\n <option value=\"\">All levels</option>\n <option value=\"debug\">Debug</option>\n <option value=\"info\">Info</option>\n <option value=\"warn\">Warn</option>\n <option value=\"error\">Error</option>\n <option value=\"fatal\">Fatal</option>\n </select>\n <input\n type=\"text\"\n placeholder=\"Search logs...\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n className=\"rounded border border-neutral-300 dark:border-neutral-700 bg-white dark:bg-neutral-900 px-3 py-1.5 text-sm flex-1\"\n />\n </div>\n\n {/* Log List */}\n {isLoading ? (\n <div className=\"text-sm text-neutral-500\">Loading...</div>\n ) : logs.length === 0 ? (\n <div className=\"text-sm text-neutral-500 py-8 text-center\">No logs found</div>\n ) : (\n <div className=\"space-y-1\">\n {logs.map((log: any) => (\n <div\n key={log.id}\n className=\"rounded border border-neutral-200 dark:border-neutral-800 text-sm\"\n >\n <div\n onClick={() => log.metadata && toggleExpand(log.id)}\n className={`flex items-start gap-3 p-2 ${log.metadata ? 'cursor-pointer hover:bg-neutral-50 dark:hover:bg-neutral-800/50' : ''}`}\n >\n <span className={`inline-block px-1.5 py-0.5 rounded text-xs font-medium ${LEVEL_BADGE[log.level] ?? ''}`}>\n {log.level}\n </span>\n <span className=\"flex-1 text-neutral-800 dark:text-neutral-200\">\n {log.message}\n </span>\n {log.source && (\n <span className=\"text-xs text-neutral-400 font-mono\">{log.source}</span>\n )}\n <span className=\"text-xs text-neutral-400 whitespace-nowrap\">\n {new Date(log.createdAt).toLocaleTimeString()}\n </span>\n </div>\n {expanded.has(log.id) && log.metadata && (\n <pre className=\"px-3 py-2 text-xs bg-neutral-50 dark:bg-neutral-900 border-t border-neutral-200 dark:border-neutral-800 overflow-x-auto\">\n {JSON.stringify(log.metadata, null, 2)}\n </pre>\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":["useState","useState","useEffect","monitorApi","jsx","jsxs","useState","useEffect","useCallback","monitorApi","jsx","jsxs","useState","useEffect","monitorApi","jsx","jsxs","jsx","jsxs","useState"]}