@alepha/devtools 0.16.0 → 0.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/README.md +1 -5
  2. package/dist/index.browser.js +224 -0
  3. package/dist/index.browser.js.map +1 -0
  4. package/dist/index.d.ts +349 -321
  5. package/dist/index.js +293 -186
  6. package/dist/index.js.map +1 -1
  7. package/package.json +30 -23
  8. package/src/assets.ts +6 -0
  9. package/src/{api/entities → entities}/logs.ts +2 -2
  10. package/src/index.browser.ts +11 -0
  11. package/src/index.shared.ts +15 -0
  12. package/src/index.ts +11 -37
  13. package/src/{api/providers → providers}/DevToolsMetadataProvider.ts +84 -47
  14. package/src/providers/DevToolsProvider.ts +280 -0
  15. package/src/{api/schemas → schemas}/DevActionMetadata.ts +8 -0
  16. package/src/{api/schemas → schemas}/DevEntityMetadata.ts +3 -0
  17. package/src/{api/schemas → schemas}/DevMetadata.ts +13 -2
  18. package/src/{api/schemas → schemas}/DevPageMetadata.ts +3 -0
  19. package/src/{api/schemas → schemas}/DevTopicMetadata.ts +1 -0
  20. package/src/ui/AppRouter.tsx +55 -59
  21. package/src/ui/components/DevLayout.tsx +104 -84
  22. package/src/ui/components/configuration/ConfigAtoms.page.tsx +5 -0
  23. package/src/ui/components/configuration/ConfigAtoms.tsx +511 -0
  24. package/src/ui/components/configuration/ConfigEnv.page.tsx +5 -0
  25. package/src/ui/components/configuration/ConfigEnv.tsx +230 -0
  26. package/src/ui/components/configuration/DevConfiguration.tsx +36 -0
  27. package/src/ui/components/configuration/index.ts +3 -0
  28. package/src/ui/components/dashboard/DevDashboard.tsx +482 -0
  29. package/src/ui/components/database/DatabaseEditor.page.tsx +23 -0
  30. package/src/ui/components/database/DatabaseEditor.tsx +399 -0
  31. package/src/ui/components/database/DatabaseErd.page.tsx +28 -0
  32. package/src/ui/components/database/DatabaseErd.tsx +107 -0
  33. package/src/ui/components/database/DevDatabase.tsx +36 -0
  34. package/src/ui/components/database/EntityNode.tsx +83 -0
  35. package/src/ui/components/explorer/DevExplorer.tsx +351 -0
  36. package/src/ui/components/explorer/ExplorerTree.tsx +178 -0
  37. package/src/ui/components/explorer/panels/DevPanelAction.tsx +499 -0
  38. package/src/ui/components/explorer/panels/DevPanelCache.tsx +73 -0
  39. package/src/ui/components/explorer/panels/DevPanelPage.tsx +96 -0
  40. package/src/ui/components/explorer/panels/DevPanelQueue.tsx +51 -0
  41. package/src/ui/components/explorer/panels/DevPanelTopic.tsx +56 -0
  42. package/src/ui/components/explorer/panels/index.ts +5 -0
  43. package/src/ui/components/graph/DevDependencyGraph.tsx +35 -60
  44. package/src/ui/components/graph/GraphControls.tsx +10 -11
  45. package/src/ui/components/graph/NodeDetails.tsx +22 -29
  46. package/src/ui/components/graph/ProviderNode.tsx +4 -4
  47. package/src/ui/components/graph/helpers.ts +1 -1
  48. package/src/ui/components/logs/DevLogs.tsx +661 -0
  49. package/src/ui/components/logs/index.ts +1 -0
  50. package/src/ui/components/shared/TreeView.tsx +189 -0
  51. package/src/ui/main.css +17 -0
  52. package/src/ui/main.ts +2 -6
  53. package/LICENSE +0 -21
  54. package/assets/devtools/actions.html +0 -21
  55. package/assets/devtools/actions.html.br +0 -0
  56. package/assets/devtools/actions.html.gz +0 -0
  57. package/assets/devtools/asset.BJSLFcNT.css +0 -1
  58. package/assets/devtools/asset.BJSLFcNT.css.br +0 -0
  59. package/assets/devtools/asset.BJSLFcNT.css.gz +0 -0
  60. package/assets/devtools/asset.BZV40eAE.css +0 -1
  61. package/assets/devtools/asset.BZV40eAE.css.br +0 -0
  62. package/assets/devtools/asset.BZV40eAE.css.gz +0 -0
  63. package/assets/devtools/atoms.html +0 -21
  64. package/assets/devtools/atoms.html.br +0 -0
  65. package/assets/devtools/atoms.html.gz +0 -0
  66. package/assets/devtools/caches.html +0 -21
  67. package/assets/devtools/caches.html.br +0 -0
  68. package/assets/devtools/caches.html.gz +0 -0
  69. package/assets/devtools/chunk.1h5GuATm.js +0 -1
  70. package/assets/devtools/chunk.1h5GuATm.js.br +0 -0
  71. package/assets/devtools/chunk.1h5GuATm.js.gz +0 -0
  72. package/assets/devtools/chunk.3PgxxOdM.js +0 -1
  73. package/assets/devtools/chunk.3PgxxOdM.js.br +0 -0
  74. package/assets/devtools/chunk.3PgxxOdM.js.gz +0 -0
  75. package/assets/devtools/chunk.A_W3H6Aa.js +0 -1
  76. package/assets/devtools/chunk.A_W3H6Aa.js.br +0 -0
  77. package/assets/devtools/chunk.A_W3H6Aa.js.gz +0 -0
  78. package/assets/devtools/chunk.B5tL0VjH.js +0 -1
  79. package/assets/devtools/chunk.B5tL0VjH.js.br +0 -0
  80. package/assets/devtools/chunk.B5tL0VjH.js.gz +0 -0
  81. package/assets/devtools/chunk.B8p_Szro.js +0 -1
  82. package/assets/devtools/chunk.B8p_Szro.js.br +0 -0
  83. package/assets/devtools/chunk.B8p_Szro.js.gz +0 -0
  84. package/assets/devtools/chunk.BANy8c2v.js +0 -1
  85. package/assets/devtools/chunk.BANy8c2v.js.br +0 -0
  86. package/assets/devtools/chunk.BANy8c2v.js.gz +0 -0
  87. package/assets/devtools/chunk.BKph0hv1.js +0 -1
  88. package/assets/devtools/chunk.BKph0hv1.js.br +0 -0
  89. package/assets/devtools/chunk.BKph0hv1.js.gz +0 -0
  90. package/assets/devtools/chunk.BUs1kuwE.js +0 -1
  91. package/assets/devtools/chunk.BUs1kuwE.js.br +0 -0
  92. package/assets/devtools/chunk.BUs1kuwE.js.gz +0 -0
  93. package/assets/devtools/chunk.BVIEr21R.js +0 -1
  94. package/assets/devtools/chunk.BVIEr21R.js.br +0 -0
  95. package/assets/devtools/chunk.BVIEr21R.js.gz +0 -0
  96. package/assets/devtools/chunk.Bb3re2d8.js +0 -1
  97. package/assets/devtools/chunk.Bb3re2d8.js.br +0 -2
  98. package/assets/devtools/chunk.Bb3re2d8.js.gz +0 -0
  99. package/assets/devtools/chunk.BjFrJKj1.js +0 -1
  100. package/assets/devtools/chunk.BjFrJKj1.js.br +0 -2
  101. package/assets/devtools/chunk.BjFrJKj1.js.gz +0 -0
  102. package/assets/devtools/chunk.BkXzz14p.js +0 -1
  103. package/assets/devtools/chunk.BkXzz14p.js.br +0 -0
  104. package/assets/devtools/chunk.BkXzz14p.js.gz +0 -0
  105. package/assets/devtools/chunk.BlqFPyLh.js +0 -1
  106. package/assets/devtools/chunk.BlqFPyLh.js.br +0 -0
  107. package/assets/devtools/chunk.BlqFPyLh.js.gz +0 -0
  108. package/assets/devtools/chunk.BymZ9jU5.js +0 -1
  109. package/assets/devtools/chunk.BymZ9jU5.js.br +0 -0
  110. package/assets/devtools/chunk.BymZ9jU5.js.gz +0 -0
  111. package/assets/devtools/chunk.C0BD3Ujz.js +0 -1
  112. package/assets/devtools/chunk.C0BD3Ujz.js.br +0 -0
  113. package/assets/devtools/chunk.C0BD3Ujz.js.gz +0 -0
  114. package/assets/devtools/chunk.C63rzhbT.js +0 -1
  115. package/assets/devtools/chunk.C63rzhbT.js.br +0 -0
  116. package/assets/devtools/chunk.C63rzhbT.js.gz +0 -0
  117. package/assets/devtools/chunk.CJrYVzjN.js +0 -9
  118. package/assets/devtools/chunk.CJrYVzjN.js.br +0 -0
  119. package/assets/devtools/chunk.CJrYVzjN.js.gz +0 -0
  120. package/assets/devtools/chunk.CPGX3Xpx.js +0 -1
  121. package/assets/devtools/chunk.CPGX3Xpx.js.br +0 -1
  122. package/assets/devtools/chunk.CPGX3Xpx.js.gz +0 -0
  123. package/assets/devtools/chunk.Cf-3skUw.js +0 -1
  124. package/assets/devtools/chunk.Cf-3skUw.js.br +0 -0
  125. package/assets/devtools/chunk.Cf-3skUw.js.gz +0 -0
  126. package/assets/devtools/chunk.D7JLxcoJ.js +0 -7
  127. package/assets/devtools/chunk.D7JLxcoJ.js.br +0 -0
  128. package/assets/devtools/chunk.D7JLxcoJ.js.gz +0 -0
  129. package/assets/devtools/chunk.D7e5mBY4.js +0 -1
  130. package/assets/devtools/chunk.D7e5mBY4.js.br +0 -0
  131. package/assets/devtools/chunk.D7e5mBY4.js.gz +0 -0
  132. package/assets/devtools/chunk.DClU9Z1_.js +0 -1
  133. package/assets/devtools/chunk.DClU9Z1_.js.br +0 -0
  134. package/assets/devtools/chunk.DClU9Z1_.js.gz +0 -0
  135. package/assets/devtools/chunk.DE_M8b3Z.js +0 -1
  136. package/assets/devtools/chunk.DE_M8b3Z.js.br +0 -0
  137. package/assets/devtools/chunk.DE_M8b3Z.js.gz +0 -0
  138. package/assets/devtools/chunk.DWASJDBE.js +0 -1
  139. package/assets/devtools/chunk.DWASJDBE.js.br +0 -0
  140. package/assets/devtools/chunk.DWASJDBE.js.gz +0 -0
  141. package/assets/devtools/chunk.DfzRLqwW.js +0 -1
  142. package/assets/devtools/chunk.DfzRLqwW.js.br +0 -0
  143. package/assets/devtools/chunk.DfzRLqwW.js.gz +0 -0
  144. package/assets/devtools/chunk.Dww1YQtc.js +0 -1
  145. package/assets/devtools/chunk.Dww1YQtc.js.br +0 -0
  146. package/assets/devtools/chunk.Dww1YQtc.js.gz +0 -0
  147. package/assets/devtools/chunk.HFLdduaf.js +0 -1
  148. package/assets/devtools/chunk.HFLdduaf.js.br +0 -0
  149. package/assets/devtools/chunk.HFLdduaf.js.gz +0 -0
  150. package/assets/devtools/chunk.J-htqECs.js +0 -1
  151. package/assets/devtools/chunk.J-htqECs.js.br +0 -2
  152. package/assets/devtools/chunk.J-htqECs.js.gz +0 -0
  153. package/assets/devtools/chunk.JjTGVewZ.js +0 -2
  154. package/assets/devtools/chunk.JjTGVewZ.js.br +0 -0
  155. package/assets/devtools/chunk.JjTGVewZ.js.gz +0 -0
  156. package/assets/devtools/chunk.OV_89czZ.js +0 -1
  157. package/assets/devtools/chunk.OV_89czZ.js.br +0 -0
  158. package/assets/devtools/chunk.OV_89czZ.js.gz +0 -0
  159. package/assets/devtools/chunk.YFkMUqFM.js +0 -1
  160. package/assets/devtools/chunk.YFkMUqFM.js.br +0 -0
  161. package/assets/devtools/chunk.YFkMUqFM.js.gz +0 -0
  162. package/assets/devtools/chunk._KdUFIrt.js +0 -1
  163. package/assets/devtools/chunk._KdUFIrt.js.br +0 -0
  164. package/assets/devtools/chunk._KdUFIrt.js.gz +0 -0
  165. package/assets/devtools/chunk.pjP6xqG8.js +0 -1
  166. package/assets/devtools/chunk.pjP6xqG8.js.br +0 -0
  167. package/assets/devtools/chunk.pjP6xqG8.js.gz +0 -0
  168. package/assets/devtools/chunk.uTFtY0ae.js +0 -2
  169. package/assets/devtools/chunk.uTFtY0ae.js.br +0 -0
  170. package/assets/devtools/chunk.uTFtY0ae.js.gz +0 -0
  171. package/assets/devtools/chunk.uyVen0u2.js +0 -1
  172. package/assets/devtools/chunk.uyVen0u2.js.br +0 -0
  173. package/assets/devtools/chunk.uyVen0u2.js.gz +0 -0
  174. package/assets/devtools/chunk.vHjNjQS8.js +0 -1
  175. package/assets/devtools/chunk.vHjNjQS8.js.br +0 -0
  176. package/assets/devtools/chunk.vHjNjQS8.js.gz +0 -0
  177. package/assets/devtools/db.html +0 -21
  178. package/assets/devtools/db.html.br +0 -0
  179. package/assets/devtools/db.html.gz +0 -0
  180. package/assets/devtools/entry.DhzNl8q_.js +0 -79
  181. package/assets/devtools/entry.DhzNl8q_.js.br +0 -0
  182. package/assets/devtools/entry.DhzNl8q_.js.gz +0 -0
  183. package/assets/devtools/env.html +0 -21
  184. package/assets/devtools/env.html.br +0 -0
  185. package/assets/devtools/env.html.gz +0 -0
  186. package/assets/devtools/graph.html +0 -22
  187. package/assets/devtools/graph.html.br +0 -0
  188. package/assets/devtools/graph.html.gz +0 -0
  189. package/assets/devtools/index.html +0 -21
  190. package/assets/devtools/index.html.br +0 -0
  191. package/assets/devtools/index.html.gz +0 -0
  192. package/assets/devtools/logs.html +0 -21
  193. package/assets/devtools/logs.html.br +0 -0
  194. package/assets/devtools/logs.html.gz +0 -0
  195. package/assets/devtools/queues.html +0 -21
  196. package/assets/devtools/queues.html.br +0 -0
  197. package/assets/devtools/queues.html.gz +0 -0
  198. package/assets/devtools/topics.html +0 -21
  199. package/assets/devtools/topics.html.br +0 -0
  200. package/assets/devtools/topics.html.gz +0 -0
  201. package/src/api/DevToolsProvider.ts +0 -157
  202. package/src/api/providers/DevToolsDatabaseProvider.ts +0 -27
  203. package/src/api/repositories/LogRepository.ts +0 -8
  204. package/src/api/schemas/DevCommandMetadata.ts +0 -9
  205. package/src/ui/components/DevAtomsViewer.tsx +0 -637
  206. package/src/ui/components/DevCacheInspector.tsx +0 -423
  207. package/src/ui/components/DevDashboard.tsx +0 -38
  208. package/src/ui/components/DevEnvExplorer.tsx +0 -462
  209. package/src/ui/components/DevLogViewer.tsx +0 -252
  210. package/src/ui/components/DevQueueMonitor.tsx +0 -51
  211. package/src/ui/components/DevTopicsViewer.tsx +0 -686
  212. package/src/ui/components/actions/ActionGroup.tsx +0 -37
  213. package/src/ui/components/actions/ActionItem.tsx +0 -138
  214. package/src/ui/components/actions/DevActionsExplorer.tsx +0 -132
  215. package/src/ui/components/actions/MethodBadge.tsx +0 -18
  216. package/src/ui/components/actions/SchemaViewer.tsx +0 -21
  217. package/src/ui/components/actions/TryItPanel.tsx +0 -140
  218. package/src/ui/components/actions/constants.ts +0 -7
  219. package/src/ui/components/actions/helpers.ts +0 -18
  220. package/src/ui/components/actions/index.ts +0 -8
  221. package/src/ui/components/db/ColumnBadge.tsx +0 -55
  222. package/src/ui/components/db/DevDbStudio.tsx +0 -485
  223. package/src/ui/components/db/constants.ts +0 -11
  224. package/src/ui/components/db/index.ts +0 -4
  225. package/src/ui/components/db/types.ts +0 -7
  226. package/src/ui/styles.css +0 -1
  227. /package/src/{api/schemas → schemas}/DevAtomMetadata.ts +0 -0
  228. /package/src/{api/schemas → schemas}/DevBucketMetadata.ts +0 -0
  229. /package/src/{api/schemas → schemas}/DevCacheMetadata.ts +0 -0
  230. /package/src/{api/schemas → schemas}/DevEnvMetadata.ts +0 -0
  231. /package/src/{api/schemas → schemas}/DevModuleMetadata.ts +0 -0
  232. /package/src/{api/schemas → schemas}/DevProviderMetadata.ts +0 -0
  233. /package/src/{api/schemas → schemas}/DevQueueMetadata.ts +0 -0
  234. /package/src/{api/schemas → schemas}/DevRealmMetadata.ts +0 -0
  235. /package/src/{api/schemas → schemas}/DevRouteMetadata.ts +0 -0
  236. /package/src/{api/schemas → schemas}/DevSchedulerMetadata.ts +0 -0
