@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.
Files changed (167) hide show
  1. package/README.md +14 -4
  2. package/dist/{hono-Bz9PQQzj.mjs → hono-Bw-ey6CT.mjs} +2 -2
  3. package/dist/{hono-Bz9PQQzj.mjs.map → hono-Bw-ey6CT.mjs.map} +1 -1
  4. package/dist/{hono-Z_JIICmt.cjs → hono-CPHX7FAm.cjs} +2 -2
  5. package/dist/{hono-Z_JIICmt.cjs.map → hono-CPHX7FAm.cjs.map} +1 -1
  6. package/dist/{http-Crsriv32.mjs → http-BIvr0YdI.mjs} +1 -1
  7. package/dist/{http-Crsriv32.mjs.map → http-BIvr0YdI.mjs.map} +1 -1
  8. package/dist/{http-JycFRfSe.cjs → http-BsqmhurZ.cjs} +1 -1
  9. package/dist/{http-JycFRfSe.cjs.map → http-BsqmhurZ.cjs.map} +1 -1
  10. package/dist/{http-D5kn3_Mt.d.mts → http-DULM1u2L.d.cts} +3 -3
  11. package/dist/{http-D5kn3_Mt.d.mts.map → http-DULM1u2L.d.cts.map} +1 -1
  12. package/dist/{http-Dvca6fC3.d.cts → http-GB-Nlvwc.d.mts} +3 -3
  13. package/dist/{http-Dvca6fC3.d.cts.map → http-GB-Nlvwc.d.mts.map} +1 -1
  14. package/dist/instrumentation/hono.cjs +2 -2
  15. package/dist/instrumentation/hono.mjs +2 -2
  16. package/dist/instrumentation/http.cjs +1 -1
  17. package/dist/instrumentation/http.d.cts +1 -1
  18. package/dist/instrumentation/http.d.mts +1 -1
  19. package/dist/instrumentation/http.mjs +1 -1
  20. package/dist/instrumentation/index.cjs +5 -5
  21. package/dist/instrumentation/index.d.cts +3 -3
  22. package/dist/instrumentation/index.d.mts +3 -3
  23. package/dist/instrumentation/index.mjs +5 -5
  24. package/dist/instrumentation/middleware.cjs +2 -2
  25. package/dist/instrumentation/middleware.d.cts +1 -1
  26. package/dist/instrumentation/middleware.d.mts +1 -1
  27. package/dist/instrumentation/middleware.mjs +2 -2
  28. package/dist/instrumentation/otel.cjs +2 -2
  29. package/dist/instrumentation/otel.d.cts +1 -1
  30. package/dist/instrumentation/otel.d.mts +1 -1
  31. package/dist/instrumentation/otel.mjs +2 -2
  32. package/dist/instrumentation/setup.cjs +1 -1
  33. package/dist/instrumentation/setup.mjs +1 -1
  34. package/dist/logger/console.d.cts.map +1 -1
  35. package/dist/{middleware-D3v5SnSs.cjs → middleware-Bcq6XXGG.cjs} +3 -3
  36. package/dist/middleware-Bcq6XXGG.cjs.map +1 -0
  37. package/dist/{middleware-Dy1ctr2N.mjs → middleware-D1XEHvyy.mjs} +3 -3
  38. package/dist/middleware-D1XEHvyy.mjs.map +1 -0
  39. package/dist/{middleware-CA84nsNN.d.cts → middleware-PeyS-9Ny.d.cts} +2 -2
  40. package/dist/{middleware-CA84nsNN.d.cts.map → middleware-PeyS-9Ny.d.cts.map} +1 -1
  41. package/dist/{middleware-DAceVpfw.d.mts → middleware-oHvo_ZgU.d.mts} +2 -2
  42. package/dist/{middleware-DAceVpfw.d.mts.map → middleware-oHvo_ZgU.d.mts.map} +1 -1
  43. package/dist/{otel-DPKNyJ0t.mjs → otel-B9bNoeZ2.mjs} +4 -4
  44. package/dist/otel-B9bNoeZ2.mjs.map +1 -0
  45. package/dist/{otel-1bJPb2Z7.d.mts → otel-Bk89b1lX.d.cts} +3 -3
  46. package/dist/{otel-1bJPb2Z7.d.mts.map → otel-Bk89b1lX.d.cts.map} +1 -1
  47. package/dist/{otel-DuiBeBPU.cjs → otel-BzjOtVxZ.cjs} +4 -4
  48. package/dist/otel-BzjOtVxZ.cjs.map +1 -0
  49. package/dist/{otel-SBVoVEHr.d.cts → otel-lPMsM26e.d.mts} +3 -3
  50. package/dist/{otel-SBVoVEHr.d.cts.map → otel-lPMsM26e.d.mts.map} +1 -1
  51. package/dist/otlp/hono.cjs +3 -3
  52. package/dist/otlp/hono.mjs +3 -3
  53. package/dist/otlp/index.cjs +3 -3
  54. package/dist/otlp/index.mjs +3 -3
  55. package/dist/otlp/receiver.cjs +3 -3
  56. package/dist/otlp/receiver.mjs +3 -3
  57. package/dist/otlp/transformer.cjs +2 -2
  58. package/dist/otlp/transformer.mjs +2 -2
  59. package/dist/otlp/types.cjs +1 -1
  60. package/dist/otlp/types.mjs +1 -1
  61. package/dist/{receiver-q4f7eirs.mjs → receiver-D46dfkYI.mjs} +2 -2
  62. package/dist/{receiver-q4f7eirs.mjs.map → receiver-D46dfkYI.mjs.map} +1 -1
  63. package/dist/{receiver-BW_4go_X.cjs → receiver-slCoAFat.cjs} +2 -2
  64. package/dist/{receiver-BW_4go_X.cjs.map → receiver-slCoAFat.cjs.map} +1 -1
  65. package/dist/{setup-BhCp2LUp.mjs → setup-DC9JMEYZ.mjs} +1 -1
  66. package/dist/{setup-BhCp2LUp.mjs.map → setup-DC9JMEYZ.mjs.map} +1 -1
  67. package/dist/{setup-_uT4L_x5.cjs → setup-DufxQpi9.cjs} +1 -1
  68. package/dist/{setup-_uT4L_x5.cjs.map → setup-DufxQpi9.cjs.map} +1 -1
  69. package/dist/storage/kysely.cjs +48 -38
  70. package/dist/storage/kysely.cjs.map +1 -1
  71. package/dist/storage/kysely.d.cts +29 -9
  72. package/dist/storage/kysely.d.cts.map +1 -1
  73. package/dist/storage/kysely.d.mts +29 -9
  74. package/dist/storage/kysely.d.mts.map +1 -1
  75. package/dist/storage/kysely.mjs +48 -38
  76. package/dist/storage/kysely.mjs.map +1 -1
  77. package/dist/tracing-BX8uY2nR.d.cts.map +1 -1
  78. package/dist/tracing-PnECEbEu.d.mts.map +1 -1
  79. package/dist/{transformer-CFrJQkUg.mjs → transformer-CFQ4WFk6.mjs} +2 -2
  80. package/dist/{transformer-CFrJQkUg.mjs.map → transformer-CFQ4WFk6.mjs.map} +1 -1
  81. package/dist/{transformer-DvDYyFRQ.cjs → transformer-Csl6fbgv.cjs} +2 -2
  82. package/dist/{transformer-DvDYyFRQ.cjs.map → transformer-Csl6fbgv.cjs.map} +1 -1
  83. package/dist/types-B1EIa_cQ.d.mts.map +1 -1
  84. package/dist/{types-BBeTDpHr.cjs → types-BMiHgsi4.cjs} +1 -1
  85. package/dist/{types-BBeTDpHr.cjs.map → types-BMiHgsi4.cjs.map} +1 -1
  86. package/dist/{types-CXYLhG_G.mjs → types-Cj59nqy7.mjs} +1 -1
  87. package/dist/{types-CXYLhG_G.mjs.map → types-Cj59nqy7.mjs.map} +1 -1
  88. package/dist/types-l7Rlb5Mg.d.cts.map +1 -1
  89. package/package.json +6 -3
  90. package/CHANGELOG.md +0 -51
  91. package/dist/middleware-D3v5SnSs.cjs.map +0 -1
  92. package/dist/middleware-Dy1ctr2N.mjs.map +0 -1
  93. package/dist/otel-DPKNyJ0t.mjs.map +0 -1
  94. package/dist/otel-DuiBeBPU.cjs.map +0 -1
  95. package/src/Telescope.ts +0 -561
  96. package/src/__tests__/Telescope.spec.ts +0 -438
  97. package/src/__tests__/pino-transport.spec.ts +0 -115
  98. package/src/__tests__/redact.spec.ts +0 -336
  99. package/src/adapters/__tests__/lambda.spec.ts +0 -318
  100. package/src/adapters/__tests__/middy.spec.ts +0 -741
  101. package/src/adapters/index.ts +0 -22
  102. package/src/adapters/lambda.ts +0 -432
  103. package/src/adapters/middy.ts +0 -402
  104. package/src/adapters/types.ts +0 -181
  105. package/src/core.ts +0 -51
  106. package/src/index.ts +0 -34
  107. package/src/instrumentation/__tests__/core.spec.ts +0 -248
  108. package/src/instrumentation/__tests__/hono.spec.ts +0 -348
  109. package/src/instrumentation/__tests__/middleware.spec.ts +0 -528
  110. package/src/instrumentation/__tests__/otel.spec.ts +0 -291
  111. package/src/instrumentation/__tests__/tracing.spec.ts +0 -208
  112. package/src/instrumentation/core.ts +0 -228
  113. package/src/instrumentation/hono.ts +0 -270
  114. package/src/instrumentation/http.ts +0 -306
  115. package/src/instrumentation/index.ts +0 -90
  116. package/src/instrumentation/middleware.ts +0 -297
  117. package/src/instrumentation/otel.ts +0 -271
  118. package/src/instrumentation/setup.ts +0 -246
  119. package/src/instrumentation/tracing.ts +0 -145
  120. package/src/logger/__tests__/console.spec.ts +0 -366
  121. package/src/logger/__tests__/pino.spec.ts +0 -217
  122. package/src/logger/console.ts +0 -230
  123. package/src/logger/pino.ts +0 -201
  124. package/src/metrics/MetricsAggregator.ts +0 -555
  125. package/src/metrics/__tests__/MetricsAggregator.spec.ts +0 -298
  126. package/src/metrics/index.ts +0 -2
  127. package/src/metrics/storage/InMemoryMetricsStorage.ts +0 -380
  128. package/src/metrics/storage/MetricsStorage.ts +0 -74
  129. package/src/metrics/storage/index.ts +0 -3
  130. package/src/metrics/storage/types.ts +0 -243
  131. package/src/otlp/__tests__/receiver.spec.ts +0 -367
  132. package/src/otlp/__tests__/transformer.spec.ts +0 -455
  133. package/src/otlp/hono.ts +0 -120
  134. package/src/otlp/index.ts +0 -22
  135. package/src/otlp/receiver.ts +0 -162
  136. package/src/otlp/transformer.ts +0 -367
  137. package/src/otlp/types.ts +0 -364
  138. package/src/redact.ts +0 -116
  139. package/src/server/__tests__/hono.spec.ts +0 -814
  140. package/src/server/hono.ts +0 -442
  141. package/src/storage/__tests__/kysely.spec.ts +0 -715
  142. package/src/storage/__tests__/memory.spec.ts +0 -694
  143. package/src/storage/kysely.ts +0 -599
  144. package/src/storage/memory.ts +0 -204
  145. package/src/types.ts +0 -447
  146. package/src/ui-assets.ts +0 -40
  147. package/tsconfig.json +0 -9
  148. package/ui/CHANGELOG.md +0 -7
  149. package/ui/index.html +0 -12
  150. package/ui/node_modules/.bin/tsc +0 -21
  151. package/ui/node_modules/.bin/tsserver +0 -21
  152. package/ui/node_modules/.bin/vite +0 -21
  153. package/ui/package.json +0 -24
  154. package/ui/src/App.tsx +0 -595
  155. package/ui/src/api.ts +0 -88
  156. package/ui/src/components/ExceptionDetail.tsx +0 -100
  157. package/ui/src/components/FilterBar.tsx +0 -162
  158. package/ui/src/components/LogDetail.tsx +0 -91
  159. package/ui/src/components/RequestDetail.tsx +0 -143
  160. package/ui/src/main.tsx +0 -10
  161. package/ui/src/styles.css +0 -11
  162. package/ui/src/types.ts +0 -63
  163. package/ui/src/vite-env.d.ts +0 -1
  164. package/ui/src/vite-plugin-gkm-config.ts +0 -54
  165. package/ui/tsconfig.json +0 -20
  166. package/ui/tsconfig.tsbuildinfo +0 -1
  167. 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">&#128301;</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
- }