@geekmidas/telescope 0.0.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.
Files changed (103) hide show
  1. package/README.md +521 -0
  2. package/dist/Telescope-B3Wd82yk.cjs +602 -0
  3. package/dist/Telescope-B3Wd82yk.cjs.map +1 -0
  4. package/dist/Telescope-C5dyDYYB.d.cts +133 -0
  5. package/dist/Telescope-D-uoZB6b.mjs +596 -0
  6. package/dist/Telescope-D-uoZB6b.mjs.map +1 -0
  7. package/dist/Telescope-DyIWgh9-.d.mts +133 -0
  8. package/dist/Telescope.cjs +3 -0
  9. package/dist/Telescope.d.cts +3 -0
  10. package/dist/Telescope.d.mts +3 -0
  11. package/dist/Telescope.mjs +3 -0
  12. package/dist/chunk-CUT6urMc.cjs +30 -0
  13. package/dist/index.cjs +5 -0
  14. package/dist/index.d.cts +4 -0
  15. package/dist/index.d.mts +4 -0
  16. package/dist/index.mjs +4 -0
  17. package/dist/logger/console.cjs +161 -0
  18. package/dist/logger/console.cjs.map +1 -0
  19. package/dist/logger/console.d.cts +109 -0
  20. package/dist/logger/console.d.mts +109 -0
  21. package/dist/logger/console.mjs +159 -0
  22. package/dist/logger/console.mjs.map +1 -0
  23. package/dist/logger/pino.cjs +118 -0
  24. package/dist/logger/pino.cjs.map +1 -0
  25. package/dist/logger/pino.d.cts +89 -0
  26. package/dist/logger/pino.d.mts +89 -0
  27. package/dist/logger/pino.mjs +116 -0
  28. package/dist/logger/pino.mjs.map +1 -0
  29. package/dist/memory-9-B9WACq.cjs +110 -0
  30. package/dist/memory-9-B9WACq.cjs.map +1 -0
  31. package/dist/memory-Cm0eevCS.d.mts +38 -0
  32. package/dist/memory-DiP1a-pp.d.cts +38 -0
  33. package/dist/memory-SdN5vtG9.mjs +104 -0
  34. package/dist/memory-SdN5vtG9.mjs.map +1 -0
  35. package/dist/server/hono.cjs +180 -0
  36. package/dist/server/hono.cjs.map +1 -0
  37. package/dist/server/hono.d.cts +26 -0
  38. package/dist/server/hono.d.mts +26 -0
  39. package/dist/server/hono.mjs +176 -0
  40. package/dist/server/hono.mjs.map +1 -0
  41. package/dist/storage/kysely.cjs +336 -0
  42. package/dist/storage/kysely.cjs.map +1 -0
  43. package/dist/storage/kysely.d.cts +161 -0
  44. package/dist/storage/kysely.d.mts +161 -0
  45. package/dist/storage/kysely.mjs +334 -0
  46. package/dist/storage/kysely.mjs.map +1 -0
  47. package/dist/storage/memory.cjs +3 -0
  48. package/dist/storage/memory.d.cts +3 -0
  49. package/dist/storage/memory.d.mts +3 -0
  50. package/dist/storage/memory.mjs +3 -0
  51. package/dist/types-BGDhFv4R.d.cts +170 -0
  52. package/dist/types-CZbzz8kx.d.mts +170 -0
  53. package/dist/types.cjs +0 -0
  54. package/dist/types.d.cts +2 -0
  55. package/dist/types.d.mts +2 -0
  56. package/dist/types.mjs +0 -0
  57. package/dist/ui-assets-D6-8TAr_.mjs +30 -0
  58. package/dist/ui-assets-D6-8TAr_.mjs.map +1 -0
  59. package/dist/ui-assets-ulevVble.cjs +48 -0
  60. package/dist/ui-assets-ulevVble.cjs.map +1 -0
  61. package/dist/ui-assets.cjs +5 -0
  62. package/dist/ui-assets.d.cts +12 -0
  63. package/dist/ui-assets.d.mts +12 -0
  64. package/dist/ui-assets.mjs +3 -0
  65. package/package.json +83 -0
  66. package/scripts/embed-ui.ts +90 -0
  67. package/src/Telescope.ts +714 -0
  68. package/src/__tests__/Telescope.spec.ts +356 -0
  69. package/src/index.ts +23 -0
  70. package/src/logger/__tests__/console.spec.ts +266 -0
  71. package/src/logger/__tests__/pino.spec.ts +217 -0
  72. package/src/logger/console.ts +230 -0
  73. package/src/logger/pino.ts +191 -0
  74. package/src/server/__tests__/hono.spec.ts +340 -0
  75. package/src/server/hono.ts +247 -0
  76. package/src/storage/__tests__/kysely.spec.ts +715 -0
  77. package/src/storage/__tests__/memory.spec.ts +411 -0
  78. package/src/storage/kysely.ts +572 -0
  79. package/src/storage/memory.ts +168 -0
  80. package/src/types.ts +188 -0
  81. package/src/ui-assets.ts +40 -0
  82. package/ui/index.html +12 -0
  83. package/ui/node_modules/.bin/browserslist +21 -0
  84. package/ui/node_modules/.bin/jiti +21 -0
  85. package/ui/node_modules/.bin/terser +21 -0
  86. package/ui/node_modules/.bin/tsc +21 -0
  87. package/ui/node_modules/.bin/tsserver +21 -0
  88. package/ui/node_modules/.bin/tsx +21 -0
  89. package/ui/node_modules/.bin/vite +21 -0
  90. package/ui/package.json +24 -0
  91. package/ui/src/App.tsx +342 -0
  92. package/ui/src/api.ts +75 -0
  93. package/ui/src/components/ExceptionDetail.tsx +100 -0
  94. package/ui/src/components/LogDetail.tsx +91 -0
  95. package/ui/src/components/RequestDetail.tsx +143 -0
  96. package/ui/src/main.tsx +10 -0
  97. package/ui/src/styles.css +10 -0
  98. package/ui/src/types.ts +63 -0
  99. package/ui/src/vite-env.d.ts +1 -0
  100. package/ui/src/vite-plugin-gkm-config.ts +54 -0
  101. package/ui/tsconfig.json +20 -0
  102. package/ui/tsconfig.tsbuildinfo +14 -0
  103. package/ui/vite.config.ts +13 -0