@@ -0,0 +1,661 @@
1
+ import { Flex, JsonViewer, ui } from "@alepha/ui";
2
+ import {
3
+ Badge,
4
+ CloseButton,
5
+ Code,
6
+ ScrollArea,
7
+ SegmentedControl,
8
+ Select,
9
+ Text,
10
+ TextInput,
11
+ } from "@mantine/core";
12
+ import { IconSearch } from "@tabler/icons-react";
13
+ import { useInject } from "alepha/react";
14
+ import { HttpClient } from "alepha/server";
15
+ import {
16
+ type MouseEvent as ReactMouseEvent,
17
+ useCallback,
18
+ useEffect,
19
+ useMemo,
20
+ useRef,
21
+ useState,
22
+ } from "react";
23
+
24
+ interface LogEntry {
25
+ level: string;
26
+ message: string;
27
+ module: string;
28
+ service: string;
29
+ context?: string;
30
+ data?: any;
31
+ timestamp: number;
32
+ stack?: string;
33
+ }
34
+
35
+ const LEVEL_COLORS: Record<string, string> = {
36
+ ERROR: "red",
37
+ WARN: "yellow",
38
+ INFO: "blue",
39
+ DEBUG: "gray",
40
+ TRACE: "dark",
41
+ };
42
+
43
+ const TYPE_COLORS: Record<string, string> = {
44
+ http: "teal",
45
+ db: "violet",
46
+ };
47
+
48
+ const detectEventType = (data: any): string | undefined => {
49
+ if (!data || typeof data !== "object") return undefined;
50
+ if (data.status && data.method && data.path && data.duration) return "http";
51
+ if (data.type === "db:query") return "db";
52
+ return undefined;
53
+ };
54
+
55
+ const TIME_RANGES = [
56
+ { value: "300000", label: "Last 5m" },
57
+ { value: "900000", label: "Last 15m" },
58
+ { value: "1800000", label: "Last 30m" },
59
+ { value: "3600000", label: "Last 1h" },
60
+ { value: "21600000", label: "Last 6h" },
61
+ { value: "86400000", label: "Last 24h" },
62
+ { value: "0", label: "All time" },
63
+ ];
64
+
65
+ const pad2 = (n: number) => (n < 10 ? `0${n}` : String(n));
66
+ const pad3 = (n: number) => (n < 10 ? `00${n}` : n < 100 ? `0${n}` : String(n));
67
+
68
+ const formatTime = (ts: number): string => {
69
+ const d = new Date(ts);
70
+ return `${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}.${pad3(d.getMilliseconds())}`;
71
+ };
72
+
73
+ const formatRelative = (ts: number): string => {
74
+ const diff = Date.now() - ts;
75
+ if (diff < 1000) return "just now";
76
+ if (diff < 60_000) return `${Math.floor(diff / 1000)}s ago`;
77
+ if (diff < 3_600_000) return `${Math.floor(diff / 60_000)}m ago`;
78
+ return `${Math.floor(diff / 3_600_000)}h ago`;
79
+ };
80
+
81
+ type ColumnDef = {
82
+ key: string;
83
+ label: string;
84
+ defaultWidth: number;
85
+ minWidth: number;
86
+ };
87
+
88
+ const COLUMNS: ColumnDef[] = [
89
+ { key: "time", label: "Time", defaultWidth: 105, minWidth: 60 },
90
+ { key: "level", label: "Level", defaultWidth: 58, minWidth: 40 },
91
+ { key: "type", label: "Type", defaultWidth: 60, minWidth: 40 },
92
+ { key: "context", label: "Context", defaultWidth: 80, minWidth: 40 },
93
+ { key: "module", label: "Module", defaultWidth: 100, minWidth: 50 },
94
+ ];
95
+
96
+ export const DevLogs = () => {
97
+ const http = useInject(HttpClient);
98
+ const [logs, setLogs] = useState<LogEntry[]>([]);
99
+ const [total, setTotal] = useState(0);
100
+ const [selectedIndex, setSelectedIndex] = useState<number | null>(null);
101
+
102
+ // Filters
103
+ const [level, setLevel] = useState("DEBUG");
104
+ const [typeFilter, setTypeFilter] = useState("");
105
+ const [moduleFilter, setModuleFilter] = useState("");
106
+ const [search, setSearch] = useState("");
107
+ const [timeRange, setTimeRange] = useState("0");
108
+
109
+ // Column widths
110
+ const [colWidths, setColWidths] = useState<Record<string, number>>(() => {
111
+ const widths: Record<string, number> = {};
112
+ for (const col of COLUMNS) {
113
+ widths[col.key] = col.defaultWidth;
114
+ }
115
+ return widths;
116
+ });
117
+
118
+ // Resize state
119
+ const resizeRef = useRef<{
120
+ colKey: string;
121
+ startX: number;
122
+ startWidth: number;
123
+ } | null>(null);
124
+
125
+ useEffect(() => {
126
+ const onMouseMove = (e: globalThis.MouseEvent) => {
127
+ if (!resizeRef.current) return;
128
+ const { colKey, startX, startWidth } = resizeRef.current;
129
+ const col = COLUMNS.find((c) => c.key === colKey);
130
+ const minW = col?.minWidth ?? 40;
131
+ const newWidth = Math.max(minW, startWidth + (e.clientX - startX));
132
+ setColWidths((prev) => ({ ...prev, [colKey]: newWidth }));
133
+ };
134
+ const onMouseUp = () => {
135
+ resizeRef.current = null;
136
+ document.body.style.cursor = "";
137
+ document.body.style.userSelect = "";
138
+ };
139
+ document.addEventListener("mousemove", onMouseMove);
140
+ document.addEventListener("mouseup", onMouseUp);
141
+ return () => {
142
+ document.removeEventListener("mousemove", onMouseMove);
143
+ document.removeEventListener("mouseup", onMouseUp);
144
+ };
145
+ }, []);
146
+
147
+ const startResize = (colKey: string, e: ReactMouseEvent<HTMLDivElement>) => {
148
+ e.preventDefault();
149
+ resizeRef.current = {
150
+ colKey,
151
+ startX: e.clientX,
152
+ startWidth: colWidths[colKey],
153
+ };
154
+ document.body.style.cursor = "col-resize";
155
+ document.body.style.userSelect = "none";
156
+ };
157
+
158
+ // Available modules (collected from logs)
159
+ const modules = useMemo(() => {
160
+ const set = new Set<string>();
161
+ for (const log of logs) {
162
+ if (log.module) set.add(log.module);
163
+ }
164
+ return Array.from(set).sort();
165
+ }, [logs]);
166
+
167
+ const fetchLogs = useCallback(async () => {
168
+ if (document.visibilityState !== "visible") return;
169
+ try {
170
+ const params = new URLSearchParams();
171
+ if (level) params.set("level", level);
172
+ if (typeFilter) params.set("type", typeFilter);
173
+ if (moduleFilter) params.set("module", moduleFilter);
174
+ if (search) params.set("search", search);
175
+ if (timeRange !== "0") {
176
+ params.set("since", String(Date.now() - Number(timeRange)));
177
+ }
178
+ params.set("limit", "500");
179
+
180
+ const res = await http.fetch(`/__devtools/api/logs?${params.toString()}`);
181
+ const newLogs = (res.data as any)?.logs ?? [];
182
+ setLogs(newLogs);
183
+ setTotal((res.data as any)?.total ?? 0);
184
+ // Reset selection when logs change to avoid stale index
185
+ setSelectedIndex(null);
186
+ } catch {
187
+ // silently fail
188
+ }
189
+ }, [http, level, typeFilter, moduleFilter, search, timeRange]);
190
+
191
+ useEffect(() => {
192
+ fetchLogs();
193
+ const interval = setInterval(fetchLogs, 10_000);
194
+ return () => clearInterval(interval);
195
+ }, [fetchLogs]);
196
+
197
+ const selectedLog = selectedIndex !== null ? logs[selectedIndex] : null;
198
+
199
+ return (
200
+ <Flex style={{ flex: 1, overflow: "hidden" }} direction="column">
201
+ {/* Filters bar */}
202
+ <Flex
203
+ px="md"
204
+ py="xs"
205
+ style={{
206
+ borderBottom: `1px solid ${ui.colors.border}`,
207
+ flexShrink: 0,
208
+ }}
209
+ >
210
+ <Flex gap="sm" align="center" wrap="wrap">
211
+ <SegmentedControl
212
+ size="xs"
213
+ value={level}
214
+ onChange={setLevel}
215
+ data={["TRACE", "DEBUG", "INFO", "WARN", "ERROR"]}
216
+ />
217
+ <Select
218
+ size="xs"
219
+ placeholder="Type"
220
+ clearable
221
+ value={typeFilter || null}
222
+ onChange={(v) => setTypeFilter(v ?? "")}
223
+ data={[
224
+ { value: "http", label: "HTTP" },
225
+ { value: "db", label: "DB Query" },
226
+ ]}
227
+ w={120}
228
+ />
229
+ <Select
230
+ size="xs"
231
+ placeholder="Module"
232
+ clearable
233
+ searchable
234
+ value={moduleFilter || null}
235
+ onChange={(v) => setModuleFilter(v ?? "")}
236
+ data={modules}
237
+ w={150}
238
+ />
239
+ <Select
240
+ size="xs"
241
+ value={timeRange}
242
+ onChange={(v) => setTimeRange(v ?? "0")}
243
+ data={TIME_RANGES}
244
+ w={120}
245
+ />
246
+ <TextInput
247
+ size="xs"
248
+ placeholder="Search..."
249
+ leftSection={<IconSearch size={14} />}
250
+ value={search}
251
+ onChange={(e) => setSearch(e.currentTarget.value)}
252
+ style={{ flex: 1, minWidth: 150, maxWidth: 300 }}
253
+ />
254
+ <Badge variant="light" color="gray" size="sm">
255
+ {total} total
256
+ </Badge>
257
+ </Flex>
258
+ </Flex>
259
+
260
+ {/* Main area: table + detail */}
261
+ <Flex style={{ flex: 1, overflow: "hidden" }}>
262
+ {/* Log table */}
263
+ <Flex direction="column" style={{ flex: 1, overflow: "hidden" }}>
264
+ {/* Table header */}
265
+ <Flex
266
+ style={{
267
+ borderBottom: `1px solid ${ui.colors.border}`,
268
+ flexShrink: 0,
269
+ }}
270
+ >
271
+ {COLUMNS.map((col) => (
272
+ <Flex
273
+ key={col.key}
274
+ align="center"
275
+ px="xs"
276
+ py={4}
277
+ style={{
278
+ width: colWidths[col.key],
279
+ minWidth: col.minWidth,
280
+ flexShrink: 0,
281
+ position: "relative",
282
+ userSelect: "none",
283
+ }}
284
+ >
285
+ <Text fz={10} c="dimmed" tt="uppercase" fw={600} lts={0.5}>
286
+ {col.label}
287
+ </Text>
288
+ <div
289
+ onMouseDown={(e) => startResize(col.key, e)}
290
+ style={{
291
+ position: "absolute",
292
+ right: 0,
293
+ top: 0,
294
+ bottom: 0,
295
+ width: 4,
296
+ cursor: "col-resize",
297
+ background: "transparent",
298
+ }}
299
+ onMouseEnter={(e) => {
300
+ (e.currentTarget as HTMLElement).style.background =
301
+ ui.colors.border;
302
+ }}
303
+ onMouseLeave={(e) => {
304
+ if (!resizeRef.current) {
305
+ (e.currentTarget as HTMLElement).style.background =
306
+ "transparent";
307
+ }
308
+ }}
309
+ />
310
+ </Flex>
311
+ ))}
312
+ <Flex align="center" px="xs" py={4} style={{ flex: 1 }}>
313
+ <Text fz={10} c="dimmed" tt="uppercase" fw={600} lts={0.5}>
314
+ Message
315
+ </Text>
316
+ </Flex>
317
+ </Flex>
318
+
319
+ {/* Table body */}
320
+ <ScrollArea style={{ flex: 1 }}>
321
+ {logs.length === 0 && (
322
+ <Flex align="center" justify="center" py="xl" c="dimmed">
323
+ <Text fz="sm">No logs match the current filters</Text>
324
+ </Flex>
325
+ )}
326
+ {logs.map((entry, i) => {
327
+ const isSelected = selectedIndex === i;
328
+ const eventType = detectEventType(entry.data);
329
+
330
+ return (
331
+ <Flex
332
+ key={`${entry.timestamp}-${i}`}
333
+ onClick={() => setSelectedIndex(isSelected ? null : i)}
334
+ style={{
335
+ borderBottom: `1px solid ${ui.colors.border}20`,
336
+ background: isSelected ? ui.colors.elevated : "transparent",
337
+ cursor: "pointer",
338
+ transition: "background 100ms",
339
+ }}
340
+ onMouseEnter={(e) => {
341
+ if (!isSelected) {
342
+ (e.currentTarget as HTMLElement).style.background =
343
+ `${ui.colors.elevated}80`;
344
+ }
345
+ }}
346
+ onMouseLeave={(e) => {
347
+ if (!isSelected) {
348
+ (e.currentTarget as HTMLElement).style.background =
349
+ "transparent";
350
+ }
351
+ }}
352
+ >
353
+ {/* Time */}
354
+ <Flex
355
+ align="center"
356
+ px="xs"
357
+ py={4}
358
+ style={{
359
+ width: colWidths.time,
360
+ flexShrink: 0,
361
+ overflow: "hidden",
362
+ }}
363
+ >
364
+ <Text fz={11} ff="monospace" c="dimmed" truncate>
365
+ {formatTime(entry.timestamp)}
366
+ </Text>
367
+ </Flex>
368
+
369
+ {/* Level */}
370
+ <Flex
371
+ align="center"
372
+ px="xs"
373
+ py={4}
374
+ style={{
375
+ width: colWidths.level,
376
+ flexShrink: 0,
377
+ overflow: "hidden",
378
+ }}
379
+ >
380
+ <Badge
381
+ size="xs"
382
+ variant="light"
383
+ color={LEVEL_COLORS[entry.level] ?? "gray"}
384
+ >
385
+ {entry.level}
386
+ </Badge>
387
+ </Flex>
388
+
389
+ {/* Type */}
390
+ <Flex
391
+ align="center"
392
+ px="xs"
393
+ py={4}
394
+ style={{
395
+ width: colWidths.type,
396
+ flexShrink: 0,
397
+ overflow: "hidden",
398
+ }}
399
+ >
400
+ {eventType && (
401
+ <Badge
402
+ size="xs"
403
+ variant="dot"
404
+ color={TYPE_COLORS[eventType] ?? "gray"}
405
+ >
406
+ {eventType === "http"
407
+ ? "HTTP"
408
+ : eventType === "db"
409
+ ? "DB"
410
+ : eventType}
411
+ </Badge>
412
+ )}
413
+ </Flex>
414
+
415
+ {/* Context */}
416
+ <Flex
417
+ align="center"
418
+ px="xs"
419
+ py={4}
420
+ style={{
421
+ width: colWidths.context,
422
+ flexShrink: 0,
423
+ overflow: "hidden",
424
+ }}
425
+ >
426
+ {entry.context && (
427
+ <Text fz={11} ff="monospace" c="dimmed" truncate>
428
+ {entry.context}
429
+ </Text>
430
+ )}
431
+ </Flex>
432
+
433
+ {/* Module */}
434
+ <Flex
435
+ align="center"
436
+ px="xs"
437
+ py={4}
438
+ style={{
439
+ width: colWidths.module,
440
+ flexShrink: 0,
441
+ overflow: "hidden",
442
+ }}
443
+ >
444
+ <Text fz={11} c="dimmed" truncate>
445
+ {entry.module}
446
+ </Text>
447
+ </Flex>
448
+
449
+ {/* Message */}
450
+ <Flex
451
+ align="center"
452
+ px="xs"
453
+ py={4}
454
+ style={{ flex: 1, overflow: "hidden" }}
455
+ >
456
+ <Text fz={11} ff="monospace" truncate>
457
+ {entry.message}
458
+ </Text>
459
+ </Flex>
460
+ </Flex>
461
+ );
462
+ })}
463
+ </ScrollArea>
464
+ </Flex>
465
+
466
+ {/* Detail panel */}
467
+ {selectedLog && (
468
+ <Flex
469
+ w={400}
470
+ style={{
471
+ borderLeft: `1px solid ${ui.colors.border}`,
472
+ flexShrink: 0,
473
+ overflow: "hidden",
474
+ display: "flex",
475
+ flexDirection: "column",
476
+ }}
477
+ >
478
+ <Flex
479
+ px="md"
480
+ py="xs"
481
+ align="center"
482
+ justify="space-between"
483
+ style={{
484
+ borderBottom: `1px solid ${ui.colors.border}`,
485
+ flexShrink: 0,
486
+ }}
487
+ >
488
+ <Text fz="xs" fw={600} tt="uppercase" c="dimmed" lts={0.5}>
489
+ Log Detail
490
+ </Text>
491
+ <CloseButton size="xs" onClick={() => setSelectedIndex(null)} />
492
+ </Flex>
493
+ <ScrollArea style={{ flex: 1 }} p="md">
494
+ <Flex direction="column" gap="md">
495
+ {/* Meta */}
496
+ <Flex gap="xs" wrap="wrap">
497
+ <Badge
498
+ size="sm"
499
+ variant="light"
500
+ color={LEVEL_COLORS[selectedLog.level] ?? "gray"}
501
+ >
502
+ {selectedLog.level}
503
+ </Badge>
504
+ {detectEventType(selectedLog.data) && (
505
+ <Badge
506
+ size="sm"
507
+ variant="dot"
508
+ color={
509
+ TYPE_COLORS[detectEventType(selectedLog.data)!] ??
510
+ "gray"
511
+ }
512
+ >
513
+ {detectEventType(selectedLog.data) === "http"
514
+ ? "HTTP"
515
+ : "DB"}
516
+ </Badge>
517
+ )}
518
+ {selectedLog.module && (
519
+ <Badge
520
+ size="sm"
521
+ variant="outline"
522
+ color="gray"
523
+ style={{ cursor: "pointer" }}
524
+ onClick={() => setModuleFilter(selectedLog.module)}
525
+ >
526
+ {selectedLog.module}
527
+ </Badge>
528
+ )}
529
+ </Flex>
530
+
531
+ {/* Timestamp */}
532
+ <Flex direction="column">
533
+ <Text
534
+ fz={10}
535
+ c="dimmed"
536
+ tt="uppercase"
537
+ fw={600}
538
+ lts={0.5}
539
+ mb={4}
540
+ >
541
+ Timestamp
542
+ </Text>
543
+ <Text fz="xs" ff="monospace">
544
+ {new Date(selectedLog.timestamp).toISOString()}
545
+ </Text>
546
+ <Text fz="xs" c="dimmed">
547
+ {formatRelative(selectedLog.timestamp)}
548
+ </Text>
549
+ </Flex>
550
+
551
+ {/* Context */}
552
+ {selectedLog.context && (
553
+ <Flex direction="column">
554
+ <Text
555
+ fz={10}
556
+ c="dimmed"
557
+ tt="uppercase"
558
+ fw={600}
559
+ lts={0.5}
560
+ mb={4}
561
+ >
562
+ Context
563
+ </Text>
564
+ <Text fz="xs" ff="monospace">
565
+ {selectedLog.context}
566
+ </Text>
567
+ </Flex>
568
+ )}
569
+
570
+ {/* Message */}
571
+ <Flex direction="column">
572
+ <Text
573
+ fz={10}
574
+ c="dimmed"
575
+ tt="uppercase"
576
+ fw={600}
577
+ lts={0.5}
578
+ mb={4}
579
+ >
580
+ Message
581
+ </Text>
582
+ <Text
583
+ fz="xs"
584
+ ff="monospace"
585
+ style={{ whiteSpace: "pre-wrap" }}
586
+ >
587
+ {selectedLog.message}
588
+ </Text>
589
+ </Flex>
590
+
591
+ {/* Service */}
592
+ {selectedLog.service && (
593
+ <Flex direction="column">
594
+ <Text
595
+ fz={10}
596
+ c="dimmed"
597
+ tt="uppercase"
598
+ fw={600}
599
+ lts={0.5}
600
+ mb={4}
601
+ >
602
+ Service
603
+ </Text>
604
+ <Text fz="xs" ff="monospace">
605
+ {selectedLog.service}
606
+ </Text>
607
+ </Flex>
608
+ )}
609
+
610
+ {/* Structured data */}
611
+ {selectedLog.data && (
612
+ <Flex direction="column">
613
+ <Text
614
+ fz={10}
615
+ c="dimmed"
616
+ tt="uppercase"
617
+ fw={600}
618
+ lts={0.5}
619
+ mb={4}
620
+ >
621
+ Data
622
+ </Text>
623
+ <JsonViewer data={selectedLog.data} maxDepth={4} />
624
+ </Flex>
625
+ )}
626
+
627
+ {/* Stack trace */}
628
+ {selectedLog.stack && (
629
+ <Flex direction="column">
630
+ <Text
631
+ fz={10}
632
+ c="dimmed"
633
+ tt="uppercase"
634
+ fw={600}
635
+ lts={0.5}
636
+ mb={4}
637
+ >
638
+ Stack Trace
639
+ </Text>
640
+ <Code
641
+ block
642
+ style={{
643
+ fontSize: 11,
644
+ maxHeight: 300,
645
+ overflow: "auto",
646
+ }}
647
+ >
648
+ {selectedLog.stack}
649
+ </Code>
650
+ </Flex>
651
+ )}
652
+ </Flex>
653
+ </ScrollArea>
654
+ </Flex>
655
+ )}
656
+ </Flex>
657
+ </Flex>
658
+ );
659
+ };
660
+
661
+ export default DevLogs;
@@ -0,0 +1 @@
1
+ export { DevLogs } from "./DevLogs.tsx";