@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.
- package/README.md +521 -0
- package/dist/Telescope-B3Wd82yk.cjs +602 -0
- package/dist/Telescope-B3Wd82yk.cjs.map +1 -0
- package/dist/Telescope-C5dyDYYB.d.cts +133 -0
- package/dist/Telescope-D-uoZB6b.mjs +596 -0
- package/dist/Telescope-D-uoZB6b.mjs.map +1 -0
- package/dist/Telescope-DyIWgh9-.d.mts +133 -0
- package/dist/Telescope.cjs +3 -0
- package/dist/Telescope.d.cts +3 -0
- package/dist/Telescope.d.mts +3 -0
- package/dist/Telescope.mjs +3 -0
- package/dist/chunk-CUT6urMc.cjs +30 -0
- package/dist/index.cjs +5 -0
- package/dist/index.d.cts +4 -0
- package/dist/index.d.mts +4 -0
- package/dist/index.mjs +4 -0
- package/dist/logger/console.cjs +161 -0
- package/dist/logger/console.cjs.map +1 -0
- package/dist/logger/console.d.cts +109 -0
- package/dist/logger/console.d.mts +109 -0
- package/dist/logger/console.mjs +159 -0
- package/dist/logger/console.mjs.map +1 -0
- package/dist/logger/pino.cjs +118 -0
- package/dist/logger/pino.cjs.map +1 -0
- package/dist/logger/pino.d.cts +89 -0
- package/dist/logger/pino.d.mts +89 -0
- package/dist/logger/pino.mjs +116 -0
- package/dist/logger/pino.mjs.map +1 -0
- package/dist/memory-9-B9WACq.cjs +110 -0
- package/dist/memory-9-B9WACq.cjs.map +1 -0
- package/dist/memory-Cm0eevCS.d.mts +38 -0
- package/dist/memory-DiP1a-pp.d.cts +38 -0
- package/dist/memory-SdN5vtG9.mjs +104 -0
- package/dist/memory-SdN5vtG9.mjs.map +1 -0
- package/dist/server/hono.cjs +180 -0
- package/dist/server/hono.cjs.map +1 -0
- package/dist/server/hono.d.cts +26 -0
- package/dist/server/hono.d.mts +26 -0
- package/dist/server/hono.mjs +176 -0
- package/dist/server/hono.mjs.map +1 -0
- package/dist/storage/kysely.cjs +336 -0
- package/dist/storage/kysely.cjs.map +1 -0
- package/dist/storage/kysely.d.cts +161 -0
- package/dist/storage/kysely.d.mts +161 -0
- package/dist/storage/kysely.mjs +334 -0
- package/dist/storage/kysely.mjs.map +1 -0
- package/dist/storage/memory.cjs +3 -0
- package/dist/storage/memory.d.cts +3 -0
- package/dist/storage/memory.d.mts +3 -0
- package/dist/storage/memory.mjs +3 -0
- package/dist/types-BGDhFv4R.d.cts +170 -0
- package/dist/types-CZbzz8kx.d.mts +170 -0
- package/dist/types.cjs +0 -0
- package/dist/types.d.cts +2 -0
- package/dist/types.d.mts +2 -0
- package/dist/types.mjs +0 -0
- package/dist/ui-assets-D6-8TAr_.mjs +30 -0
- package/dist/ui-assets-D6-8TAr_.mjs.map +1 -0
- package/dist/ui-assets-ulevVble.cjs +48 -0
- package/dist/ui-assets-ulevVble.cjs.map +1 -0
- package/dist/ui-assets.cjs +5 -0
- package/dist/ui-assets.d.cts +12 -0
- package/dist/ui-assets.d.mts +12 -0
- package/dist/ui-assets.mjs +3 -0
- package/package.json +83 -0
- package/scripts/embed-ui.ts +90 -0
- package/src/Telescope.ts +714 -0
- package/src/__tests__/Telescope.spec.ts +356 -0
- package/src/index.ts +23 -0
- package/src/logger/__tests__/console.spec.ts +266 -0
- package/src/logger/__tests__/pino.spec.ts +217 -0
- package/src/logger/console.ts +230 -0
- package/src/logger/pino.ts +191 -0
- package/src/server/__tests__/hono.spec.ts +340 -0
- package/src/server/hono.ts +247 -0
- package/src/storage/__tests__/kysely.spec.ts +715 -0
- package/src/storage/__tests__/memory.spec.ts +411 -0
- package/src/storage/kysely.ts +572 -0
- package/src/storage/memory.ts +168 -0
- package/src/types.ts +188 -0
- package/src/ui-assets.ts +40 -0
- package/ui/index.html +12 -0
- package/ui/node_modules/.bin/browserslist +21 -0
- package/ui/node_modules/.bin/jiti +21 -0
- package/ui/node_modules/.bin/terser +21 -0
- package/ui/node_modules/.bin/tsc +21 -0
- package/ui/node_modules/.bin/tsserver +21 -0
- package/ui/node_modules/.bin/tsx +21 -0
- package/ui/node_modules/.bin/vite +21 -0
- package/ui/package.json +24 -0
- package/ui/src/App.tsx +342 -0
- package/ui/src/api.ts +75 -0
- package/ui/src/components/ExceptionDetail.tsx +100 -0
- package/ui/src/components/LogDetail.tsx +91 -0
- package/ui/src/components/RequestDetail.tsx +143 -0
- package/ui/src/main.tsx +10 -0
- package/ui/src/styles.css +10 -0
- package/ui/src/types.ts +63 -0
- package/ui/src/vite-env.d.ts +1 -0
- package/ui/src/vite-plugin-gkm-config.ts +54 -0
- package/ui/tsconfig.json +20 -0
- package/ui/tsconfig.tsbuildinfo +14 -0
- 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
|
package/ui/package.json
ADDED
|
@@ -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">🔭</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
|
+
}
|