@geekmidas/telescope 1.1.1 → 10.0.0-alpha.0
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/README.md +14 -4
- package/dist/{hono-Bz9PQQzj.mjs → hono-Bw-ey6CT.mjs} +2 -2
- package/dist/{hono-Bz9PQQzj.mjs.map → hono-Bw-ey6CT.mjs.map} +1 -1
- package/dist/{hono-Z_JIICmt.cjs → hono-CPHX7FAm.cjs} +2 -2
- package/dist/{hono-Z_JIICmt.cjs.map → hono-CPHX7FAm.cjs.map} +1 -1
- package/dist/{http-Crsriv32.mjs → http-BIvr0YdI.mjs} +1 -1
- package/dist/{http-Crsriv32.mjs.map → http-BIvr0YdI.mjs.map} +1 -1
- package/dist/{http-JycFRfSe.cjs → http-BsqmhurZ.cjs} +1 -1
- package/dist/{http-JycFRfSe.cjs.map → http-BsqmhurZ.cjs.map} +1 -1
- package/dist/{http-D5kn3_Mt.d.mts → http-DULM1u2L.d.cts} +3 -3
- package/dist/{http-D5kn3_Mt.d.mts.map → http-DULM1u2L.d.cts.map} +1 -1
- package/dist/{http-Dvca6fC3.d.cts → http-GB-Nlvwc.d.mts} +3 -3
- package/dist/{http-Dvca6fC3.d.cts.map → http-GB-Nlvwc.d.mts.map} +1 -1
- package/dist/instrumentation/hono.cjs +2 -2
- package/dist/instrumentation/hono.mjs +2 -2
- package/dist/instrumentation/http.cjs +1 -1
- package/dist/instrumentation/http.d.cts +1 -1
- package/dist/instrumentation/http.d.mts +1 -1
- package/dist/instrumentation/http.mjs +1 -1
- package/dist/instrumentation/index.cjs +5 -5
- package/dist/instrumentation/index.d.cts +3 -3
- package/dist/instrumentation/index.d.mts +3 -3
- package/dist/instrumentation/index.mjs +5 -5
- package/dist/instrumentation/middleware.cjs +2 -2
- package/dist/instrumentation/middleware.d.cts +1 -1
- package/dist/instrumentation/middleware.d.mts +1 -1
- package/dist/instrumentation/middleware.mjs +2 -2
- package/dist/instrumentation/otel.cjs +2 -2
- package/dist/instrumentation/otel.d.cts +1 -1
- package/dist/instrumentation/otel.d.mts +1 -1
- package/dist/instrumentation/otel.mjs +2 -2
- package/dist/instrumentation/setup.cjs +1 -1
- package/dist/instrumentation/setup.mjs +1 -1
- package/dist/logger/console.d.cts.map +1 -1
- package/dist/{middleware-D3v5SnSs.cjs → middleware-Bcq6XXGG.cjs} +3 -3
- package/dist/middleware-Bcq6XXGG.cjs.map +1 -0
- package/dist/{middleware-Dy1ctr2N.mjs → middleware-D1XEHvyy.mjs} +3 -3
- package/dist/middleware-D1XEHvyy.mjs.map +1 -0
- package/dist/{middleware-CA84nsNN.d.cts → middleware-PeyS-9Ny.d.cts} +2 -2
- package/dist/{middleware-CA84nsNN.d.cts.map → middleware-PeyS-9Ny.d.cts.map} +1 -1
- package/dist/{middleware-DAceVpfw.d.mts → middleware-oHvo_ZgU.d.mts} +2 -2
- package/dist/{middleware-DAceVpfw.d.mts.map → middleware-oHvo_ZgU.d.mts.map} +1 -1
- package/dist/{otel-DPKNyJ0t.mjs → otel-B9bNoeZ2.mjs} +4 -4
- package/dist/otel-B9bNoeZ2.mjs.map +1 -0
- package/dist/{otel-1bJPb2Z7.d.mts → otel-Bk89b1lX.d.cts} +3 -3
- package/dist/{otel-1bJPb2Z7.d.mts.map → otel-Bk89b1lX.d.cts.map} +1 -1
- package/dist/{otel-DuiBeBPU.cjs → otel-BzjOtVxZ.cjs} +4 -4
- package/dist/otel-BzjOtVxZ.cjs.map +1 -0
- package/dist/{otel-SBVoVEHr.d.cts → otel-lPMsM26e.d.mts} +3 -3
- package/dist/{otel-SBVoVEHr.d.cts.map → otel-lPMsM26e.d.mts.map} +1 -1
- package/dist/otlp/hono.cjs +3 -3
- package/dist/otlp/hono.mjs +3 -3
- package/dist/otlp/index.cjs +3 -3
- package/dist/otlp/index.mjs +3 -3
- package/dist/otlp/receiver.cjs +3 -3
- package/dist/otlp/receiver.mjs +3 -3
- package/dist/otlp/transformer.cjs +2 -2
- package/dist/otlp/transformer.mjs +2 -2
- package/dist/otlp/types.cjs +1 -1
- package/dist/otlp/types.mjs +1 -1
- package/dist/{receiver-q4f7eirs.mjs → receiver-D46dfkYI.mjs} +2 -2
- package/dist/{receiver-q4f7eirs.mjs.map → receiver-D46dfkYI.mjs.map} +1 -1
- package/dist/{receiver-BW_4go_X.cjs → receiver-slCoAFat.cjs} +2 -2
- package/dist/{receiver-BW_4go_X.cjs.map → receiver-slCoAFat.cjs.map} +1 -1
- package/dist/{setup-BhCp2LUp.mjs → setup-DC9JMEYZ.mjs} +1 -1
- package/dist/{setup-BhCp2LUp.mjs.map → setup-DC9JMEYZ.mjs.map} +1 -1
- package/dist/{setup-_uT4L_x5.cjs → setup-DufxQpi9.cjs} +1 -1
- package/dist/{setup-_uT4L_x5.cjs.map → setup-DufxQpi9.cjs.map} +1 -1
- package/dist/storage/kysely.cjs +48 -38
- package/dist/storage/kysely.cjs.map +1 -1
- package/dist/storage/kysely.d.cts +29 -9
- package/dist/storage/kysely.d.cts.map +1 -1
- package/dist/storage/kysely.d.mts +29 -9
- package/dist/storage/kysely.d.mts.map +1 -1
- package/dist/storage/kysely.mjs +48 -38
- package/dist/storage/kysely.mjs.map +1 -1
- package/dist/tracing-BX8uY2nR.d.cts.map +1 -1
- package/dist/tracing-PnECEbEu.d.mts.map +1 -1
- package/dist/{transformer-CFrJQkUg.mjs → transformer-CFQ4WFk6.mjs} +2 -2
- package/dist/{transformer-CFrJQkUg.mjs.map → transformer-CFQ4WFk6.mjs.map} +1 -1
- package/dist/{transformer-DvDYyFRQ.cjs → transformer-Csl6fbgv.cjs} +2 -2
- package/dist/{transformer-DvDYyFRQ.cjs.map → transformer-Csl6fbgv.cjs.map} +1 -1
- package/dist/types-B1EIa_cQ.d.mts.map +1 -1
- package/dist/{types-BBeTDpHr.cjs → types-BMiHgsi4.cjs} +1 -1
- package/dist/{types-BBeTDpHr.cjs.map → types-BMiHgsi4.cjs.map} +1 -1
- package/dist/{types-CXYLhG_G.mjs → types-Cj59nqy7.mjs} +1 -1
- package/dist/{types-CXYLhG_G.mjs.map → types-Cj59nqy7.mjs.map} +1 -1
- package/dist/types-l7Rlb5Mg.d.cts.map +1 -1
- package/package.json +6 -3
- package/CHANGELOG.md +0 -51
- package/dist/middleware-D3v5SnSs.cjs.map +0 -1
- package/dist/middleware-Dy1ctr2N.mjs.map +0 -1
- package/dist/otel-DPKNyJ0t.mjs.map +0 -1
- package/dist/otel-DuiBeBPU.cjs.map +0 -1
- package/src/Telescope.ts +0 -561
- package/src/__tests__/Telescope.spec.ts +0 -438
- package/src/__tests__/pino-transport.spec.ts +0 -115
- package/src/__tests__/redact.spec.ts +0 -336
- package/src/adapters/__tests__/lambda.spec.ts +0 -318
- package/src/adapters/__tests__/middy.spec.ts +0 -741
- package/src/adapters/index.ts +0 -22
- package/src/adapters/lambda.ts +0 -432
- package/src/adapters/middy.ts +0 -402
- package/src/adapters/types.ts +0 -181
- package/src/core.ts +0 -51
- package/src/index.ts +0 -34
- package/src/instrumentation/__tests__/core.spec.ts +0 -248
- package/src/instrumentation/__tests__/hono.spec.ts +0 -348
- package/src/instrumentation/__tests__/middleware.spec.ts +0 -528
- package/src/instrumentation/__tests__/otel.spec.ts +0 -291
- package/src/instrumentation/__tests__/tracing.spec.ts +0 -208
- package/src/instrumentation/core.ts +0 -228
- package/src/instrumentation/hono.ts +0 -270
- package/src/instrumentation/http.ts +0 -306
- package/src/instrumentation/index.ts +0 -90
- package/src/instrumentation/middleware.ts +0 -297
- package/src/instrumentation/otel.ts +0 -271
- package/src/instrumentation/setup.ts +0 -246
- package/src/instrumentation/tracing.ts +0 -145
- package/src/logger/__tests__/console.spec.ts +0 -366
- package/src/logger/__tests__/pino.spec.ts +0 -217
- package/src/logger/console.ts +0 -230
- package/src/logger/pino.ts +0 -201
- package/src/metrics/MetricsAggregator.ts +0 -555
- package/src/metrics/__tests__/MetricsAggregator.spec.ts +0 -298
- package/src/metrics/index.ts +0 -2
- package/src/metrics/storage/InMemoryMetricsStorage.ts +0 -380
- package/src/metrics/storage/MetricsStorage.ts +0 -74
- package/src/metrics/storage/index.ts +0 -3
- package/src/metrics/storage/types.ts +0 -243
- package/src/otlp/__tests__/receiver.spec.ts +0 -367
- package/src/otlp/__tests__/transformer.spec.ts +0 -455
- package/src/otlp/hono.ts +0 -120
- package/src/otlp/index.ts +0 -22
- package/src/otlp/receiver.ts +0 -162
- package/src/otlp/transformer.ts +0 -367
- package/src/otlp/types.ts +0 -364
- package/src/redact.ts +0 -116
- package/src/server/__tests__/hono.spec.ts +0 -814
- package/src/server/hono.ts +0 -442
- package/src/storage/__tests__/kysely.spec.ts +0 -715
- package/src/storage/__tests__/memory.spec.ts +0 -694
- package/src/storage/kysely.ts +0 -599
- package/src/storage/memory.ts +0 -204
- package/src/types.ts +0 -447
- package/src/ui-assets.ts +0 -40
- package/tsconfig.json +0 -9
- package/ui/CHANGELOG.md +0 -7
- package/ui/index.html +0 -12
- package/ui/node_modules/.bin/tsc +0 -21
- package/ui/node_modules/.bin/tsserver +0 -21
- package/ui/node_modules/.bin/vite +0 -21
- package/ui/package.json +0 -24
- package/ui/src/App.tsx +0 -595
- package/ui/src/api.ts +0 -88
- package/ui/src/components/ExceptionDetail.tsx +0 -100
- package/ui/src/components/FilterBar.tsx +0 -162
- package/ui/src/components/LogDetail.tsx +0 -91
- package/ui/src/components/RequestDetail.tsx +0 -143
- package/ui/src/main.tsx +0 -10
- package/ui/src/styles.css +0 -11
- package/ui/src/types.ts +0 -63
- package/ui/src/vite-env.d.ts +0 -1
- package/ui/src/vite-plugin-gkm-config.ts +0 -54
- package/ui/tsconfig.json +0 -20
- package/ui/tsconfig.tsbuildinfo +0 -1
- package/ui/vite.config.ts +0 -13
package/ui/src/App.tsx
DELETED
|
@@ -1,595 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
-
import * as api from './api';
|
|
3
|
-
import { ExceptionDetail } from './components/ExceptionDetail';
|
|
4
|
-
import {
|
|
5
|
-
type ExceptionFilters,
|
|
6
|
-
FilterBar,
|
|
7
|
-
type LogFilters,
|
|
8
|
-
type RequestFilters,
|
|
9
|
-
} from './components/FilterBar';
|
|
10
|
-
import { LogDetail } from './components/LogDetail';
|
|
11
|
-
import { RequestDetail } from './components/RequestDetail';
|
|
12
|
-
import type {
|
|
13
|
-
ExceptionEntry,
|
|
14
|
-
LogEntry,
|
|
15
|
-
RequestEntry,
|
|
16
|
-
Tab,
|
|
17
|
-
TelescopeStats,
|
|
18
|
-
WebSocketMessage,
|
|
19
|
-
} from './types';
|
|
20
|
-
|
|
21
|
-
const DEFAULT_REQUEST_FILTERS: RequestFilters = {
|
|
22
|
-
search: '',
|
|
23
|
-
method: '',
|
|
24
|
-
status: '',
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
const DEFAULT_LOG_FILTERS: LogFilters = {
|
|
28
|
-
search: '',
|
|
29
|
-
level: '',
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const DEFAULT_EXCEPTION_FILTERS: ExceptionFilters = {
|
|
33
|
-
search: '',
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export function App() {
|
|
37
|
-
const [activeTab, setActiveTab] = useState<Tab>('requests');
|
|
38
|
-
const [stats, setStats] = useState<TelescopeStats | null>(null);
|
|
39
|
-
const [requests, setRequests] = useState<RequestEntry[]>([]);
|
|
40
|
-
const [exceptions, setExceptions] = useState<ExceptionEntry[]>([]);
|
|
41
|
-
const [logs, setLogs] = useState<LogEntry[]>([]);
|
|
42
|
-
const [loading, setLoading] = useState(true);
|
|
43
|
-
const [connected, setConnected] = useState(false);
|
|
44
|
-
const [selectedRequest, setSelectedRequest] = useState<RequestEntry | null>(
|
|
45
|
-
null,
|
|
46
|
-
);
|
|
47
|
-
const [selectedException, setSelectedException] =
|
|
48
|
-
useState<ExceptionEntry | null>(null);
|
|
49
|
-
const [selectedLog, setSelectedLog] = useState<LogEntry | null>(null);
|
|
50
|
-
|
|
51
|
-
// Filter state
|
|
52
|
-
const [requestFilters, setRequestFilters] = useState<RequestFilters>(
|
|
53
|
-
DEFAULT_REQUEST_FILTERS,
|
|
54
|
-
);
|
|
55
|
-
const [logFilters, setLogFilters] = useState<LogFilters>(DEFAULT_LOG_FILTERS);
|
|
56
|
-
const [exceptionFilters, setExceptionFilters] = useState<ExceptionFilters>(
|
|
57
|
-
DEFAULT_EXCEPTION_FILTERS,
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
// Track if we have active filters to show filtered count
|
|
61
|
-
const hasRequestFilters =
|
|
62
|
-
requestFilters.search || requestFilters.method || requestFilters.status;
|
|
63
|
-
const hasLogFilters = logFilters.search || logFilters.level;
|
|
64
|
-
const hasExceptionFilters = exceptionFilters.search;
|
|
65
|
-
|
|
66
|
-
// Debounce timer ref
|
|
67
|
-
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
68
|
-
|
|
69
|
-
// Load initial data and stats
|
|
70
|
-
const loadInitialData = useCallback(async () => {
|
|
71
|
-
try {
|
|
72
|
-
const [statsData, requestsData, exceptionsData, logsData] =
|
|
73
|
-
await Promise.all([
|
|
74
|
-
api.getStats(),
|
|
75
|
-
api.getRequests({ limit: 100 }),
|
|
76
|
-
api.getExceptions({ limit: 100 }),
|
|
77
|
-
api.getLogs({ limit: 100 }),
|
|
78
|
-
]);
|
|
79
|
-
|
|
80
|
-
setStats(statsData);
|
|
81
|
-
setRequests(requestsData);
|
|
82
|
-
setExceptions(exceptionsData);
|
|
83
|
-
setLogs(logsData);
|
|
84
|
-
} catch (_error) {
|
|
85
|
-
} finally {
|
|
86
|
-
setLoading(false);
|
|
87
|
-
}
|
|
88
|
-
}, []);
|
|
89
|
-
|
|
90
|
-
useEffect(() => {
|
|
91
|
-
loadInitialData();
|
|
92
|
-
}, [loadInitialData]);
|
|
93
|
-
|
|
94
|
-
// Fetch requests with filters
|
|
95
|
-
const fetchRequests = useCallback(async (filters: RequestFilters) => {
|
|
96
|
-
try {
|
|
97
|
-
const data = await api.getRequests({
|
|
98
|
-
limit: 100,
|
|
99
|
-
search: filters.search || undefined,
|
|
100
|
-
method: filters.method || undefined,
|
|
101
|
-
status: filters.status || undefined,
|
|
102
|
-
});
|
|
103
|
-
setRequests(data);
|
|
104
|
-
} catch (_error) {}
|
|
105
|
-
}, []);
|
|
106
|
-
|
|
107
|
-
// Fetch logs with filters
|
|
108
|
-
const fetchLogs = useCallback(async (filters: LogFilters) => {
|
|
109
|
-
try {
|
|
110
|
-
const data = await api.getLogs({
|
|
111
|
-
limit: 100,
|
|
112
|
-
search: filters.search || undefined,
|
|
113
|
-
level: filters.level || undefined,
|
|
114
|
-
});
|
|
115
|
-
setLogs(data);
|
|
116
|
-
} catch (_error) {}
|
|
117
|
-
}, []);
|
|
118
|
-
|
|
119
|
-
// Fetch exceptions with filters
|
|
120
|
-
const fetchExceptions = useCallback(async (filters: ExceptionFilters) => {
|
|
121
|
-
try {
|
|
122
|
-
const data = await api.getExceptions({
|
|
123
|
-
limit: 100,
|
|
124
|
-
search: filters.search || undefined,
|
|
125
|
-
});
|
|
126
|
-
setExceptions(data);
|
|
127
|
-
} catch (_error) {}
|
|
128
|
-
}, []);
|
|
129
|
-
|
|
130
|
-
// Handle request filter changes with debounce
|
|
131
|
-
const handleRequestFiltersChange = useCallback(
|
|
132
|
-
(filters: RequestFilters) => {
|
|
133
|
-
setRequestFilters(filters);
|
|
134
|
-
|
|
135
|
-
if (debounceRef.current) {
|
|
136
|
-
clearTimeout(debounceRef.current);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
debounceRef.current = setTimeout(() => {
|
|
140
|
-
fetchRequests(filters);
|
|
141
|
-
}, 300);
|
|
142
|
-
},
|
|
143
|
-
[fetchRequests],
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
// Handle log filter changes with debounce
|
|
147
|
-
const handleLogFiltersChange = useCallback(
|
|
148
|
-
(filters: LogFilters) => {
|
|
149
|
-
setLogFilters(filters);
|
|
150
|
-
|
|
151
|
-
if (debounceRef.current) {
|
|
152
|
-
clearTimeout(debounceRef.current);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
debounceRef.current = setTimeout(() => {
|
|
156
|
-
fetchLogs(filters);
|
|
157
|
-
}, 300);
|
|
158
|
-
},
|
|
159
|
-
[fetchLogs],
|
|
160
|
-
);
|
|
161
|
-
|
|
162
|
-
// Handle exception filter changes with debounce
|
|
163
|
-
const handleExceptionFiltersChange = useCallback(
|
|
164
|
-
(filters: ExceptionFilters) => {
|
|
165
|
-
setExceptionFilters(filters);
|
|
166
|
-
|
|
167
|
-
if (debounceRef.current) {
|
|
168
|
-
clearTimeout(debounceRef.current);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
debounceRef.current = setTimeout(() => {
|
|
172
|
-
fetchExceptions(filters);
|
|
173
|
-
}, 300);
|
|
174
|
-
},
|
|
175
|
-
[fetchExceptions],
|
|
176
|
-
);
|
|
177
|
-
|
|
178
|
-
// Clear filters and reload
|
|
179
|
-
const clearFilters = useCallback(() => {
|
|
180
|
-
setRequestFilters(DEFAULT_REQUEST_FILTERS);
|
|
181
|
-
setLogFilters(DEFAULT_LOG_FILTERS);
|
|
182
|
-
setExceptionFilters(DEFAULT_EXCEPTION_FILTERS);
|
|
183
|
-
|
|
184
|
-
// Reload without filters
|
|
185
|
-
fetchRequests(DEFAULT_REQUEST_FILTERS);
|
|
186
|
-
fetchLogs(DEFAULT_LOG_FILTERS);
|
|
187
|
-
fetchExceptions(DEFAULT_EXCEPTION_FILTERS);
|
|
188
|
-
}, [fetchRequests, fetchLogs, fetchExceptions]);
|
|
189
|
-
|
|
190
|
-
// Helper to check if a new WebSocket entry matches current filters
|
|
191
|
-
const matchesRequestFilters = useCallback(
|
|
192
|
-
(request: RequestEntry, filters: RequestFilters) => {
|
|
193
|
-
if (
|
|
194
|
-
filters.search &&
|
|
195
|
-
!request.path.toLowerCase().includes(filters.search.toLowerCase())
|
|
196
|
-
) {
|
|
197
|
-
return false;
|
|
198
|
-
}
|
|
199
|
-
if (filters.method && request.method !== filters.method) {
|
|
200
|
-
return false;
|
|
201
|
-
}
|
|
202
|
-
if (filters.status) {
|
|
203
|
-
const statusCategory = Math.floor(request.status / 100);
|
|
204
|
-
const filterCategory = parseInt(filters.status[0], 10);
|
|
205
|
-
if (statusCategory !== filterCategory) {
|
|
206
|
-
return false;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
return true;
|
|
210
|
-
},
|
|
211
|
-
[],
|
|
212
|
-
);
|
|
213
|
-
|
|
214
|
-
const matchesLogFilters = useCallback(
|
|
215
|
-
(log: LogEntry, filters: LogFilters) => {
|
|
216
|
-
if (
|
|
217
|
-
filters.search &&
|
|
218
|
-
!log.message.toLowerCase().includes(filters.search.toLowerCase())
|
|
219
|
-
) {
|
|
220
|
-
return false;
|
|
221
|
-
}
|
|
222
|
-
if (filters.level && log.level !== filters.level) {
|
|
223
|
-
return false;
|
|
224
|
-
}
|
|
225
|
-
return true;
|
|
226
|
-
},
|
|
227
|
-
[],
|
|
228
|
-
);
|
|
229
|
-
|
|
230
|
-
const matchesExceptionFilters = useCallback(
|
|
231
|
-
(exception: ExceptionEntry, filters: ExceptionFilters) => {
|
|
232
|
-
if (filters.search) {
|
|
233
|
-
const searchLower = filters.search.toLowerCase();
|
|
234
|
-
if (
|
|
235
|
-
!exception.name.toLowerCase().includes(searchLower) &&
|
|
236
|
-
!exception.message.toLowerCase().includes(searchLower)
|
|
237
|
-
) {
|
|
238
|
-
return false;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
return true;
|
|
242
|
-
},
|
|
243
|
-
[],
|
|
244
|
-
);
|
|
245
|
-
|
|
246
|
-
// WebSocket connection for real-time updates
|
|
247
|
-
useEffect(() => {
|
|
248
|
-
let ws: WebSocket | null = null;
|
|
249
|
-
let reconnectTimeout: ReturnType<typeof setTimeout>;
|
|
250
|
-
|
|
251
|
-
function connect() {
|
|
252
|
-
try {
|
|
253
|
-
ws = api.createWebSocket();
|
|
254
|
-
|
|
255
|
-
ws.onopen = () => {
|
|
256
|
-
setConnected(true);
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
ws.onclose = () => {
|
|
260
|
-
setConnected(false);
|
|
261
|
-
reconnectTimeout = setTimeout(connect, 3000);
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
ws.onerror = () => {
|
|
265
|
-
ws?.close();
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
ws.onmessage = (event) => {
|
|
269
|
-
try {
|
|
270
|
-
const message: WebSocketMessage = JSON.parse(event.data);
|
|
271
|
-
|
|
272
|
-
switch (message.type) {
|
|
273
|
-
case 'request': {
|
|
274
|
-
const newRequest = message.payload as RequestEntry;
|
|
275
|
-
setStats((prev) =>
|
|
276
|
-
prev ? { ...prev, requests: prev.requests + 1 } : prev,
|
|
277
|
-
);
|
|
278
|
-
// Only add to list if it matches current filters
|
|
279
|
-
setRequestFilters((currentFilters) => {
|
|
280
|
-
if (matchesRequestFilters(newRequest, currentFilters)) {
|
|
281
|
-
setRequests((prev) => [newRequest, ...prev].slice(0, 100));
|
|
282
|
-
}
|
|
283
|
-
return currentFilters;
|
|
284
|
-
});
|
|
285
|
-
break;
|
|
286
|
-
}
|
|
287
|
-
case 'exception': {
|
|
288
|
-
const newException = message.payload as ExceptionEntry;
|
|
289
|
-
setStats((prev) =>
|
|
290
|
-
prev ? { ...prev, exceptions: prev.exceptions + 1 } : prev,
|
|
291
|
-
);
|
|
292
|
-
setExceptionFilters((currentFilters) => {
|
|
293
|
-
if (matchesExceptionFilters(newException, currentFilters)) {
|
|
294
|
-
setExceptions((prev) =>
|
|
295
|
-
[newException, ...prev].slice(0, 100),
|
|
296
|
-
);
|
|
297
|
-
}
|
|
298
|
-
return currentFilters;
|
|
299
|
-
});
|
|
300
|
-
break;
|
|
301
|
-
}
|
|
302
|
-
case 'log': {
|
|
303
|
-
const newLog = message.payload as LogEntry;
|
|
304
|
-
setStats((prev) =>
|
|
305
|
-
prev ? { ...prev, logs: prev.logs + 1 } : prev,
|
|
306
|
-
);
|
|
307
|
-
setLogFilters((currentFilters) => {
|
|
308
|
-
if (matchesLogFilters(newLog, currentFilters)) {
|
|
309
|
-
setLogs((prev) => [newLog, ...prev].slice(0, 100));
|
|
310
|
-
}
|
|
311
|
-
return currentFilters;
|
|
312
|
-
});
|
|
313
|
-
break;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
} catch {
|
|
317
|
-
// Ignore parse errors
|
|
318
|
-
}
|
|
319
|
-
};
|
|
320
|
-
} catch {
|
|
321
|
-
reconnectTimeout = setTimeout(connect, 3000);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
connect();
|
|
326
|
-
|
|
327
|
-
return () => {
|
|
328
|
-
clearTimeout(reconnectTimeout);
|
|
329
|
-
ws?.close();
|
|
330
|
-
};
|
|
331
|
-
}, [matchesRequestFilters, matchesLogFilters, matchesExceptionFilters]);
|
|
332
|
-
|
|
333
|
-
const formatTime = (timestamp: string) => {
|
|
334
|
-
return new Date(timestamp).toLocaleTimeString();
|
|
335
|
-
};
|
|
336
|
-
|
|
337
|
-
const formatDuration = (ms: number) => {
|
|
338
|
-
if (ms < 1) return '<1ms';
|
|
339
|
-
if (ms < 1000) return `${Math.round(ms)}ms`;
|
|
340
|
-
return `${(ms / 1000).toFixed(2)}s`;
|
|
341
|
-
};
|
|
342
|
-
|
|
343
|
-
const getMethodColor = (method: string) => {
|
|
344
|
-
const colors: Record<string, string> = {
|
|
345
|
-
GET: 'bg-green-500/20 text-green-400',
|
|
346
|
-
POST: 'bg-blue-500/20 text-blue-400',
|
|
347
|
-
PUT: 'bg-amber-500/20 text-amber-400',
|
|
348
|
-
PATCH: 'bg-purple-500/20 text-purple-400',
|
|
349
|
-
DELETE: 'bg-red-500/20 text-red-400',
|
|
350
|
-
};
|
|
351
|
-
return colors[method] || 'bg-slate-500/20 text-slate-400';
|
|
352
|
-
};
|
|
353
|
-
|
|
354
|
-
const getStatusColor = (status: number) => {
|
|
355
|
-
if (status >= 500) return 'bg-red-500/20 text-red-400';
|
|
356
|
-
if (status >= 400) return 'bg-amber-500/20 text-amber-400';
|
|
357
|
-
if (status >= 300) return 'bg-blue-500/20 text-blue-400';
|
|
358
|
-
return 'bg-green-500/20 text-green-400';
|
|
359
|
-
};
|
|
360
|
-
|
|
361
|
-
const getLogLevelColor = (level: string) => {
|
|
362
|
-
const colors: Record<string, string> = {
|
|
363
|
-
debug: 'bg-slate-500/20 text-slate-400',
|
|
364
|
-
info: 'bg-blue-500/20 text-blue-400',
|
|
365
|
-
warn: 'bg-amber-500/20 text-amber-400',
|
|
366
|
-
error: 'bg-red-500/20 text-red-400',
|
|
367
|
-
};
|
|
368
|
-
return colors[level] || 'bg-slate-500/20 text-slate-400';
|
|
369
|
-
};
|
|
370
|
-
|
|
371
|
-
const closeDetail = () => {
|
|
372
|
-
setSelectedRequest(null);
|
|
373
|
-
setSelectedException(null);
|
|
374
|
-
setSelectedLog(null);
|
|
375
|
-
};
|
|
376
|
-
|
|
377
|
-
return (
|
|
378
|
-
<div className="flex flex-col min-h-screen bg-bg-primary font-mono text-slate-100">
|
|
379
|
-
{/* Header */}
|
|
380
|
-
<header className="bg-bg-secondary border-b border-border px-6 py-4 flex items-center justify-between">
|
|
381
|
-
<h1 className="text-xl font-semibold flex items-center gap-2">
|
|
382
|
-
<span className="text-blue-400">🔭</span> Telescope
|
|
383
|
-
</h1>
|
|
384
|
-
<div className="flex items-center gap-6">
|
|
385
|
-
{stats && (
|
|
386
|
-
<>
|
|
387
|
-
<div className="text-sm text-slate-400">
|
|
388
|
-
<span className="font-semibold text-slate-100">
|
|
389
|
-
{stats.requests}
|
|
390
|
-
</span>{' '}
|
|
391
|
-
requests
|
|
392
|
-
</div>
|
|
393
|
-
<div className="text-sm text-slate-400">
|
|
394
|
-
<span className="font-semibold text-slate-100">
|
|
395
|
-
{stats.exceptions}
|
|
396
|
-
</span>{' '}
|
|
397
|
-
exceptions
|
|
398
|
-
</div>
|
|
399
|
-
<div className="text-sm text-slate-400">
|
|
400
|
-
<span className="font-semibold text-slate-100">
|
|
401
|
-
{stats.logs}
|
|
402
|
-
</span>{' '}
|
|
403
|
-
logs
|
|
404
|
-
</div>
|
|
405
|
-
</>
|
|
406
|
-
)}
|
|
407
|
-
<div className="flex items-center gap-2 text-xs text-slate-500">
|
|
408
|
-
<span
|
|
409
|
-
className={`w-2 h-2 rounded-full ${connected ? 'bg-green-500' : 'bg-red-500'}`}
|
|
410
|
-
/>
|
|
411
|
-
{connected ? 'Live' : 'Disconnected'}
|
|
412
|
-
</div>
|
|
413
|
-
</div>
|
|
414
|
-
</header>
|
|
415
|
-
|
|
416
|
-
{/* Main */}
|
|
417
|
-
<main className="flex-1 flex flex-col">
|
|
418
|
-
{/* Tabs */}
|
|
419
|
-
<div className="flex bg-bg-secondary border-b border-border">
|
|
420
|
-
{(['requests', 'exceptions', 'logs'] as Tab[]).map((tab) => (
|
|
421
|
-
<button
|
|
422
|
-
key={tab}
|
|
423
|
-
className={`px-6 py-3 text-sm capitalize border-b-2 transition-colors ${
|
|
424
|
-
activeTab === tab
|
|
425
|
-
? 'text-blue-400 border-blue-400'
|
|
426
|
-
: 'text-slate-400 border-transparent hover:text-slate-100 hover:bg-bg-tertiary'
|
|
427
|
-
}`}
|
|
428
|
-
onClick={() => setActiveTab(tab)}
|
|
429
|
-
>
|
|
430
|
-
{tab}
|
|
431
|
-
{tab === 'requests' && hasRequestFilters && (
|
|
432
|
-
<span className="ml-2 text-xs text-slate-500">
|
|
433
|
-
({requests.length})
|
|
434
|
-
</span>
|
|
435
|
-
)}
|
|
436
|
-
{tab === 'logs' && hasLogFilters && (
|
|
437
|
-
<span className="ml-2 text-xs text-slate-500">
|
|
438
|
-
({logs.length})
|
|
439
|
-
</span>
|
|
440
|
-
)}
|
|
441
|
-
{tab === 'exceptions' && hasExceptionFilters && (
|
|
442
|
-
<span className="ml-2 text-xs text-slate-500">
|
|
443
|
-
({exceptions.length})
|
|
444
|
-
</span>
|
|
445
|
-
)}
|
|
446
|
-
</button>
|
|
447
|
-
))}
|
|
448
|
-
</div>
|
|
449
|
-
|
|
450
|
-
{/* Filter Bar */}
|
|
451
|
-
<FilterBar
|
|
452
|
-
tab={activeTab}
|
|
453
|
-
requestFilters={requestFilters}
|
|
454
|
-
logFilters={logFilters}
|
|
455
|
-
exceptionFilters={exceptionFilters}
|
|
456
|
-
onRequestFiltersChange={handleRequestFiltersChange}
|
|
457
|
-
onLogFiltersChange={handleLogFiltersChange}
|
|
458
|
-
onExceptionFiltersChange={handleExceptionFiltersChange}
|
|
459
|
-
onClear={clearFilters}
|
|
460
|
-
/>
|
|
461
|
-
|
|
462
|
-
{/* Content */}
|
|
463
|
-
<div className="flex-1 p-4 overflow-y-auto">
|
|
464
|
-
{loading ? (
|
|
465
|
-
<div className="flex items-center justify-center py-16 text-slate-500">
|
|
466
|
-
Loading...
|
|
467
|
-
</div>
|
|
468
|
-
) : activeTab === 'requests' ? (
|
|
469
|
-
requests.length === 0 ? (
|
|
470
|
-
<div className="flex flex-col items-center justify-center py-16 text-slate-500 text-center">
|
|
471
|
-
<h3 className="text-lg mb-2">
|
|
472
|
-
{hasRequestFilters
|
|
473
|
-
? 'No matching requests'
|
|
474
|
-
: 'No requests yet'}
|
|
475
|
-
</h3>
|
|
476
|
-
<p className="text-sm">
|
|
477
|
-
{hasRequestFilters
|
|
478
|
-
? 'Try adjusting your filters.'
|
|
479
|
-
: 'Requests will appear here as they are captured.'}
|
|
480
|
-
</p>
|
|
481
|
-
</div>
|
|
482
|
-
) : (
|
|
483
|
-
<div className="flex flex-col gap-2">
|
|
484
|
-
{requests.map((request) => (
|
|
485
|
-
<div
|
|
486
|
-
key={request.id}
|
|
487
|
-
className="bg-bg-secondary border border-border rounded-lg p-4 cursor-pointer transition-colors hover:border-blue-500 hover:bg-bg-tertiary flex items-center gap-4"
|
|
488
|
-
onClick={() => setSelectedRequest(request)}
|
|
489
|
-
>
|
|
490
|
-
<span
|
|
491
|
-
className={`font-semibold px-2 py-1 rounded text-xs min-w-16 text-center ${getMethodColor(request.method)}`}
|
|
492
|
-
>
|
|
493
|
-
{request.method}
|
|
494
|
-
</span>
|
|
495
|
-
<span className="flex-1 truncate">{request.path}</span>
|
|
496
|
-
<span
|
|
497
|
-
className={`text-sm px-2 py-1 rounded ${getStatusColor(request.status)}`}
|
|
498
|
-
>
|
|
499
|
-
{request.status}
|
|
500
|
-
</span>
|
|
501
|
-
<span className="text-xs text-slate-500 min-w-16 text-right">
|
|
502
|
-
{formatDuration(request.duration)}
|
|
503
|
-
</span>
|
|
504
|
-
<span className="text-xs text-slate-500 min-w-20 text-right">
|
|
505
|
-
{formatTime(request.timestamp)}
|
|
506
|
-
</span>
|
|
507
|
-
</div>
|
|
508
|
-
))}
|
|
509
|
-
</div>
|
|
510
|
-
)
|
|
511
|
-
) : activeTab === 'exceptions' ? (
|
|
512
|
-
exceptions.length === 0 ? (
|
|
513
|
-
<div className="flex flex-col items-center justify-center py-16 text-slate-500 text-center">
|
|
514
|
-
<h3 className="text-lg mb-2">
|
|
515
|
-
{hasExceptionFilters
|
|
516
|
-
? 'No matching exceptions'
|
|
517
|
-
: 'No exceptions'}
|
|
518
|
-
</h3>
|
|
519
|
-
<p className="text-sm">
|
|
520
|
-
{hasExceptionFilters
|
|
521
|
-
? 'Try adjusting your filters.'
|
|
522
|
-
: 'Exceptions will appear here when they occur.'}
|
|
523
|
-
</p>
|
|
524
|
-
</div>
|
|
525
|
-
) : (
|
|
526
|
-
<div className="flex flex-col gap-2">
|
|
527
|
-
{exceptions.map((exception) => (
|
|
528
|
-
<div
|
|
529
|
-
key={exception.id}
|
|
530
|
-
className="bg-bg-secondary border border-border rounded-lg p-4 cursor-pointer transition-colors hover:border-blue-500 hover:bg-bg-tertiary"
|
|
531
|
-
onClick={() => setSelectedException(exception)}
|
|
532
|
-
>
|
|
533
|
-
<div className="flex justify-between items-center mb-1">
|
|
534
|
-
<span className="font-semibold text-red-400">
|
|
535
|
-
{exception.name}
|
|
536
|
-
</span>
|
|
537
|
-
<span className="text-xs text-slate-500">
|
|
538
|
-
{formatTime(exception.timestamp)}
|
|
539
|
-
</span>
|
|
540
|
-
</div>
|
|
541
|
-
<span className="text-sm text-slate-400 truncate block">
|
|
542
|
-
{exception.message}
|
|
543
|
-
</span>
|
|
544
|
-
</div>
|
|
545
|
-
))}
|
|
546
|
-
</div>
|
|
547
|
-
)
|
|
548
|
-
) : logs.length === 0 ? (
|
|
549
|
-
<div className="flex flex-col items-center justify-center py-16 text-slate-500 text-center">
|
|
550
|
-
<h3 className="text-lg mb-2">
|
|
551
|
-
{hasLogFilters ? 'No matching logs' : 'No logs yet'}
|
|
552
|
-
</h3>
|
|
553
|
-
<p className="text-sm">
|
|
554
|
-
{hasLogFilters
|
|
555
|
-
? 'Try adjusting your filters.'
|
|
556
|
-
: 'Log entries will appear here as they are recorded.'}
|
|
557
|
-
</p>
|
|
558
|
-
</div>
|
|
559
|
-
) : (
|
|
560
|
-
<div className="flex flex-col gap-2">
|
|
561
|
-
{logs.map((log) => (
|
|
562
|
-
<div
|
|
563
|
-
key={log.id}
|
|
564
|
-
className="bg-bg-secondary border border-border rounded-lg p-4 cursor-pointer transition-colors hover:border-blue-500 hover:bg-bg-tertiary flex items-start gap-4"
|
|
565
|
-
onClick={() => setSelectedLog(log)}
|
|
566
|
-
>
|
|
567
|
-
<span
|
|
568
|
-
className={`font-semibold px-2 py-1 rounded text-xs min-w-14 text-center ${getLogLevelColor(log.level)}`}
|
|
569
|
-
>
|
|
570
|
-
{log.level}
|
|
571
|
-
</span>
|
|
572
|
-
<span className="flex-1 wrap-break-word">{log.message}</span>
|
|
573
|
-
<span className="text-xs text-slate-500 min-w-20 text-right">
|
|
574
|
-
{formatTime(log.timestamp)}
|
|
575
|
-
</span>
|
|
576
|
-
</div>
|
|
577
|
-
))}
|
|
578
|
-
</div>
|
|
579
|
-
)}
|
|
580
|
-
</div>
|
|
581
|
-
</main>
|
|
582
|
-
|
|
583
|
-
{/* Detail Panels */}
|
|
584
|
-
{selectedRequest && (
|
|
585
|
-
<RequestDetail request={selectedRequest} onClose={closeDetail} />
|
|
586
|
-
)}
|
|
587
|
-
|
|
588
|
-
{selectedException && (
|
|
589
|
-
<ExceptionDetail exception={selectedException} onClose={closeDetail} />
|
|
590
|
-
)}
|
|
591
|
-
|
|
592
|
-
{selectedLog && <LogDetail log={selectedLog} onClose={closeDetail} />}
|
|
593
|
-
</div>
|
|
594
|
-
);
|
|
595
|
-
}
|
package/ui/src/api.ts
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ExceptionEntry,
|
|
3
|
-
LogEntry,
|
|
4
|
-
RequestEntry,
|
|
5
|
-
TelescopeStats,
|
|
6
|
-
} from './types';
|
|
7
|
-
|
|
8
|
-
const BASE_URL = '/__telescope/api';
|
|
9
|
-
|
|
10
|
-
export interface FilterOptions {
|
|
11
|
-
limit?: number;
|
|
12
|
-
offset?: number;
|
|
13
|
-
search?: string;
|
|
14
|
-
before?: string;
|
|
15
|
-
after?: string;
|
|
16
|
-
tags?: string[];
|
|
17
|
-
method?: string;
|
|
18
|
-
status?: string;
|
|
19
|
-
level?: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
async function fetchJson<T>(path: string): Promise<T> {
|
|
23
|
-
const response = await fetch(`${BASE_URL}${path}`);
|
|
24
|
-
if (!response.ok) {
|
|
25
|
-
throw new Error(`HTTP error! status: ${response.status}`);
|
|
26
|
-
}
|
|
27
|
-
return response.json();
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function buildQueryString(options?: FilterOptions): string {
|
|
31
|
-
if (!options) return '';
|
|
32
|
-
|
|
33
|
-
const params = new URLSearchParams();
|
|
34
|
-
if (options.limit) params.set('limit', String(options.limit));
|
|
35
|
-
if (options.offset) params.set('offset', String(options.offset));
|
|
36
|
-
if (options.search) params.set('search', options.search);
|
|
37
|
-
if (options.before) params.set('before', options.before);
|
|
38
|
-
if (options.after) params.set('after', options.after);
|
|
39
|
-
if (options.tags?.length) params.set('tags', options.tags.join(','));
|
|
40
|
-
if (options.method) params.set('method', options.method);
|
|
41
|
-
if (options.status) params.set('status', options.status);
|
|
42
|
-
if (options.level) params.set('level', options.level);
|
|
43
|
-
|
|
44
|
-
const query = params.toString();
|
|
45
|
-
return query ? `?${query}` : '';
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export async function getRequests(
|
|
49
|
-
options?: FilterOptions,
|
|
50
|
-
): Promise<RequestEntry[]> {
|
|
51
|
-
return fetchJson(`/requests${buildQueryString(options)}`);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export async function getRequest(id: string): Promise<RequestEntry | null> {
|
|
55
|
-
try {
|
|
56
|
-
return await fetchJson(`/requests/${id}`);
|
|
57
|
-
} catch {
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export async function getExceptions(
|
|
63
|
-
options?: FilterOptions,
|
|
64
|
-
): Promise<ExceptionEntry[]> {
|
|
65
|
-
return fetchJson(`/exceptions${buildQueryString(options)}`);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export async function getException(id: string): Promise<ExceptionEntry | null> {
|
|
69
|
-
try {
|
|
70
|
-
return await fetchJson(`/exceptions/${id}`);
|
|
71
|
-
} catch {
|
|
72
|
-
return null;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export async function getLogs(options?: FilterOptions): Promise<LogEntry[]> {
|
|
77
|
-
return fetchJson(`/logs${buildQueryString(options)}`);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export async function getStats(): Promise<TelescopeStats> {
|
|
81
|
-
return fetchJson('/stats');
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export function createWebSocket(): WebSocket {
|
|
85
|
-
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
86
|
-
const host = window.location.host;
|
|
87
|
-
return new WebSocket(`${protocol}//${host}/__telescope/ws`);
|
|
88
|
-
}
|