package/ui/index.html ADDED
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Telescope</title>
7
+ </head>
8
+ <body class="bg-slate-900 text-slate-100 antialiased">
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,21 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*|*MINGW*|*MSYS*)
6
+ if command -v cygpath > /dev/null 2>&1; then
7
+ basedir=`cygpath -w "$basedir"`
8
+ fi
9
+ ;;
10
+ esac
11
+
12
+ if [ -z "$NODE_PATH" ]; then
13
+ export NODE_PATH="/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/browserslist@4.25.2/node_modules/browserslist/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/browserslist@4.25.2/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/node_modules"
14
+ else
15
+ export NODE_PATH="/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/browserslist@4.25.2/node_modules/browserslist/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/browserslist@4.25.2/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/node_modules:$NODE_PATH"
16
+ fi
17
+ if [ -x "$basedir/node" ]; then
18
+ exec "$basedir/node" "$basedir/../../../../../node_modules/.pnpm/browserslist@4.25.2/node_modules/browserslist/cli.js" "$@"
19
+ else
20
+ exec node "$basedir/../../../../../node_modules/.pnpm/browserslist@4.25.2/node_modules/browserslist/cli.js" "$@"
21
+ fi
@@ -0,0 +1,21 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*|*MINGW*|*MSYS*)
6
+ if command -v cygpath > /dev/null 2>&1; then
7
+ basedir=`cygpath -w "$basedir"`
8
+ fi
9
+ ;;
10
+ esac
11
+
12
+ if [ -z "$NODE_PATH" ]; then
13
+ export NODE_PATH="/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/jiti@2.6.1/node_modules/jiti/lib/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/jiti@2.6.1/node_modules/jiti/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/jiti@2.6.1/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/node_modules"
14
+ else
15
+ export NODE_PATH="/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/jiti@2.6.1/node_modules/jiti/lib/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/jiti@2.6.1/node_modules/jiti/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/jiti@2.6.1/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/node_modules:$NODE_PATH"
16
+ fi
17
+ if [ -x "$basedir/node" ]; then
18
+ exec "$basedir/node" "$basedir/../../../../../node_modules/.pnpm/jiti@2.6.1/node_modules/jiti/lib/jiti-cli.mjs" "$@"
19
+ else
20
+ exec node "$basedir/../../../../../node_modules/.pnpm/jiti@2.6.1/node_modules/jiti/lib/jiti-cli.mjs" "$@"
21
+ fi
@@ -0,0 +1,21 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*|*MINGW*|*MSYS*)
6
+ if command -v cygpath > /dev/null 2>&1; then
7
+ basedir=`cygpath -w "$basedir"`
8
+ fi
9
+ ;;
10
+ esac
11
+
12
+ if [ -z "$NODE_PATH" ]; then
13
+ export NODE_PATH="/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/terser@5.43.1/node_modules/terser/bin/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/terser@5.43.1/node_modules/terser/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/terser@5.43.1/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/node_modules"
14
+ else
15
+ export NODE_PATH="/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/terser@5.43.1/node_modules/terser/bin/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/terser@5.43.1/node_modules/terser/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/terser@5.43.1/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/node_modules:$NODE_PATH"
16
+ fi
17
+ if [ -x "$basedir/node" ]; then
18
+ exec "$basedir/node" "$basedir/../../../../../node_modules/.pnpm/terser@5.43.1/node_modules/terser/bin/terser" "$@"
19
+ else
20
+ exec node "$basedir/../../../../../node_modules/.pnpm/terser@5.43.1/node_modules/terser/bin/terser" "$@"
21
+ fi
@@ -0,0 +1,21 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*|*MINGW*|*MSYS*)
6
+ if command -v cygpath > /dev/null 2>&1; then
7
+ basedir=`cygpath -w "$basedir"`
8
+ fi
9
+ ;;
10
+ esac
11
+
12
+ if [ -z "$NODE_PATH" ]; then
13
+ export NODE_PATH="/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/typescript@5.8.2/node_modules/typescript/bin/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/typescript@5.8.2/node_modules/typescript/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/typescript@5.8.2/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/node_modules"
14
+ else
15
+ export NODE_PATH="/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/typescript@5.8.2/node_modules/typescript/bin/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/typescript@5.8.2/node_modules/typescript/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/typescript@5.8.2/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/node_modules:$NODE_PATH"
16
+ fi
17
+ if [ -x "$basedir/node" ]; then
18
+ exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@"
19
+ else
20
+ exec node "$basedir/../typescript/bin/tsc" "$@"
21
+ fi
@@ -0,0 +1,21 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*|*MINGW*|*MSYS*)
6
+ if command -v cygpath > /dev/null 2>&1; then
7
+ basedir=`cygpath -w "$basedir"`
8
+ fi
9
+ ;;
10
+ esac
11
+
12
+ if [ -z "$NODE_PATH" ]; then
13
+ export NODE_PATH="/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/typescript@5.8.2/node_modules/typescript/bin/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/typescript@5.8.2/node_modules/typescript/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/typescript@5.8.2/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/node_modules"
14
+ else
15
+ export NODE_PATH="/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/typescript@5.8.2/node_modules/typescript/bin/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/typescript@5.8.2/node_modules/typescript/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/typescript@5.8.2/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/node_modules:$NODE_PATH"
16
+ fi
17
+ if [ -x "$basedir/node" ]; then
18
+ exec "$basedir/node" "$basedir/../typescript/bin/tsserver" "$@"
19
+ else
20
+ exec node "$basedir/../typescript/bin/tsserver" "$@"
21
+ fi
@@ -0,0 +1,21 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*|*MINGW*|*MSYS*)
6
+ if command -v cygpath > /dev/null 2>&1; then
7
+ basedir=`cygpath -w "$basedir"`
8
+ fi
9
+ ;;
10
+ esac
11
+
12
+ if [ -z "$NODE_PATH" ]; then
13
+ export NODE_PATH="/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/tsx@4.20.6/node_modules/tsx/dist/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/tsx@4.20.6/node_modules/tsx/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/tsx@4.20.6/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/node_modules"
14
+ else
15
+ export NODE_PATH="/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/tsx@4.20.6/node_modules/tsx/dist/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/tsx@4.20.6/node_modules/tsx/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/tsx@4.20.6/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/node_modules:$NODE_PATH"
16
+ fi
17
+ if [ -x "$basedir/node" ]; then
18
+ exec "$basedir/node" "$basedir/../../../../../node_modules/.pnpm/tsx@4.20.6/node_modules/tsx/dist/cli.mjs" "$@"
19
+ else
20
+ exec node "$basedir/../../../../../node_modules/.pnpm/tsx@4.20.6/node_modules/tsx/dist/cli.mjs" "$@"
21
+ fi
@@ -0,0 +1,21 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*|*MINGW*|*MSYS*)
6
+ if command -v cygpath > /dev/null 2>&1; then
7
+ basedir=`cygpath -w "$basedir"`
8
+ fi
9
+ ;;
10
+ esac
11
+
12
+ if [ -z "$NODE_PATH" ]; then
13
+ export NODE_PATH="/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/vite@6.3.5_@types+node@24.9.1_jiti@2.6.1_lightningcss@1.30.2_terser@5.43.1_tsx@4.20.6/node_modules/vite/bin/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/vite@6.3.5_@types+node@24.9.1_jiti@2.6.1_lightningcss@1.30.2_terser@5.43.1_tsx@4.20.6/node_modules/vite/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/vite@6.3.5_@types+node@24.9.1_jiti@2.6.1_lightningcss@1.30.2_terser@5.43.1_tsx@4.20.6/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/node_modules"
14
+ else
15
+ export NODE_PATH="/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/vite@6.3.5_@types+node@24.9.1_jiti@2.6.1_lightningcss@1.30.2_terser@5.43.1_tsx@4.20.6/node_modules/vite/bin/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/vite@6.3.5_@types+node@24.9.1_jiti@2.6.1_lightningcss@1.30.2_terser@5.43.1_tsx@4.20.6/node_modules/vite/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/vite@6.3.5_@types+node@24.9.1_jiti@2.6.1_lightningcss@1.30.2_terser@5.43.1_tsx@4.20.6/node_modules:/Users/cerberus/technanimals/toolbox/node_modules/.pnpm/node_modules:$NODE_PATH"
16
+ fi
17
+ if [ -x "$basedir/node" ]; then
18
+ exec "$basedir/node" "$basedir/../vite/bin/vite.js" "$@"
19
+ else
20
+ exec node "$basedir/../vite/bin/vite.js" "$@"
21
+ fi
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@geekmidas/telescope-ui",
3
+ "private": true,
4
+ "version": "0.0.1",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc -b && vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "react": "^19.0.0",
13
+ "react-dom": "^19.0.0"
14
+ },
15
+ "devDependencies": {
16
+ "@tailwindcss/vite": "^4.0.0",
17
+ "@types/react": "^19.0.0",
18
+ "@types/react-dom": "^19.0.0",
19
+ "@vitejs/plugin-react": "^4.3.4",
20
+ "tailwindcss": "^4.0.0",
21
+ "typescript": "~5.8.2",
22
+ "vite": "^6.0.0"
23
+ }
24
+ }
package/ui/src/App.tsx ADDED
@@ -0,0 +1,342 @@
1
+ import { useCallback, useEffect, useState } from 'react';
2
+ import * as api from './api';
3
+ import { ExceptionDetail } from './components/ExceptionDetail';
4
+ import { LogDetail } from './components/LogDetail';
5
+ import { RequestDetail } from './components/RequestDetail';
6
+ import type {
7
+ ExceptionEntry,
8
+ LogEntry,
9
+ RequestEntry,
10
+ Tab,
11
+ TelescopeStats,
12
+ WebSocketMessage,
13
+ } from './types';
14
+
15
+ export function App() {
16
+ const [activeTab, setActiveTab] = useState<Tab>('requests');
17
+ const [stats, setStats] = useState<TelescopeStats | null>(null);
18
+ const [requests, setRequests] = useState<RequestEntry[]>([]);
19
+ const [exceptions, setExceptions] = useState<ExceptionEntry[]>([]);
20
+ const [logs, setLogs] = useState<LogEntry[]>([]);
21
+ const [loading, setLoading] = useState(true);
22
+ const [connected, setConnected] = useState(false);
23
+ const [selectedRequest, setSelectedRequest] = useState<RequestEntry | null>(
24
+ null,
25
+ );
26
+ const [selectedException, setSelectedException] =
27
+ useState<ExceptionEntry | null>(null);
28
+ const [selectedLog, setSelectedLog] = useState<LogEntry | null>(null);
29
+
30
+ const loadData = useCallback(async () => {
31
+ try {
32
+ const [statsData, requestsData, exceptionsData, logsData] =
33
+ await Promise.all([
34
+ api.getStats(),
35
+ api.getRequests({ limit: 50 }),
36
+ api.getExceptions({ limit: 50 }),
37
+ api.getLogs({ limit: 50 }),
38
+ ]);
39
+
40
+ setStats(statsData);
41
+ setRequests(requestsData);
42
+ setExceptions(exceptionsData);
43
+ setLogs(logsData);
44
+ } catch (error) {
45
+ console.error('Failed to load data:', error);
46
+ } finally {
47
+ setLoading(false);
48
+ }
49
+ }, []);
50
+
51
+ useEffect(() => {
52
+ loadData();
53
+ }, [loadData]);
54
+
55
+ useEffect(() => {
56
+ let ws: WebSocket | null = null;
57
+ let reconnectTimeout: ReturnType<typeof setTimeout>;
58
+
59
+ function connect() {
60
+ try {
61
+ ws = api.createWebSocket();
62
+
63
+ ws.onopen = () => {
64
+ setConnected(true);
65
+ };
66
+
67
+ ws.onclose = () => {
68
+ setConnected(false);
69
+ reconnectTimeout = setTimeout(connect, 3000);
70
+ };
71
+
72
+ ws.onerror = () => {
73
+ ws?.close();
74
+ };
75
+
76
+ ws.onmessage = (event) => {
77
+ try {
78
+ const message: WebSocketMessage = JSON.parse(event.data);
79
+
80
+ switch (message.type) {
81
+ case 'request':
82
+ setRequests((prev) =>
83
+ [message.payload as RequestEntry, ...prev].slice(0, 100),
84
+ );
85
+ setStats((prev) =>
86
+ prev ? { ...prev, requests: prev.requests + 1 } : prev,
87
+ );
88
+ break;
89
+ case 'exception':
90
+ setExceptions((prev) =>
91
+ [message.payload as ExceptionEntry, ...prev].slice(0, 100),
92
+ );
93
+ setStats((prev) =>
94
+ prev ? { ...prev, exceptions: prev.exceptions + 1 } : prev,
95
+ );
96
+ break;
97
+ case 'log':
98
+ setLogs((prev) =>
99
+ [message.payload as LogEntry, ...prev].slice(0, 100),
100
+ );
101
+ setStats((prev) =>
102
+ prev ? { ...prev, logs: prev.logs + 1 } : prev,
103
+ );
104
+ break;
105
+ }
106
+ } catch {
107
+ // Ignore parse errors
108
+ }
109
+ };
110
+ } catch {
111
+ reconnectTimeout = setTimeout(connect, 3000);
112
+ }
113
+ }
114
+
115
+ connect();
116
+
117
+ return () => {
118
+ clearTimeout(reconnectTimeout);
119
+ ws?.close();
120
+ };
121
+ }, []);
122
+
123
+ const formatTime = (timestamp: string) => {
124
+ return new Date(timestamp).toLocaleTimeString();
125
+ };
126
+
127
+ const formatDuration = (ms: number) => {
128
+ if (ms < 1) return '<1ms';
129
+ if (ms < 1000) return `${Math.round(ms)}ms`;
130
+ return `${(ms / 1000).toFixed(2)}s`;
131
+ };
132
+
133
+ const getMethodColor = (method: string) => {
134
+ const colors: Record<string, string> = {
135
+ GET: 'bg-green-500/20 text-green-400',
136
+ POST: 'bg-blue-500/20 text-blue-400',
137
+ PUT: 'bg-amber-500/20 text-amber-400',
138
+ PATCH: 'bg-purple-500/20 text-purple-400',
139
+ DELETE: 'bg-red-500/20 text-red-400',
140
+ };
141
+ return colors[method] || 'bg-slate-500/20 text-slate-400';
142
+ };
143
+
144
+ const getStatusColor = (status: number) => {
145
+ if (status >= 500) return 'bg-red-500/20 text-red-400';
146
+ if (status >= 400) return 'bg-amber-500/20 text-amber-400';
147
+ if (status >= 300) return 'bg-blue-500/20 text-blue-400';
148
+ return 'bg-green-500/20 text-green-400';
149
+ };
150
+
151
+ const getLogLevelColor = (level: string) => {
152
+ const colors: Record<string, string> = {
153
+ debug: 'bg-slate-500/20 text-slate-400',
154
+ info: 'bg-blue-500/20 text-blue-400',
155
+ warn: 'bg-amber-500/20 text-amber-400',
156
+ error: 'bg-red-500/20 text-red-400',
157
+ };
158
+ return colors[level] || 'bg-slate-500/20 text-slate-400';
159
+ };
160
+
161
+ const closeDetail = () => {
162
+ setSelectedRequest(null);
163
+ setSelectedException(null);
164
+ setSelectedLog(null);
165
+ };
166
+
167
+ return (
168
+ <div className="flex flex-col min-h-screen bg-bg-primary font-mono text-slate-100">
169
+ {/* Header */}
170
+ <header className="bg-bg-secondary border-b border-border px-6 py-4 flex items-center justify-between">
171
+ <h1 className="text-xl font-semibold flex items-center gap-2">
172
+ <span className="text-blue-400">&#128301;</span> Telescope
173
+ </h1>
174
+ <div className="flex items-center gap-6">
175
+ {stats && (
176
+ <>
177
+ <div className="text-sm text-slate-400">
178
+ <span className="font-semibold text-slate-100">
179
+ {stats.requests}
180
+ </span>{' '}
181
+ requests
182
+ </div>
183
+ <div className="text-sm text-slate-400">
184
+ <span className="font-semibold text-slate-100">
185
+ {stats.exceptions}
186
+ </span>{' '}
187
+ exceptions
188
+ </div>
189
+ <div className="text-sm text-slate-400">
190
+ <span className="font-semibold text-slate-100">
191
+ {stats.logs}
192
+ </span>{' '}
193
+ logs
194
+ </div>
195
+ </>
196
+ )}
197
+ <div className="flex items-center gap-2 text-xs text-slate-500">
198
+ <span
199
+ className={`w-2 h-2 rounded-full ${connected ? 'bg-green-500' : 'bg-red-500'}`}
200
+ />
201
+ {connected ? 'Live' : 'Disconnected'}
202
+ </div>
203
+ </div>
204
+ </header>
205
+
206
+ {/* Main */}
207
+ <main className="flex-1 flex flex-col">
208
+ {/* Tabs */}
209
+ <div className="flex bg-bg-secondary border-b border-border">
210
+ {(['requests', 'exceptions', 'logs'] as Tab[]).map((tab) => (
211
+ <button
212
+ key={tab}
213
+ className={`px-6 py-3 text-sm capitalize border-b-2 transition-colors ${
214
+ activeTab === tab
215
+ ? 'text-blue-400 border-blue-400'
216
+ : 'text-slate-400 border-transparent hover:text-slate-100 hover:bg-bg-tertiary'
217
+ }`}
218
+ onClick={() => setActiveTab(tab)}
219
+ >
220
+ {tab}
221
+ </button>
222
+ ))}
223
+ </div>
224
+
225
+ {/* Content */}
226
+ <div className="flex-1 p-4 overflow-y-auto">
227
+ {loading ? (
228
+ <div className="flex items-center justify-center py-16 text-slate-500">
229
+ Loading...
230
+ </div>
231
+ ) : activeTab === 'requests' ? (
232
+ requests.length === 0 ? (
233
+ <div className="flex flex-col items-center justify-center py-16 text-slate-500 text-center">
234
+ <h3 className="text-lg mb-2">No requests yet</h3>
235
+ <p className="text-sm">
236
+ Requests will appear here as they are captured.
237
+ </p>
238
+ </div>
239
+ ) : (
240
+ <div className="flex flex-col gap-2">
241
+ {requests.map((request) => (
242
+ <div
243
+ key={request.id}
244
+ 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"
245
+ onClick={() => setSelectedRequest(request)}
246
+ >
247
+ <span
248
+ className={`font-semibold px-2 py-1 rounded text-xs min-w-16 text-center ${getMethodColor(request.method)}`}
249
+ >
250
+ {request.method}
251
+ </span>
252
+ <span className="flex-1 truncate">{request.path}</span>
253
+ <span
254
+ className={`text-sm px-2 py-1 rounded ${getStatusColor(request.status)}`}
255
+ >
256
+ {request.status}
257
+ </span>
258
+ <span className="text-xs text-slate-500 min-w-16 text-right">
259
+ {formatDuration(request.duration)}
260
+ </span>
261
+ <span className="text-xs text-slate-500 min-w-20 text-right">
262
+ {formatTime(request.timestamp)}
263
+ </span>
264
+ </div>
265
+ ))}
266
+ </div>
267
+ )
268
+ ) : activeTab === 'exceptions' ? (
269
+ exceptions.length === 0 ? (
270
+ <div className="flex flex-col items-center justify-center py-16 text-slate-500 text-center">
271
+ <h3 className="text-lg mb-2">No exceptions</h3>
272
+ <p className="text-sm">
273
+ Exceptions will appear here when they occur.
274
+ </p>
275
+ </div>
276
+ ) : (
277
+ <div className="flex flex-col gap-2">
278
+ {exceptions.map((exception) => (
279
+ <div
280
+ key={exception.id}
281
+ className="bg-bg-secondary border border-border rounded-lg p-4 cursor-pointer transition-colors hover:border-blue-500 hover:bg-bg-tertiary"
282
+ onClick={() => setSelectedException(exception)}
283
+ >
284
+ <div className="flex justify-between items-center mb-1">
285
+ <span className="font-semibold text-red-400">
286
+ {exception.name}
287
+ </span>
288
+ <span className="text-xs text-slate-500">
289
+ {formatTime(exception.timestamp)}
290
+ </span>
291
+ </div>
292
+ <span className="text-sm text-slate-400 truncate block">
293
+ {exception.message}
294
+ </span>
295
+ </div>
296
+ ))}
297
+ </div>
298
+ )
299
+ ) : logs.length === 0 ? (
300
+ <div className="flex flex-col items-center justify-center py-16 text-slate-500 text-center">
301
+ <h3 className="text-lg mb-2">No logs yet</h3>
302
+ <p className="text-sm">
303
+ Log entries will appear here as they are recorded.
304
+ </p>
305
+ </div>
306
+ ) : (
307
+ <div className="flex flex-col gap-2">
308
+ {logs.map((log) => (
309
+ <div
310
+ key={log.id}
311
+ 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"
312
+ onClick={() => setSelectedLog(log)}
313
+ >
314
+ <span
315
+ className={`font-semibold px-2 py-1 rounded text-xs min-w-14 text-center ${getLogLevelColor(log.level)}`}
316
+ >
317
+ {log.level}
318
+ </span>
319
+ <span className="flex-1 wrap-break-word">{log.message}</span>
320
+ <span className="text-xs text-slate-500 min-w-20 text-right">
321
+ {formatTime(log.timestamp)}
322
+ </span>
323
+ </div>
324
+ ))}
325
+ </div>
326
+ )}
327
+ </div>
328
+ </main>
329
+
330
+ {/* Detail Panels */}
331
+ {selectedRequest && (
332
+ <RequestDetail request={selectedRequest} onClose={closeDetail} />
333
+ )}
334
+
335
+ {selectedException && (
336
+ <ExceptionDetail exception={selectedException} onClose={closeDetail} />
337
+ )}
338
+
339
+ {selectedLog && <LogDetail log={selectedLog} onClose={closeDetail} />}
340
+ </div>
341
+ );
342
+ }
package/ui/src/api.ts ADDED
@@ -0,0 +1,75 @@
1
+ import type {
2
+ ExceptionEntry,
3
+ LogEntry,
4
+ RequestEntry,
5
+ TelescopeStats,
6
+ } from './types';
7
+
8
+ const BASE_URL = '/__telescope/api';
9
+
10
+ async function fetchJson<T>(path: string): Promise<T> {
11
+ const response = await fetch(`${BASE_URL}${path}`);
12
+ if (!response.ok) {
13
+ throw new Error(`HTTP error! status: ${response.status}`);
14
+ }
15
+ return response.json();
16
+ }
17
+
18
+ export async function getRequests(options?: {
19
+ limit?: number;
20
+ offset?: number;
21
+ }): Promise<RequestEntry[]> {
22
+ const params = new URLSearchParams();
23
+ if (options?.limit) params.set('limit', String(options.limit));
24
+ if (options?.offset) params.set('offset', String(options.offset));
25
+ const query = params.toString();
26
+ return fetchJson(`/requests${query ? `?${query}` : ''}`);
27
+ }
28
+
29
+ export async function getRequest(id: string): Promise<RequestEntry | null> {
30
+ try {
31
+ return await fetchJson(`/requests/${id}`);
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+
37
+ export async function getExceptions(options?: {
38
+ limit?: number;
39
+ offset?: number;
40
+ }): Promise<ExceptionEntry[]> {
41
+ const params = new URLSearchParams();
42
+ if (options?.limit) params.set('limit', String(options.limit));
43
+ if (options?.offset) params.set('offset', String(options.offset));
44
+ const query = params.toString();
45
+ return fetchJson(`/exceptions${query ? `?${query}` : ''}`);
46
+ }
47
+
48
+ export async function getException(id: string): Promise<ExceptionEntry | null> {
49
+ try {
50
+ return await fetchJson(`/exceptions/${id}`);
51
+ } catch {
52
+ return null;
53
+ }
54
+ }
55
+
56
+ export async function getLogs(options?: {
57
+ limit?: number;
58
+ offset?: number;
59
+ }): Promise<LogEntry[]> {
60
+ const params = new URLSearchParams();
61
+ if (options?.limit) params.set('limit', String(options.limit));
62
+ if (options?.offset) params.set('offset', String(options.offset));
63
+ const query = params.toString();
64
+ return fetchJson(`/logs${query ? `?${query}` : ''}`);
65
+ }
66
+
67
+ export async function getStats(): Promise<TelescopeStats> {
68
+ return fetchJson('/stats');
69
+ }
70
+
71
+ export function createWebSocket(): WebSocket {
72
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
73
+ const host = window.location.host;
74
+ return new WebSocket(`${protocol}//${host}/__telescope/ws`);
75
+ }