dev3000 0.0.66 → 0.0.67
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/dist/cdp-monitor.d.ts +4 -1
- package/dist/cdp-monitor.d.ts.map +1 -1
- package/dist/cdp-monitor.js +35 -1
- package/dist/cdp-monitor.js.map +1 -1
- package/dist/cli.js +10 -5
- package/dist/cli.js.map +1 -1
- package/dist/dev-environment.d.ts +2 -0
- package/dist/dev-environment.d.ts.map +1 -1
- package/dist/dev-environment.js +44 -27
- package/dist/dev-environment.js.map +1 -1
- package/dist/services/parsers/log-parsers/base.d.ts +1 -1
- package/dist/services/parsers/log-parsers/base.d.ts.map +1 -1
- package/dist/src/tui-interface-impl.tsx +163 -48
- package/dist/tui-interface-impl.d.ts.map +1 -1
- package/dist/tui-interface-impl.js +72 -14
- package/dist/tui-interface-impl.js.map +1 -1
- package/dist/utils/project-name.d.ts +18 -0
- package/dist/utils/project-name.d.ts.map +1 -0
- package/dist/utils/project-name.js +114 -0
- package/dist/utils/project-name.js.map +1 -0
- package/dist/utils/timestamp.d.ts +8 -0
- package/dist/utils/timestamp.d.ts.map +1 -0
- package/dist/utils/timestamp.js +18 -0
- package/dist/utils/timestamp.js.map +1 -0
- package/mcp-server/.next/BUILD_ID +1 -1
- package/mcp-server/.next/app-build-manifest.json +8 -6
- package/mcp-server/.next/build-manifest.json +2 -2
- package/mcp-server/.next/cache/.tsbuildinfo +1 -1
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000005.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000006.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000007.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000008.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000009.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000010.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000011.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000013.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000014.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000016.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000017.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000018.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000021.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000022.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000023.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000025.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000027.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000028.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000029.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000030.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000031.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000032.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000034.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000035.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000037.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000038.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000039.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000040.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000041.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000042.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000044.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000045.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000047.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000048.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000049.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000050.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000051.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000052.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000054.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000055.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000057.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000058.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000059.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000060.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000061.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000062.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000064.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000065.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000066.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000067.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000069.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000070.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000071.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000072.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000073.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000074.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000075.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000076.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000077.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000078.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000079.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000080.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000081.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000082.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000083.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000084.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000085.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000086.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000087.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000088.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000089.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000090.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000091.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000092.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000093.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000094.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000095.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000096.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000097.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000098.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000099.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000100.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000101.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000102.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000103.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000104.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000105.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000106.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000107.sst +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000108.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000109.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000110.meta +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/CURRENT +0 -0
- package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/LOG +230 -0
- package/mcp-server/.next/fallback-build-manifest.json +2 -2
- package/mcp-server/.next/package.json +3 -1
- package/mcp-server/.next/required-server-files.json +1 -0
- package/mcp-server/.next/server/app/_global-error.html +2 -2
- package/mcp-server/.next/server/app/_global-error.rsc +1 -1
- package/mcp-server/.next/server/app/_not-found/page/app-build-manifest.json +1 -1
- package/mcp-server/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/mcp-server/.next/server/app/_not-found.html +1 -1
- package/mcp-server/.next/server/app/_not-found.rsc +2 -2
- package/mcp-server/.next/server/app/index.html +1 -1
- package/mcp-server/.next/server/app/index.rsc +3 -3
- package/mcp-server/.next/server/app/logs/page/app-build-manifest.json +4 -3
- package/mcp-server/.next/server/app/logs/page.js.nft.json +1 -1
- package/mcp-server/.next/server/app/logs/page_client-reference-manifest.js +1 -1
- package/mcp-server/.next/server/app/mcp/route.js.nft.json +1 -1
- package/mcp-server/.next/server/app/page/app-build-manifest.json +3 -2
- package/mcp-server/.next/server/app/page.js.nft.json +1 -1
- package/mcp-server/.next/server/app/page_client-reference-manifest.js +1 -1
- package/mcp-server/.next/server/chunks/[root-of-the-server]__1b561deb._.js +1 -1
- package/mcp-server/.next/server/chunks/[root-of-the-server]__1b561deb._.js.map +1 -1
- package/mcp-server/.next/server/chunks/[root-of-the-server]__38e8baae._.js +7 -8
- package/mcp-server/.next/server/chunks/[root-of-the-server]__38e8baae._.js.map +1 -1
- package/mcp-server/.next/server/chunks/ssr/{node_modules__pnpm_4f58b96e._.js → _188bfe33._.js} +2 -2
- package/mcp-server/.next/server/chunks/ssr/_188bfe33._.js.map +1 -0
- package/mcp-server/.next/server/chunks/ssr/_9d670a6b._.js +2 -2
- package/mcp-server/.next/server/chunks/ssr/_9d670a6b._.js.map +1 -1
- package/mcp-server/.next/server/chunks/ssr/_d858c4cd._.js +1 -1
- package/mcp-server/.next/server/chunks/ssr/_d858c4cd._.js.map +1 -1
- package/mcp-server/.next/server/chunks/ssr/_dae9c1d5._.js +1 -1
- package/mcp-server/.next/server/chunks/ssr/_dae9c1d5._.js.map +1 -1
- package/mcp-server/.next/server/chunks/ssr/{node_modules__pnpm_87fb6266._.js → _f03e80a8._.js} +2 -2
- package/mcp-server/.next/server/chunks/ssr/_f03e80a8._.js.map +1 -0
- package/mcp-server/.next/server/chunks/ssr/mcp-server_app_layout_tsx_afa41767._.js +1 -1
- package/mcp-server/.next/server/chunks/ssr/mcp-server_app_layout_tsx_afa41767._.js.map +1 -1
- package/mcp-server/.next/server/pages/404.html +1 -1
- package/mcp-server/.next/server/pages/500.html +2 -2
- package/mcp-server/.next/static/chunks/5a5edc75ee7e7de4.js +1 -0
- package/mcp-server/.next/static/chunks/65b18bf1ede9811a.css +1 -0
- package/mcp-server/.next/static/chunks/909033014621484e.js +1 -0
- package/mcp-server/.next/static/chunks/c5f8464bc8083ee7.js +1 -0
- package/mcp-server/.next/trace +1 -1
- package/mcp-server/app/api/tools/route.ts +22 -5
- package/mcp-server/app/layout.tsx +4 -2
- package/mcp-server/app/logs/LogsClient.infinite-loop.test.tsx +127 -0
- package/mcp-server/app/logs/LogsClient.tsx +318 -201
- package/mcp-server/app/logs/page.tsx +19 -3
- package/mcp-server/app/logs/utils.ts +15 -3
- package/mcp-server/app/mcp/route.ts +75 -509
- package/mcp-server/app/mcp/tools.ts +747 -0
- package/mcp-server/app/page.tsx +244 -169
- package/mcp-server/next.config.ts +1 -1
- package/mcp-server/package.json +9 -1
- package/mcp-server/public/favicon-16.svg +4 -0
- package/mcp-server/public/favicon-180.png +0 -0
- package/mcp-server/public/favicon-64.svg +4 -0
- package/mcp-server/public/favicon-preview.html +67 -0
- package/mcp-server/public/favicon.ico +0 -0
- package/mcp-server/public/favicon.svg +4 -0
- package/mcp-server/public/screenshots/test.txt +1 -0
- package/package.json +3 -4
- package/src/tui-interface-impl.tsx +163 -48
- package/mcp-server/.next/build/chunks/[root-of-the-server]__25374c4f._.js +0 -496
- package/mcp-server/.next/build/chunks/[root-of-the-server]__25374c4f._.js.map +0 -11
- package/mcp-server/.next/build/chunks/[root-of-the-server]__4718a9dd._.js +0 -408
- package/mcp-server/.next/build/chunks/[root-of-the-server]__4718a9dd._.js.map +0 -7
- package/mcp-server/.next/build/chunks/[root-of-the-server]__c438ef56._.js +0 -205
- package/mcp-server/.next/build/chunks/[root-of-the-server]__c438ef56._.js.map +0 -8
- package/mcp-server/.next/build/chunks/[root-of-the-server]__c7ae8543._.js +0 -496
- package/mcp-server/.next/build/chunks/[root-of-the-server]__c7ae8543._.js.map +0 -11
- package/mcp-server/.next/build/chunks/[turbopack-node]_transforms_postcss_ts_d723d216._.js +0 -13
- package/mcp-server/.next/build/chunks/[turbopack-node]_transforms_postcss_ts_d723d216._.js.map +0 -5
- package/mcp-server/.next/build/chunks/[turbopack-node]_transforms_webpack-loaders_ts_5a40237e._.js +0 -12
- package/mcp-server/.next/build/chunks/[turbopack-node]_transforms_webpack-loaders_ts_5a40237e._.js.map +0 -5
- package/mcp-server/.next/build/chunks/[turbopack]_runtime.js +0 -770
- package/mcp-server/.next/build/chunks/[turbopack]_runtime.js.map +0 -10
- package/mcp-server/.next/build/chunks/node_modules__pnpm_806d01c0._.js +0 -6759
- package/mcp-server/.next/build/chunks/node_modules__pnpm_806d01c0._.js.map +0 -47
- package/mcp-server/.next/postcss.js +0 -6
- package/mcp-server/.next/postcss.js.map +0 -5
- package/mcp-server/.next/server/chunks/ssr/node_modules__pnpm_4f58b96e._.js.map +0 -1
- package/mcp-server/.next/server/chunks/ssr/node_modules__pnpm_87fb6266._.js.map +0 -1
- package/mcp-server/.next/static/chunks/11f1b53bdf7a9af0.js +0 -1
- package/mcp-server/.next/static/chunks/3d4ea64f6384f2c6.js +0 -1
- package/mcp-server/.next/static/chunks/50335dad5c51aab8.js +0 -1
- package/mcp-server/.next/static/chunks/bdd0789390bc312f.css +0 -1
- package/mcp-server/.next/webpack-loaders.js +0 -6
- package/mcp-server/.next/webpack-loaders.js.map +0 -5
- /package/mcp-server/.next/static/{mZfouQw6OHfahPQayuVeY → KrGcHKj--hSqNUOXmnA4A}/_buildManifest.js +0 -0
- /package/mcp-server/.next/static/{mZfouQw6OHfahPQayuVeY → KrGcHKj--hSqNUOXmnA4A}/_clientMiddlewareManifest.json +0 -0
- /package/mcp-server/.next/static/{mZfouQw6OHfahPQayuVeY → KrGcHKj--hSqNUOXmnA4A}/_ssgManifest.js +0 -0
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
import Image from "next/image"
|
|
4
4
|
import { useRouter, useSearchParams } from "next/navigation"
|
|
5
5
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
|
6
|
+
import { DarkModeToggle } from "@/components/dark-mode-toggle"
|
|
7
|
+
import { useDarkMode } from "@/hooks/use-dark-mode"
|
|
6
8
|
import type { LogEntry, LogFile, LogListResponse, LogsApiResponse } from "@/types"
|
|
7
9
|
import { getTextColor, LOG_COLORS } from "../../../src/constants/log-colors"
|
|
8
10
|
|
|
@@ -29,51 +31,6 @@ interface ReplayEvent {
|
|
|
29
31
|
|
|
30
32
|
import { parseLogEntries } from "./utils"
|
|
31
33
|
|
|
32
|
-
// Hook for dark mode with system preference detection
|
|
33
|
-
function useDarkMode() {
|
|
34
|
-
const [darkMode, setDarkMode] = useState<boolean>(() => {
|
|
35
|
-
if (typeof window !== "undefined") {
|
|
36
|
-
// Check localStorage first
|
|
37
|
-
const saved = localStorage.getItem("dev3000-dark-mode")
|
|
38
|
-
if (saved !== null) {
|
|
39
|
-
return JSON.parse(saved)
|
|
40
|
-
}
|
|
41
|
-
// Default to system preference
|
|
42
|
-
return window.matchMedia("(prefers-color-scheme: dark)").matches
|
|
43
|
-
}
|
|
44
|
-
return false
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
useEffect(() => {
|
|
48
|
-
// Save to localStorage
|
|
49
|
-
localStorage.setItem("dev3000-dark-mode", JSON.stringify(darkMode))
|
|
50
|
-
|
|
51
|
-
// Apply dark class to document
|
|
52
|
-
if (darkMode) {
|
|
53
|
-
document.documentElement.classList.add("dark")
|
|
54
|
-
} else {
|
|
55
|
-
document.documentElement.classList.remove("dark")
|
|
56
|
-
}
|
|
57
|
-
}, [darkMode])
|
|
58
|
-
|
|
59
|
-
// Listen for system theme changes
|
|
60
|
-
useEffect(() => {
|
|
61
|
-
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)")
|
|
62
|
-
const handler = (e: MediaQueryListEvent) => {
|
|
63
|
-
// Only update if no explicit choice has been made
|
|
64
|
-
const saved = localStorage.getItem("dev3000-dark-mode")
|
|
65
|
-
if (saved === null) {
|
|
66
|
-
setDarkMode(e.matches)
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
mediaQuery.addEventListener("change", handler)
|
|
71
|
-
return () => mediaQuery.removeEventListener("change", handler)
|
|
72
|
-
}, [])
|
|
73
|
-
|
|
74
|
-
return [darkMode, setDarkMode] as const
|
|
75
|
-
}
|
|
76
|
-
|
|
77
34
|
// Keep this for backwards compatibility, but it's not used anymore
|
|
78
35
|
function _parseLogLine(line: string): LogEntry | null {
|
|
79
36
|
const match = line.match(/^\[([^\]]+)\] \[([^\]]+)\] (.*)$/s)
|
|
@@ -125,7 +82,7 @@ function URLRenderer({ url, maxLength = 60 }: { url: string; maxLength?: number
|
|
|
125
82
|
<button
|
|
126
83
|
type="button"
|
|
127
84
|
onClick={() => setIsExpanded(false)}
|
|
128
|
-
className="ml-2 text-xs text-
|
|
85
|
+
className="ml-2 text-xs text-muted-foreground hover:text-foreground px-1 py-0.5 rounded hover:bg-muted"
|
|
129
86
|
>
|
|
130
87
|
[collapse]
|
|
131
88
|
</button>
|
|
@@ -143,7 +100,7 @@ function URLRenderer({ url, maxLength = 60 }: { url: string; maxLength?: number
|
|
|
143
100
|
<button
|
|
144
101
|
type="button"
|
|
145
102
|
onClick={() => setIsExpanded(true)}
|
|
146
|
-
className="ml-1 text-xs text-
|
|
103
|
+
className="ml-1 text-xs text-muted-foreground hover:text-foreground px-1 py-0.5 rounded hover:bg-muted"
|
|
147
104
|
>
|
|
148
105
|
[expand]
|
|
149
106
|
</button>
|
|
@@ -285,7 +242,7 @@ function ObjectRenderer({ content }: { content: string }) {
|
|
|
285
242
|
|
|
286
243
|
{isExpanded && (
|
|
287
244
|
<div className="mt-1 ml-4 border-l-2 border-gray-200 pl-3">
|
|
288
|
-
<pre className="font-mono text-sm text-
|
|
245
|
+
<pre className="font-mono text-sm text-foreground whitespace-pre-wrap">{JSON.stringify(obj, null, 2)}</pre>
|
|
289
246
|
</div>
|
|
290
247
|
)}
|
|
291
248
|
</div>
|
|
@@ -296,103 +253,90 @@ function ObjectRenderer({ content }: { content: string }) {
|
|
|
296
253
|
}
|
|
297
254
|
}
|
|
298
255
|
|
|
299
|
-
function LogEntryComponent({ entry }: { entry: LogEntry }) {
|
|
256
|
+
function LogEntryComponent({ entry, darkMode }: { entry: LogEntry; darkMode: boolean }) {
|
|
300
257
|
// Parse log type from message patterns - using shared TUI colors
|
|
301
258
|
const parseLogType = (message: string) => {
|
|
302
259
|
if (message.includes("[INTERACTION]"))
|
|
303
260
|
return {
|
|
304
261
|
type: "INTERACTION",
|
|
305
|
-
color: "bg-purple-50 dark:bg-purple-900/20 border-purple-200 dark:border-purple-800",
|
|
306
262
|
backgroundColor: LOG_COLORS.CONSOLE_DEBUG,
|
|
307
263
|
textColor: getTextColor(LOG_COLORS.CONSOLE_DEBUG)
|
|
308
264
|
}
|
|
309
265
|
if (message.includes("[CONSOLE ERROR]"))
|
|
310
266
|
return {
|
|
311
267
|
type: "CONSOLE ERROR",
|
|
312
|
-
color: "bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-800",
|
|
313
268
|
backgroundColor: LOG_COLORS.CONSOLE_ERROR,
|
|
314
269
|
textColor: getTextColor(LOG_COLORS.CONSOLE_ERROR)
|
|
315
270
|
}
|
|
316
271
|
if (message.includes("[CONSOLE WARN]"))
|
|
317
272
|
return {
|
|
318
273
|
type: "CONSOLE WARN",
|
|
319
|
-
color: "bg-yellow-50 dark:bg-yellow-900/20 border-yellow-200 dark:border-yellow-800",
|
|
320
274
|
backgroundColor: LOG_COLORS.CONSOLE_WARN,
|
|
321
275
|
textColor: getTextColor(LOG_COLORS.CONSOLE_WARN)
|
|
322
276
|
}
|
|
323
277
|
if (message.includes("[CONSOLE INFO]"))
|
|
324
278
|
return {
|
|
325
279
|
type: "CONSOLE INFO",
|
|
326
|
-
color: "bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800",
|
|
327
280
|
backgroundColor: LOG_COLORS.CONSOLE_INFO,
|
|
328
281
|
textColor: getTextColor(LOG_COLORS.CONSOLE_INFO)
|
|
329
282
|
}
|
|
330
283
|
if (message.includes("[CONSOLE LOG]"))
|
|
331
284
|
return {
|
|
332
285
|
type: "CONSOLE LOG",
|
|
333
|
-
color: "bg-gray-50 dark:bg-gray-800/50 border-gray-200 dark:border-gray-700",
|
|
334
286
|
backgroundColor: LOG_COLORS.CONSOLE_LOG,
|
|
335
287
|
textColor: getTextColor(LOG_COLORS.CONSOLE_LOG)
|
|
336
288
|
}
|
|
337
289
|
if (message.includes("[CONSOLE DEBUG]"))
|
|
338
290
|
return {
|
|
339
291
|
type: "CONSOLE DEBUG",
|
|
340
|
-
color: "bg-purple-50 dark:bg-purple-900/20 border-purple-200 dark:border-purple-800",
|
|
341
292
|
backgroundColor: LOG_COLORS.CONSOLE_DEBUG,
|
|
342
293
|
textColor: getTextColor(LOG_COLORS.CONSOLE_DEBUG)
|
|
343
294
|
}
|
|
344
295
|
if (message.includes("[SCREENSHOT]"))
|
|
345
296
|
return {
|
|
346
297
|
type: "SCREENSHOT",
|
|
347
|
-
color: "bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800",
|
|
348
298
|
backgroundColor: LOG_COLORS.SCREENSHOT,
|
|
349
299
|
textColor: getTextColor(LOG_COLORS.SCREENSHOT)
|
|
350
300
|
}
|
|
351
301
|
if (message.includes("[NAVIGATION]") || message.includes("[PAGE]"))
|
|
352
302
|
return {
|
|
353
303
|
type: "PAGE",
|
|
354
|
-
color: "bg-green-50 dark:bg-green-900/20 border-green-200 dark:border-green-800",
|
|
355
304
|
backgroundColor: LOG_COLORS.PAGE,
|
|
356
305
|
textColor: getTextColor(LOG_COLORS.PAGE)
|
|
357
306
|
}
|
|
358
307
|
if (message.includes("[DOM]"))
|
|
359
308
|
return {
|
|
360
309
|
type: "DOM",
|
|
361
|
-
color: "bg-purple-50 dark:bg-purple-900/20 border-purple-200 dark:border-purple-800",
|
|
362
310
|
backgroundColor: LOG_COLORS.DOM,
|
|
363
311
|
textColor: getTextColor(LOG_COLORS.DOM)
|
|
364
312
|
}
|
|
365
313
|
if (message.includes("[CDP]"))
|
|
366
314
|
return {
|
|
367
315
|
type: "CDP",
|
|
368
|
-
color: "bg-yellow-50 dark:bg-yellow-900/20 border-yellow-200 dark:border-yellow-800",
|
|
369
316
|
backgroundColor: LOG_COLORS.CDP,
|
|
370
317
|
textColor: getTextColor(LOG_COLORS.CDP)
|
|
371
318
|
}
|
|
372
319
|
if (message.includes("[NETWORK]"))
|
|
373
320
|
return {
|
|
374
321
|
type: "NETWORK",
|
|
375
|
-
color: "bg-yellow-50 dark:bg-yellow-900/20 border-yellow-200 dark:border-yellow-800",
|
|
376
322
|
backgroundColor: LOG_COLORS.NETWORK,
|
|
377
323
|
textColor: getTextColor(LOG_COLORS.NETWORK)
|
|
378
324
|
}
|
|
379
325
|
if (message.includes("[ERROR]") || message.includes("[PAGE ERROR]") || message.includes("[NETWORK ERROR]"))
|
|
380
326
|
return {
|
|
381
327
|
type: "ERROR",
|
|
382
|
-
color: "bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-800",
|
|
383
328
|
backgroundColor: LOG_COLORS.ERROR,
|
|
384
329
|
textColor: getTextColor(LOG_COLORS.ERROR)
|
|
385
330
|
}
|
|
386
331
|
if (message.includes("[CRITICAL ERROR]"))
|
|
387
332
|
return {
|
|
388
333
|
type: "CRITICAL ERROR",
|
|
389
|
-
color: "bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-800",
|
|
390
334
|
backgroundColor: LOG_COLORS.CRITICAL_ERROR,
|
|
391
335
|
textColor: getTextColor(LOG_COLORS.CRITICAL_ERROR)
|
|
392
336
|
}
|
|
393
337
|
return {
|
|
394
338
|
type: "DEFAULT",
|
|
395
|
-
color: "border-gray-200 dark:border-gray-700",
|
|
339
|
+
color: "bg-gray-50 dark:bg-gray-800/50 border-gray-200 dark:border-gray-700",
|
|
396
340
|
backgroundColor: LOG_COLORS.DEFAULT,
|
|
397
341
|
textColor: getTextColor(LOG_COLORS.DEFAULT)
|
|
398
342
|
}
|
|
@@ -493,12 +437,90 @@ function LogEntryComponent({ entry }: { entry: LogEntry }) {
|
|
|
493
437
|
return parts.length > 0 ? parts : message
|
|
494
438
|
}
|
|
495
439
|
|
|
440
|
+
// Determine the background color based on log type and dark mode
|
|
441
|
+
const getBackgroundStyle = () => {
|
|
442
|
+
const type = logTypeInfo.type
|
|
443
|
+
if (darkMode) {
|
|
444
|
+
// Dark mode colors
|
|
445
|
+
switch (type) {
|
|
446
|
+
case "INTERACTION":
|
|
447
|
+
case "CONSOLE DEBUG":
|
|
448
|
+
case "DOM":
|
|
449
|
+
return { backgroundColor: "rgba(88, 28, 135, 0.3)" } // purple-950/30
|
|
450
|
+
case "CONSOLE ERROR":
|
|
451
|
+
case "ERROR":
|
|
452
|
+
case "CRITICAL ERROR":
|
|
453
|
+
return { backgroundColor: "rgba(127, 29, 29, 0.3)" } // red-950/30
|
|
454
|
+
case "CONSOLE WARN":
|
|
455
|
+
case "CDP":
|
|
456
|
+
case "NETWORK":
|
|
457
|
+
return { backgroundColor: "rgba(133, 77, 14, 0.3)" } // yellow-950/30
|
|
458
|
+
case "CONSOLE INFO":
|
|
459
|
+
case "SCREENSHOT":
|
|
460
|
+
return { backgroundColor: "rgba(30, 58, 138, 0.3)" } // blue-950/30
|
|
461
|
+
case "PAGE":
|
|
462
|
+
return { backgroundColor: "rgba(20, 83, 45, 0.3)" } // green-950/30
|
|
463
|
+
default:
|
|
464
|
+
return { backgroundColor: "rgba(17, 24, 39, 0.3)" } // gray-900/30
|
|
465
|
+
}
|
|
466
|
+
} else {
|
|
467
|
+
// Light mode colors
|
|
468
|
+
switch (type) {
|
|
469
|
+
case "INTERACTION":
|
|
470
|
+
case "CONSOLE DEBUG":
|
|
471
|
+
case "DOM":
|
|
472
|
+
return { backgroundColor: "rgba(250, 245, 255, 0.5)" } // purple-50/50
|
|
473
|
+
case "CONSOLE ERROR":
|
|
474
|
+
case "ERROR":
|
|
475
|
+
case "CRITICAL ERROR":
|
|
476
|
+
return { backgroundColor: "rgba(254, 242, 242, 0.5)" } // red-50/50
|
|
477
|
+
case "CONSOLE WARN":
|
|
478
|
+
case "CDP":
|
|
479
|
+
case "NETWORK":
|
|
480
|
+
return { backgroundColor: "rgba(254, 252, 232, 0.5)" } // yellow-50/50
|
|
481
|
+
case "CONSOLE INFO":
|
|
482
|
+
case "SCREENSHOT":
|
|
483
|
+
return { backgroundColor: "rgba(239, 246, 255, 0.5)" } // blue-50/50
|
|
484
|
+
case "PAGE":
|
|
485
|
+
return { backgroundColor: "rgba(240, 253, 244, 0.5)" } // green-50/50
|
|
486
|
+
default:
|
|
487
|
+
return { backgroundColor: "transparent" }
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Determine the border class based on log type
|
|
493
|
+
const getBorderClass = () => {
|
|
494
|
+
const type = logTypeInfo.type
|
|
495
|
+
switch (type) {
|
|
496
|
+
case "INTERACTION":
|
|
497
|
+
case "CONSOLE DEBUG":
|
|
498
|
+
case "DOM":
|
|
499
|
+
return "border-purple-200 dark:border-purple-800"
|
|
500
|
+
case "CONSOLE ERROR":
|
|
501
|
+
case "ERROR":
|
|
502
|
+
case "CRITICAL ERROR":
|
|
503
|
+
return "border-red-200 dark:border-red-800"
|
|
504
|
+
case "CONSOLE WARN":
|
|
505
|
+
case "CDP":
|
|
506
|
+
case "NETWORK":
|
|
507
|
+
return "border-yellow-200 dark:border-yellow-800"
|
|
508
|
+
case "CONSOLE INFO":
|
|
509
|
+
case "SCREENSHOT":
|
|
510
|
+
return "border-blue-200 dark:border-blue-800"
|
|
511
|
+
case "PAGE":
|
|
512
|
+
return "border-green-200 dark:border-green-800"
|
|
513
|
+
default:
|
|
514
|
+
return "border-gray-200 dark:border-gray-700"
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
496
518
|
return (
|
|
497
|
-
<div className={`border-l-4 ${
|
|
519
|
+
<div className={`border-l-4 ${getBorderClass()} pl-4 py-2`} style={getBackgroundStyle()}>
|
|
498
520
|
{/* Table-like layout using CSS Grid */}
|
|
499
521
|
<div className="grid grid-cols-[auto_auto_1fr] gap-3 items-start">
|
|
500
522
|
{/* Column 1: Timestamp */}
|
|
501
|
-
<div className="text-xs text-
|
|
523
|
+
<div className="text-xs text-muted-foreground font-mono whitespace-nowrap pt-1">
|
|
502
524
|
{new Date(entry.timestamp).toLocaleTimeString()}
|
|
503
525
|
</div>
|
|
504
526
|
|
|
@@ -514,13 +536,13 @@ function LogEntryComponent({ entry }: { entry: LogEntry }) {
|
|
|
514
536
|
</div>
|
|
515
537
|
|
|
516
538
|
{/* Column 3: Message content with user agent info */}
|
|
517
|
-
<div className="font-mono text-sm min-w-0 text-
|
|
539
|
+
<div className="font-mono text-sm min-w-0 text-foreground">
|
|
518
540
|
<div className="flex flex-wrap items-start gap-2">
|
|
519
541
|
<div className="flex-1 min-w-0">{renderMessage(entry.message)}</div>
|
|
520
542
|
{/* User Agent and Tab Identifier Pills */}
|
|
521
543
|
<div className="flex items-center gap-1 flex-shrink-0">
|
|
522
544
|
{entry.tabIdentifier && (
|
|
523
|
-
<span className="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-
|
|
545
|
+
<span className="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-muted text-muted-foreground">
|
|
524
546
|
{entry.tabIdentifier}
|
|
525
547
|
</span>
|
|
526
548
|
)}
|
|
@@ -803,6 +825,10 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
|
|
|
803
825
|
const [showReplayPreview, setShowReplayPreview] = useState(false)
|
|
804
826
|
const [replayEvents, setReplayEvents] = useState<ReplayEvent[]>([])
|
|
805
827
|
const [isRotatingLog, setIsRotatingLog] = useState(false)
|
|
828
|
+
const [retryCount, setRetryCount] = useState(0)
|
|
829
|
+
const [maxRetries] = useState(5)
|
|
830
|
+
const [lastFailedUrl, setLastFailedUrl] = useState<string | null>(null)
|
|
831
|
+
const [hasLoadedInitial, setHasLoadedInitial] = useState(false)
|
|
806
832
|
const [filters, setFilters] = useState({
|
|
807
833
|
browser: true,
|
|
808
834
|
server: true,
|
|
@@ -826,9 +852,12 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
|
|
|
826
852
|
setAvailableLogs(data.files)
|
|
827
853
|
setCurrentLogFile(data.currentFile)
|
|
828
854
|
setProjectName(data.projectName)
|
|
855
|
+
return true
|
|
829
856
|
}
|
|
857
|
+
return false
|
|
830
858
|
} catch (error) {
|
|
831
859
|
console.error("Error loading available logs:", error)
|
|
860
|
+
return false
|
|
832
861
|
}
|
|
833
862
|
}, [])
|
|
834
863
|
|
|
@@ -861,11 +890,28 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
|
|
|
861
890
|
? `/api/logs/tail?lines=50&logPath=${encodeURIComponent(logPath)}`
|
|
862
891
|
: `/api/logs/tail?lines=50`
|
|
863
892
|
|
|
893
|
+
// Check if we've exceeded retry limit for this specific URL
|
|
894
|
+
if (lastFailedUrl === apiUrl && retryCount >= maxRetries) {
|
|
895
|
+
console.error(`Maximum retry attempts (${maxRetries}) reached for ${apiUrl}. Stopping polling.`)
|
|
896
|
+
// Clear the polling interval to stop further attempts
|
|
897
|
+
if (pollIntervalRef.current) {
|
|
898
|
+
clearInterval(pollIntervalRef.current)
|
|
899
|
+
pollIntervalRef.current = null
|
|
900
|
+
}
|
|
901
|
+
return
|
|
902
|
+
}
|
|
903
|
+
|
|
864
904
|
const response = await fetch(apiUrl)
|
|
865
905
|
if (!response.ok) {
|
|
866
906
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
|
867
907
|
}
|
|
868
908
|
|
|
909
|
+
// Reset retry count on successful fetch
|
|
910
|
+
if (lastFailedUrl === apiUrl || retryCount > 0) {
|
|
911
|
+
setRetryCount(0)
|
|
912
|
+
setLastFailedUrl(null)
|
|
913
|
+
}
|
|
914
|
+
|
|
869
915
|
const data: LogsApiResponse = await response.json()
|
|
870
916
|
|
|
871
917
|
if (!data.logs) {
|
|
@@ -906,13 +952,55 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
|
|
|
906
952
|
}
|
|
907
953
|
} catch (error) {
|
|
908
954
|
console.error("Error polling logs:", error)
|
|
909
|
-
|
|
955
|
+
|
|
956
|
+
// Build the failed URL for tracking
|
|
957
|
+
const requestedFile = searchParams.get("file")
|
|
958
|
+
let logPath = ""
|
|
959
|
+
if (requestedFile && availableLogs.length > 0) {
|
|
960
|
+
const foundFile = availableLogs.find((f) => f.name === requestedFile)
|
|
961
|
+
logPath = foundFile?.path || currentLogFile
|
|
962
|
+
} else {
|
|
963
|
+
logPath = currentLogFile
|
|
964
|
+
}
|
|
965
|
+
const failedUrl = logPath
|
|
966
|
+
? `/api/logs/tail?lines=50&logPath=${encodeURIComponent(logPath)}`
|
|
967
|
+
: `/api/logs/tail?lines=50`
|
|
968
|
+
|
|
969
|
+
// Increment retry count if it's the same URL, otherwise reset
|
|
970
|
+
if (lastFailedUrl === failedUrl) {
|
|
971
|
+
const newRetryCount = retryCount + 1
|
|
972
|
+
setRetryCount(newRetryCount)
|
|
973
|
+
|
|
974
|
+
if (newRetryCount >= maxRetries) {
|
|
975
|
+
console.error(`Maximum retry attempts (${maxRetries}) reached for ${failedUrl}. Stopping polling.`)
|
|
976
|
+
// Clear the polling interval to stop further attempts
|
|
977
|
+
if (pollIntervalRef.current) {
|
|
978
|
+
clearInterval(pollIntervalRef.current)
|
|
979
|
+
pollIntervalRef.current = null
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
} else {
|
|
983
|
+
setLastFailedUrl(failedUrl)
|
|
984
|
+
setRetryCount(1)
|
|
985
|
+
}
|
|
910
986
|
}
|
|
911
|
-
}, [
|
|
987
|
+
}, [
|
|
988
|
+
mode,
|
|
989
|
+
isAtBottom,
|
|
990
|
+
searchParams,
|
|
991
|
+
availableLogs,
|
|
992
|
+
currentLogFile,
|
|
993
|
+
lastLogCount,
|
|
994
|
+
logBuffer,
|
|
995
|
+
logs,
|
|
996
|
+
retryCount,
|
|
997
|
+
lastFailedUrl,
|
|
998
|
+
maxRetries
|
|
999
|
+
])
|
|
912
1000
|
|
|
913
1001
|
// Start/stop polling based on mode (always poll in tail mode, but buffer when not at bottom)
|
|
914
1002
|
useEffect(() => {
|
|
915
|
-
if (mode === "tail") {
|
|
1003
|
+
if (mode === "tail" && retryCount < maxRetries) {
|
|
916
1004
|
pollIntervalRef.current = setInterval(pollForNewLogs, 3000) // Poll every 3 seconds
|
|
917
1005
|
return () => {
|
|
918
1006
|
if (pollIntervalRef.current) {
|
|
@@ -924,16 +1012,25 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
|
|
|
924
1012
|
clearInterval(pollIntervalRef.current)
|
|
925
1013
|
}
|
|
926
1014
|
}
|
|
927
|
-
}, [mode, pollForNewLogs]) // Removed isAtBottom - now always poll in tail mode
|
|
1015
|
+
}, [mode, pollForNewLogs, retryCount, maxRetries]) // Removed isAtBottom - now always poll in tail mode
|
|
928
1016
|
|
|
929
1017
|
// Handle returning to live mode - ONLY flush buffer when user explicitly clicks "Live" button
|
|
930
1018
|
// This effect is removed to prevent race conditions - buffer flushing now happens only on explicit user action
|
|
931
1019
|
|
|
932
1020
|
const loadInitialLogs = useCallback(async () => {
|
|
1021
|
+
if (retryCount >= maxRetries) {
|
|
1022
|
+
console.log("Max retries reached, not attempting to load")
|
|
1023
|
+
return
|
|
1024
|
+
}
|
|
1025
|
+
|
|
933
1026
|
setIsInitialLoading(true)
|
|
934
1027
|
|
|
935
1028
|
// Load available logs list first
|
|
936
|
-
await loadAvailableLogs()
|
|
1029
|
+
const logsListLoaded = await loadAvailableLogs()
|
|
1030
|
+
if (!logsListLoaded) {
|
|
1031
|
+
setIsInitialLoading(false)
|
|
1032
|
+
return
|
|
1033
|
+
}
|
|
937
1034
|
|
|
938
1035
|
try {
|
|
939
1036
|
// Determine which log file to load
|
|
@@ -954,11 +1051,25 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
|
|
|
954
1051
|
? `/api/logs/${mode}?lines=1000&logPath=${encodeURIComponent(logPath)}`
|
|
955
1052
|
: `/api/logs/${mode}?lines=1000`
|
|
956
1053
|
|
|
1054
|
+
// Check if we've exceeded retry limit for this specific URL
|
|
1055
|
+
if (lastFailedUrl === apiUrl && retryCount >= maxRetries) {
|
|
1056
|
+
console.error(`Maximum retry attempts (${maxRetries}) reached for ${apiUrl}. Not attempting to load.`)
|
|
1057
|
+
setLogs([])
|
|
1058
|
+
setIsInitialLoading(false)
|
|
1059
|
+
return
|
|
1060
|
+
}
|
|
1061
|
+
|
|
957
1062
|
const response = await fetch(apiUrl)
|
|
958
1063
|
if (!response.ok) {
|
|
959
1064
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
|
960
1065
|
}
|
|
961
1066
|
|
|
1067
|
+
// Reset retry count on successful fetch
|
|
1068
|
+
if (lastFailedUrl === apiUrl || retryCount > 0) {
|
|
1069
|
+
setRetryCount(0)
|
|
1070
|
+
setLastFailedUrl(null)
|
|
1071
|
+
}
|
|
1072
|
+
|
|
962
1073
|
const data: LogsApiResponse = await response.json()
|
|
963
1074
|
|
|
964
1075
|
if (!data.logs) {
|
|
@@ -991,17 +1102,46 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
|
|
|
991
1102
|
}
|
|
992
1103
|
} catch (error) {
|
|
993
1104
|
console.error("Error loading logs:", error)
|
|
1105
|
+
|
|
1106
|
+
// Build the failed URL for tracking
|
|
1107
|
+
const requestedFile = searchParams.get("file")
|
|
1108
|
+
let logPath = ""
|
|
1109
|
+
if (requestedFile && availableLogs.length > 0) {
|
|
1110
|
+
const foundFile = availableLogs.find((f) => f.name === requestedFile)
|
|
1111
|
+
logPath = foundFile?.path || currentLogFile
|
|
1112
|
+
} else {
|
|
1113
|
+
logPath = currentLogFile
|
|
1114
|
+
}
|
|
1115
|
+
const failedUrl = logPath
|
|
1116
|
+
? `/api/logs/${mode}?lines=1000&logPath=${encodeURIComponent(logPath)}`
|
|
1117
|
+
: `/api/logs/${mode}?lines=1000`
|
|
1118
|
+
|
|
1119
|
+
// Track retry attempts
|
|
1120
|
+
if (lastFailedUrl === failedUrl) {
|
|
1121
|
+
const newRetryCount = retryCount + 1
|
|
1122
|
+
setRetryCount(newRetryCount)
|
|
1123
|
+
|
|
1124
|
+
if (newRetryCount >= maxRetries) {
|
|
1125
|
+
console.error(`Maximum retry attempts (${maxRetries}) reached for initial load of ${failedUrl}.`)
|
|
1126
|
+
}
|
|
1127
|
+
} else {
|
|
1128
|
+
setLastFailedUrl(failedUrl)
|
|
1129
|
+
setRetryCount(1)
|
|
1130
|
+
}
|
|
1131
|
+
|
|
994
1132
|
setLogs([])
|
|
1133
|
+
} finally {
|
|
1134
|
+
setIsInitialLoading(false)
|
|
995
1135
|
}
|
|
996
|
-
}, [loadAvailableLogs, searchParams, availableLogs, currentLogFile, mode])
|
|
1136
|
+
}, [loadAvailableLogs, searchParams, availableLogs, currentLogFile, mode, retryCount, lastFailedUrl, maxRetries])
|
|
997
1137
|
|
|
998
1138
|
useEffect(() => {
|
|
999
|
-
// Only load logs if we don't have initial data
|
|
1000
|
-
const _currentMode = searchParams.get("mode") || "tail"
|
|
1139
|
+
// Only load logs if we don't have initial data and haven't tried loading yet
|
|
1001
1140
|
const hasInitialData = initialData?.logs && initialData?.logs.length > 0
|
|
1002
1141
|
|
|
1003
|
-
if (!hasInitialData && !logs.length) {
|
|
1142
|
+
if (!hasLoadedInitial && !hasInitialData && !logs.length) {
|
|
1004
1143
|
// No server-side data and no client data - load fresh
|
|
1144
|
+
setHasLoadedInitial(true)
|
|
1005
1145
|
loadInitialLogs()
|
|
1006
1146
|
} else if (hasInitialData && logs.length === 0) {
|
|
1007
1147
|
// We have server-side data but client state is empty - use server data
|
|
@@ -1022,22 +1162,10 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
|
|
|
1022
1162
|
setTimeout(scrollToBottom, 100)
|
|
1023
1163
|
setTimeout(scrollToBottom, 300)
|
|
1024
1164
|
}
|
|
1025
|
-
} else if (mode === "tail" && isAtBottom) {
|
|
1026
|
-
// Set up polling timer for new logs if we're in tail mode
|
|
1027
|
-
setIsInitialLoading(false)
|
|
1028
|
-
pollIntervalRef.current = setInterval(() => {
|
|
1029
|
-
pollForNewLogs()
|
|
1030
|
-
}, 3000)
|
|
1031
1165
|
}
|
|
1032
|
-
}, [
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
isAtBottom, // No server-side data and no client data - load fresh
|
|
1036
|
-
loadInitialLogs,
|
|
1037
|
-
logs.length,
|
|
1038
|
-
pollForNewLogs,
|
|
1039
|
-
searchParams.get
|
|
1040
|
-
]) // Only depend on mode to avoid infinite loops
|
|
1166
|
+
}, [mode, initialData, logs.length, hasLoadedInitial, loadInitialLogs]) // eslint-disable-line react-hooks/exhaustive-deps
|
|
1167
|
+
// CRITICAL: loadInitialLogs must NEVER be added to dependencies - it will cause infinite loops
|
|
1168
|
+
// The linter will complain but this is intentional. DO NOT "FIX" THIS.
|
|
1041
1169
|
|
|
1042
1170
|
// Separate effect to handle scrolling after logs are rendered
|
|
1043
1171
|
useEffect(() => {
|
|
@@ -1291,17 +1419,15 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
|
|
|
1291
1419
|
}, [logs, filters, userAgentFilters, availableUserAgents])
|
|
1292
1420
|
|
|
1293
1421
|
return (
|
|
1294
|
-
<div className="h-screen bg-
|
|
1422
|
+
<div className="h-screen bg-background text-foreground flex flex-col transition-colors">
|
|
1295
1423
|
{/* Header - Fixed */}
|
|
1296
|
-
<div className="bg-
|
|
1424
|
+
<div className="bg-card shadow-sm border-b border-border flex-none z-10">
|
|
1297
1425
|
<div className="max-w-7xl mx-auto px-4 py-3">
|
|
1298
1426
|
<div className="flex items-center justify-between">
|
|
1299
1427
|
<div className="flex items-center gap-2 sm:gap-4">
|
|
1300
1428
|
<div className="flex items-center gap-1">
|
|
1301
|
-
<h1 className="text-xl sm:text-2xl font-bold
|
|
1302
|
-
|
|
1303
|
-
</h1>
|
|
1304
|
-
<span className="text-xs text-gray-400 dark:text-gray-500 whitespace-nowrap">(v{version})</span>
|
|
1429
|
+
<h1 className="text-xl sm:text-2xl font-bold whitespace-nowrap">dev3000</h1>
|
|
1430
|
+
<span className="text-xs text-muted-foreground whitespace-nowrap">(v{version})</span>
|
|
1305
1431
|
</div>
|
|
1306
1432
|
{/* Log File Selector */}
|
|
1307
1433
|
{availableLogs.length > 1 ? (
|
|
@@ -1309,19 +1435,13 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
|
|
|
1309
1435
|
<button
|
|
1310
1436
|
type="button"
|
|
1311
1437
|
onClick={() => setShowLogSelector(!showLogSelector)}
|
|
1312
|
-
className="flex items-center gap-2 px-3 py-1 text-sm text-
|
|
1438
|
+
className="flex items-center gap-2 px-3 py-1 text-sm text-muted-foreground hover:text-foreground hover:bg-accent rounded-md transition-colors"
|
|
1313
1439
|
>
|
|
1314
1440
|
<span className="font-mono text-xs whitespace-nowrap">
|
|
1315
1441
|
{isInitialLoading && !currentLogFile ? (
|
|
1316
|
-
<div
|
|
1317
|
-
className="h-4 bg-gray-200 dark:bg-gray-700 rounded animate-pulse"
|
|
1318
|
-
style={{ width: "220px" }}
|
|
1319
|
-
/>
|
|
1442
|
+
<div className="h-4 bg-muted rounded animate-pulse" style={{ width: "220px" }} />
|
|
1320
1443
|
) : currentLogFile ? (
|
|
1321
|
-
|
|
1322
|
-
currentLogFile
|
|
1323
|
-
.split("/")
|
|
1324
|
-
.pop()
|
|
1444
|
+
"Current"
|
|
1325
1445
|
) : (
|
|
1326
1446
|
"No log file"
|
|
1327
1447
|
)}
|
|
@@ -1337,9 +1457,9 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
|
|
|
1337
1457
|
</button>
|
|
1338
1458
|
{/* Dropdown */}
|
|
1339
1459
|
{showLogSelector && availableLogs.length > 1 && (
|
|
1340
|
-
<div className="absolute top-full left-0 mt-1 bg-
|
|
1460
|
+
<div className="absolute top-full left-0 mt-1 bg-card border border-border rounded-md shadow-lg z-20 min-w-80">
|
|
1341
1461
|
<div className="py-1 max-h-60 overflow-y-auto">
|
|
1342
|
-
<div className="px-3 py-2 text-xs font-medium text-
|
|
1462
|
+
<div className="px-3 py-2 text-xs font-medium text-muted-foreground border-b border-border">
|
|
1343
1463
|
{projectName} logs ({availableLogs.length})
|
|
1344
1464
|
</div>
|
|
1345
1465
|
{availableLogs.map((logFile) => (
|
|
@@ -1350,13 +1470,13 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
|
|
|
1350
1470
|
setShowLogSelector(false)
|
|
1351
1471
|
router.push(`/logs?file=${encodeURIComponent(logFile.name)}&mode=${mode}`)
|
|
1352
1472
|
}}
|
|
1353
|
-
className={`w-full text-left px-3 py-2 text-sm hover:bg-
|
|
1354
|
-
logFile.isCurrent ? "bg-
|
|
1473
|
+
className={`w-full text-left px-3 py-2 text-sm hover:bg-accent flex items-center justify-between ${
|
|
1474
|
+
logFile.isCurrent ? "bg-primary/10 text-primary" : "text-foreground"
|
|
1355
1475
|
}`}
|
|
1356
1476
|
>
|
|
1357
1477
|
<div className="flex flex-col">
|
|
1358
1478
|
<span className="font-mono text-xs">{logFile.name}</span>
|
|
1359
|
-
<span className="text-xs text-
|
|
1479
|
+
<span className="text-xs text-muted-foreground">
|
|
1360
1480
|
{new Date(logFile.mtime).toLocaleString()} • {Math.round(logFile.size / 1024)}KB
|
|
1361
1481
|
</span>
|
|
1362
1482
|
</div>
|
|
@@ -1384,8 +1504,29 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
|
|
|
1384
1504
|
</div>
|
|
1385
1505
|
)}
|
|
1386
1506
|
|
|
1387
|
-
{/* Entries count */}
|
|
1388
|
-
{logs.length > 0 &&
|
|
1507
|
+
{/* Entries count with clear button */}
|
|
1508
|
+
{logs.length > 0 && (
|
|
1509
|
+
<div className="flex items-center gap-1 hidden sm:flex">
|
|
1510
|
+
<span className="text-sm text-muted-foreground">{logs.length} entries</span>
|
|
1511
|
+
{currentLogFile && !isInitialLoading && (
|
|
1512
|
+
<button
|
|
1513
|
+
type="button"
|
|
1514
|
+
onClick={handleRotateLog}
|
|
1515
|
+
disabled={isRotatingLog}
|
|
1516
|
+
className="p-0.5 text-muted-foreground hover:text-foreground hover:bg-accent rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
|
1517
|
+
title="Clear logs (rotate current log to archive and start fresh)"
|
|
1518
|
+
>
|
|
1519
|
+
{isRotatingLog ? (
|
|
1520
|
+
<div className="w-3 h-3 border border-current border-t-transparent rounded-full animate-spin" />
|
|
1521
|
+
) : (
|
|
1522
|
+
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
1523
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
|
1524
|
+
</svg>
|
|
1525
|
+
)}
|
|
1526
|
+
</button>
|
|
1527
|
+
)}
|
|
1528
|
+
</div>
|
|
1529
|
+
)}
|
|
1389
1530
|
|
|
1390
1531
|
{/* Buffered logs indicator */}
|
|
1391
1532
|
{logBuffer.length > 0 && !isAtBottom && (
|
|
@@ -1393,19 +1534,6 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
|
|
|
1393
1534
|
+{logBuffer.length} buffered
|
|
1394
1535
|
</span>
|
|
1395
1536
|
)}
|
|
1396
|
-
|
|
1397
|
-
{/* Clear button - always visible when we have a current log file */}
|
|
1398
|
-
{currentLogFile && !isInitialLoading && (
|
|
1399
|
-
<button
|
|
1400
|
-
type="button"
|
|
1401
|
-
onClick={handleRotateLog}
|
|
1402
|
-
disabled={isRotatingLog}
|
|
1403
|
-
className="px-2 py-1 text-xs bg-orange-100 text-orange-700 hover:bg-orange-200 rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
|
1404
|
-
title="Clear logs (rotate current log to archive and start fresh)"
|
|
1405
|
-
>
|
|
1406
|
-
{isRotatingLog ? "..." : "Clear"}
|
|
1407
|
-
</button>
|
|
1408
|
-
)}
|
|
1409
1537
|
</div>
|
|
1410
1538
|
|
|
1411
1539
|
{/* Mode Toggle */}
|
|
@@ -1472,7 +1600,7 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
|
|
|
1472
1600
|
? "bg-blue-100 text-blue-800"
|
|
1473
1601
|
: event.type === "SCROLL"
|
|
1474
1602
|
? "bg-green-100 text-green-800"
|
|
1475
|
-
: "bg-
|
|
1603
|
+
: "bg-muted text-muted-foreground"
|
|
1476
1604
|
}`}
|
|
1477
1605
|
>
|
|
1478
1606
|
{event.type}
|
|
@@ -1501,7 +1629,7 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
|
|
|
1501
1629
|
<button
|
|
1502
1630
|
type="button"
|
|
1503
1631
|
onClick={() => setShowFilters(!showFilters)}
|
|
1504
|
-
className="flex items-center gap-1 px-3 py-1 rounded text-sm font-medium transition-colors whitespace-nowrap bg-
|
|
1632
|
+
className="flex items-center gap-1 px-3 py-1 rounded text-sm font-medium transition-colors whitespace-nowrap bg-muted text-muted-foreground hover:bg-muted/80"
|
|
1505
1633
|
>
|
|
1506
1634
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
1507
1635
|
<path
|
|
@@ -1515,14 +1643,14 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
|
|
|
1515
1643
|
</button>
|
|
1516
1644
|
{/* Filter Dropdown */}
|
|
1517
1645
|
{showFilters && (
|
|
1518
|
-
<div className="absolute top-full right-0 mt-1 bg-
|
|
1646
|
+
<div className="absolute top-full right-0 mt-1 bg-card border border-border rounded-md shadow-lg z-20 min-w-48">
|
|
1519
1647
|
<div className="py-2">
|
|
1520
|
-
<div className="px-3 py-2 text-xs font-medium text-
|
|
1648
|
+
<div className="px-3 py-2 text-xs font-medium text-muted-foreground border-b border-border">
|
|
1521
1649
|
Log Types
|
|
1522
1650
|
</div>
|
|
1523
1651
|
|
|
1524
1652
|
{/* Server Logs */}
|
|
1525
|
-
<label className="flex items-center justify-between px-3 py-2 text-sm hover:bg-
|
|
1653
|
+
<label className="flex items-center justify-between px-3 py-2 text-sm hover:bg-accent cursor-pointer">
|
|
1526
1654
|
<div className="flex items-center gap-2">
|
|
1527
1655
|
<input
|
|
1528
1656
|
type="checkbox"
|
|
@@ -1533,17 +1661,17 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
|
|
|
1533
1661
|
server: e.target.checked
|
|
1534
1662
|
}))
|
|
1535
1663
|
}
|
|
1536
|
-
className="rounded border-
|
|
1664
|
+
className="rounded border-input text-primary focus:ring-ring"
|
|
1537
1665
|
/>
|
|
1538
|
-
<span className="text-
|
|
1666
|
+
<span className="text-foreground">Server</span>
|
|
1539
1667
|
</div>
|
|
1540
|
-
<span className="text-xs text-
|
|
1668
|
+
<span className="text-xs text-muted-foreground">
|
|
1541
1669
|
{logs.filter((l) => l.source === "SERVER").length}
|
|
1542
1670
|
</span>
|
|
1543
1671
|
</label>
|
|
1544
1672
|
|
|
1545
1673
|
{/* Browser Logs */}
|
|
1546
|
-
<label className="flex items-center justify-between px-3 py-2 text-sm hover:bg-
|
|
1674
|
+
<label className="flex items-center justify-between px-3 py-2 text-sm hover:bg-accent cursor-pointer">
|
|
1547
1675
|
<div className="flex items-center gap-2">
|
|
1548
1676
|
<input
|
|
1549
1677
|
type="checkbox"
|
|
@@ -1554,18 +1682,18 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
|
|
|
1554
1682
|
browser: e.target.checked
|
|
1555
1683
|
}))
|
|
1556
1684
|
}
|
|
1557
|
-
className="rounded border-
|
|
1685
|
+
className="rounded border-input text-primary focus:ring-ring"
|
|
1558
1686
|
/>
|
|
1559
|
-
<span className="text-
|
|
1687
|
+
<span className="text-foreground">Browser</span>
|
|
1560
1688
|
</div>
|
|
1561
|
-
<span className="text-xs text-
|
|
1689
|
+
<span className="text-xs text-muted-foreground">
|
|
1562
1690
|
{logs.filter((l) => l.source === "BROWSER").length}
|
|
1563
1691
|
</span>
|
|
1564
1692
|
</label>
|
|
1565
1693
|
|
|
1566
1694
|
{/* User Agent Sub-filters */}
|
|
1567
1695
|
{availableUserAgents.length > 1 && filters.browser && (
|
|
1568
|
-
<div className="ml-6 border-l border-
|
|
1696
|
+
<div className="ml-6 border-l border-border pl-2">
|
|
1569
1697
|
{availableUserAgents.map((ua) => {
|
|
1570
1698
|
const shortUA = ua.includes("Chrome")
|
|
1571
1699
|
? "Chrome"
|
|
@@ -1579,7 +1707,7 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
|
|
|
1579
1707
|
return (
|
|
1580
1708
|
<label
|
|
1581
1709
|
key={ua}
|
|
1582
|
-
className="flex items-center justify-between px-2 py-1 text-xs hover:bg-
|
|
1710
|
+
className="flex items-center justify-between px-2 py-1 text-xs hover:bg-accent cursor-pointer"
|
|
1583
1711
|
>
|
|
1584
1712
|
<div className="flex items-center gap-2">
|
|
1585
1713
|
<input
|
|
@@ -1591,11 +1719,11 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
|
|
|
1591
1719
|
[ua]: e.target.checked
|
|
1592
1720
|
}))
|
|
1593
1721
|
}
|
|
1594
|
-
className="rounded border-
|
|
1722
|
+
className="rounded border-input text-primary focus:ring-ring w-3 h-3"
|
|
1595
1723
|
/>
|
|
1596
|
-
<span className="text-
|
|
1724
|
+
<span className="text-foreground">{shortUA}</span>
|
|
1597
1725
|
</div>
|
|
1598
|
-
<span className="text-xs text-
|
|
1726
|
+
<span className="text-xs text-muted-foreground">
|
|
1599
1727
|
{logs.filter((l) => l.source === "BROWSER" && l.userAgent === ua).length}
|
|
1600
1728
|
</span>
|
|
1601
1729
|
</label>
|
|
@@ -1605,7 +1733,7 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
|
|
|
1605
1733
|
)}
|
|
1606
1734
|
|
|
1607
1735
|
{/* Interaction Logs */}
|
|
1608
|
-
<label className="flex items-center justify-between px-3 py-2 text-sm hover:bg-
|
|
1736
|
+
<label className="flex items-center justify-between px-3 py-2 text-sm hover:bg-accent cursor-pointer">
|
|
1609
1737
|
<div className="flex items-center gap-2">
|
|
1610
1738
|
<input
|
|
1611
1739
|
type="checkbox"
|
|
@@ -1616,17 +1744,17 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
|
|
|
1616
1744
|
interaction: e.target.checked
|
|
1617
1745
|
}))
|
|
1618
1746
|
}
|
|
1619
|
-
className="rounded border-
|
|
1747
|
+
className="rounded border-input text-primary focus:ring-ring"
|
|
1620
1748
|
/>
|
|
1621
|
-
<span className="text-
|
|
1749
|
+
<span className="text-foreground">Interaction</span>
|
|
1622
1750
|
</div>
|
|
1623
|
-
<span className="text-xs text-
|
|
1751
|
+
<span className="text-xs text-muted-foreground">
|
|
1624
1752
|
{logs.filter((l) => l.message.includes("[INTERACTION]")).length}
|
|
1625
1753
|
</span>
|
|
1626
1754
|
</label>
|
|
1627
1755
|
|
|
1628
1756
|
{/* Screenshot Logs */}
|
|
1629
|
-
<label className="flex items-center justify-between px-3 py-2 text-sm hover:bg-
|
|
1757
|
+
<label className="flex items-center justify-between px-3 py-2 text-sm hover:bg-accent cursor-pointer">
|
|
1630
1758
|
<div className="flex items-center gap-2">
|
|
1631
1759
|
<input
|
|
1632
1760
|
type="checkbox"
|
|
@@ -1637,11 +1765,11 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
|
|
|
1637
1765
|
screenshot: e.target.checked
|
|
1638
1766
|
}))
|
|
1639
1767
|
}
|
|
1640
|
-
className="rounded border-
|
|
1768
|
+
className="rounded border-input text-primary focus:ring-ring"
|
|
1641
1769
|
/>
|
|
1642
|
-
<span className="text-
|
|
1770
|
+
<span className="text-foreground">Screenshot</span>
|
|
1643
1771
|
</div>
|
|
1644
|
-
<span className="text-xs text-
|
|
1772
|
+
<span className="text-xs text-muted-foreground">
|
|
1645
1773
|
{logs.filter((l) => l.message.includes("[SCREENSHOT]")).length}
|
|
1646
1774
|
</span>
|
|
1647
1775
|
</label>
|
|
@@ -1649,7 +1777,8 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
|
|
|
1649
1777
|
</div>
|
|
1650
1778
|
)}
|
|
1651
1779
|
</div>
|
|
1652
|
-
|
|
1780
|
+
{/* Head/Tail toggle - commented out for now as it's too noisy */}
|
|
1781
|
+
{/* <div className="flex items-center bg-muted rounded-md p-1">
|
|
1653
1782
|
<button
|
|
1654
1783
|
type="button"
|
|
1655
1784
|
onClick={() => {
|
|
@@ -1661,7 +1790,9 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
|
|
|
1661
1790
|
}
|
|
1662
1791
|
}}
|
|
1663
1792
|
className={`px-2 sm:px-3 py-1 rounded text-xs sm:text-sm font-medium transition-colors whitespace-nowrap ${
|
|
1664
|
-
mode === "head"
|
|
1793
|
+
mode === "head"
|
|
1794
|
+
? "bg-background text-foreground shadow-sm"
|
|
1795
|
+
: "text-muted-foreground hover:text-foreground"
|
|
1665
1796
|
}`}
|
|
1666
1797
|
>
|
|
1667
1798
|
Head
|
|
@@ -1677,41 +1808,16 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
|
|
|
1677
1808
|
}
|
|
1678
1809
|
}}
|
|
1679
1810
|
className={`px-2 sm:px-3 py-1 rounded text-xs sm:text-sm font-medium transition-colors whitespace-nowrap ${
|
|
1680
|
-
mode === "tail"
|
|
1811
|
+
mode === "tail"
|
|
1812
|
+
? "bg-background text-foreground shadow-sm"
|
|
1813
|
+
: "text-muted-foreground hover:text-foreground"
|
|
1681
1814
|
}`}
|
|
1682
1815
|
>
|
|
1683
1816
|
Tail
|
|
1684
1817
|
</button>
|
|
1685
|
-
</div>
|
|
1818
|
+
</div> */}
|
|
1686
1819
|
{/* Dark Mode Toggle - moved to last item */}
|
|
1687
|
-
<
|
|
1688
|
-
type="button"
|
|
1689
|
-
onClick={() => setDarkMode(!darkMode)}
|
|
1690
|
-
className="p-2 rounded-md text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors ml-2"
|
|
1691
|
-
title={darkMode ? "Switch to light mode" : "Switch to dark mode"}
|
|
1692
|
-
>
|
|
1693
|
-
{darkMode ? (
|
|
1694
|
-
// Sun icon for light mode
|
|
1695
|
-
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
1696
|
-
<path
|
|
1697
|
-
strokeLinecap="round"
|
|
1698
|
-
strokeLinejoin="round"
|
|
1699
|
-
strokeWidth={2}
|
|
1700
|
-
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"
|
|
1701
|
-
/>
|
|
1702
|
-
</svg>
|
|
1703
|
-
) : (
|
|
1704
|
-
// Moon icon for dark mode
|
|
1705
|
-
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
1706
|
-
<path
|
|
1707
|
-
strokeLinecap="round"
|
|
1708
|
-
strokeLinejoin="round"
|
|
1709
|
-
strokeWidth={2}
|
|
1710
|
-
d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"
|
|
1711
|
-
/>
|
|
1712
|
-
</svg>
|
|
1713
|
-
)}
|
|
1714
|
-
</button>
|
|
1820
|
+
<DarkModeToggle darkMode={darkMode} setDarkMode={setDarkMode} className="ml-2" />
|
|
1715
1821
|
</div>
|
|
1716
1822
|
</div>
|
|
1717
1823
|
</div>
|
|
@@ -1722,27 +1828,45 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
|
|
|
1722
1828
|
<div ref={containerRef} className="max-w-7xl mx-auto px-4 py-6 h-full overflow-y-auto" onScroll={handleScroll}>
|
|
1723
1829
|
{isInitialLoading ? (
|
|
1724
1830
|
<div className="text-center py-12">
|
|
1725
|
-
<div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-
|
|
1726
|
-
<div className="text-
|
|
1831
|
+
<div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
|
|
1832
|
+
<div className="text-muted-foreground text-sm mt-4">Loading logs...</div>
|
|
1727
1833
|
</div>
|
|
1728
1834
|
) : logs.length === 0 ? (
|
|
1729
1835
|
<div className="text-center py-12">
|
|
1730
|
-
<div className="text-
|
|
1731
|
-
<div className="text-
|
|
1732
|
-
|
|
1836
|
+
<div className="text-muted-foreground text-lg">📝 No logs yet</div>
|
|
1837
|
+
<div className="text-muted-foreground text-sm mt-2">
|
|
1838
|
+
{retryCount >= maxRetries ? (
|
|
1839
|
+
<>
|
|
1840
|
+
<p className="text-red-500 mb-2">Failed to load logs after {maxRetries} attempts.</p>
|
|
1841
|
+
<button
|
|
1842
|
+
type="button"
|
|
1843
|
+
onClick={() => {
|
|
1844
|
+
setRetryCount(0)
|
|
1845
|
+
setLastFailedUrl(null)
|
|
1846
|
+
setHasLoadedInitial(false)
|
|
1847
|
+
loadInitialLogs()
|
|
1848
|
+
}}
|
|
1849
|
+
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors"
|
|
1850
|
+
>
|
|
1851
|
+
Retry
|
|
1852
|
+
</button>
|
|
1853
|
+
</>
|
|
1854
|
+
) : (
|
|
1855
|
+
"Logs will appear here as your development server runs"
|
|
1856
|
+
)}
|
|
1733
1857
|
</div>
|
|
1734
1858
|
</div>
|
|
1735
1859
|
) : filteredLogs.length === 0 ? (
|
|
1736
1860
|
<div className="text-center py-12">
|
|
1737
|
-
<div className="text-
|
|
1738
|
-
<div className="text-
|
|
1861
|
+
<div className="text-muted-foreground text-lg">🔍 No logs match current filters</div>
|
|
1862
|
+
<div className="text-muted-foreground text-sm mt-2">
|
|
1739
1863
|
Try adjusting your filter settings to see more logs
|
|
1740
1864
|
</div>
|
|
1741
1865
|
</div>
|
|
1742
1866
|
) : (
|
|
1743
1867
|
<div className="space-y-1 pb-4">
|
|
1744
1868
|
{filteredLogs.map((entry, index) => (
|
|
1745
|
-
<LogEntryComponent key={`${entry.timestamp}-${index}`} entry={entry} />
|
|
1869
|
+
<LogEntryComponent key={`${entry.timestamp}-${index}`} entry={entry} darkMode={darkMode} />
|
|
1746
1870
|
))}
|
|
1747
1871
|
<div ref={bottomRef} />
|
|
1748
1872
|
</div>
|
|
@@ -1751,23 +1875,16 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
|
|
|
1751
1875
|
</div>
|
|
1752
1876
|
|
|
1753
1877
|
{/* Footer - Fixed */}
|
|
1754
|
-
<div className="border-t border-
|
|
1878
|
+
<div className="border-t border-border bg-muted/50 flex-none">
|
|
1755
1879
|
<div className="max-w-7xl mx-auto px-4 py-3 flex items-center justify-between">
|
|
1756
1880
|
<div className="flex items-center gap-3">
|
|
1757
1881
|
{lastFetched && (
|
|
1758
|
-
<span className="text-xs text-
|
|
1882
|
+
<span className="text-xs text-muted-foreground font-mono">
|
|
1759
1883
|
Last updated {lastFetched.toLocaleTimeString()}
|
|
1760
1884
|
</span>
|
|
1761
1885
|
)}
|
|
1762
1886
|
{currentLogFile && (
|
|
1763
|
-
<
|
|
1764
|
-
href={`file://${currentLogFile}`}
|
|
1765
|
-
target="_blank"
|
|
1766
|
-
rel="noopener noreferrer"
|
|
1767
|
-
className="text-xs text-blue-600 hover:text-blue-800 flex items-center gap-1"
|
|
1768
|
-
>
|
|
1769
|
-
Raw Log ↗
|
|
1770
|
-
</a>
|
|
1887
|
+
<span className="text-xs text-muted-foreground font-mono break-all">{currentLogFile}</span>
|
|
1771
1888
|
)}
|
|
1772
1889
|
</div>
|
|
1773
1890
|
|