@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
@@ -1,686 +0,0 @@
1
- import { ui } from "@alepha/ui";
2
- import { JsonViewer } from "@alepha/ui/json";
3
- import {
4
- ActionIcon,
5
- Badge,
6
- Box,
7
- Button,
8
- Flex,
9
- ScrollArea,
10
- Stack,
11
- Tabs,
12
- Text,
13
- Textarea,
14
- TextInput,
15
- Tooltip,
16
- } from "@mantine/core";
17
- import {
18
- IconChevronDown,
19
- IconChevronRight,
20
- IconClock,
21
- IconFolder,
22
- IconFolderOpen,
23
- IconMessageCircle,
24
- IconPlayerPlay,
25
- IconSearch,
26
- IconSend,
27
- IconServer,
28
- IconTrash,
29
- } from "@tabler/icons-react";
30
- import { useInject } from "alepha/react";
31
- import { HttpClient } from "alepha/server";
32
- import { useCallback, useEffect, useMemo, useState } from "react";
33
- import { devMetadataSchema } from "../../api/schemas/DevMetadata.ts";
34
- import type { DevTopicMetadata } from "../../api/schemas/DevTopicMetadata.ts";
35
-
36
- const PROVIDER_COLORS: Record<string, string> = {
37
- memory: "#69db7c",
38
- redis: "#ff6b6b",
39
- default: "#495057",
40
- };
41
-
42
- const getProviderColor = (provider: string): string => {
43
- return PROVIDER_COLORS[provider] ?? PROVIDER_COLORS.default;
44
- };
45
-
46
- // Build tree structure from topic names
47
- interface TopicTreeNode {
48
- name: string;
49
- fullPath: string;
50
- topic?: DevTopicMetadata;
51
- children: Map<string, TopicTreeNode>;
52
- }
53
-
54
- const buildTopicTree = (topics: DevTopicMetadata[]): TopicTreeNode => {
55
- const root: TopicTreeNode = { name: "", fullPath: "", children: new Map() };
56
-
57
- for (const topic of topics) {
58
- const parts = topic.name.split(/[./]/);
59
- let current = root;
60
-
61
- for (let i = 0; i < parts.length; i++) {
62
- const part = parts[i];
63
- const fullPath = parts.slice(0, i + 1).join("/");
64
-
65
- if (!current.children.has(part)) {
66
- current.children.set(part, {
67
- name: part,
68
- fullPath,
69
- children: new Map(),
70
- });
71
- }
72
- current = current.children.get(part)!;
73
- }
74
- current.topic = topic;
75
- }
76
-
77
- return root;
78
- };
79
-
80
- const TopicTreeItem = ({
81
- node,
82
- depth,
83
- selectedTopic,
84
- onSelectTopic,
85
- expandedNodes,
86
- onToggleExpand,
87
- }: {
88
- node: TopicTreeNode;
89
- depth: number;
90
- selectedTopic: DevTopicMetadata | null;
91
- onSelectTopic: (topic: DevTopicMetadata) => void;
92
- expandedNodes: Set<string>;
93
- onToggleExpand: (path: string) => void;
94
- }) => {
95
- const hasChildren = node.children.size > 0;
96
- const isExpanded = expandedNodes.has(node.fullPath);
97
- const isSelected = node.topic && selectedTopic?.name === node.topic.name;
98
- const providerColor = node.topic
99
- ? getProviderColor(node.topic.provider)
100
- : "#495057";
101
-
102
- const handleClick = () => {
103
- if (node.topic) {
104
- onSelectTopic(node.topic);
105
- }
106
- if (hasChildren) {
107
- onToggleExpand(node.fullPath);
108
- }
109
- };
110
-
111
- return (
112
- <Box>
113
- <Flex
114
- align="center"
115
- gap={4}
116
- px="sm"
117
- py={4}
118
- onClick={handleClick}
119
- style={{
120
- cursor: "pointer",
121
- paddingLeft: depth * 16 + 8,
122
- backgroundColor: isSelected ? `${providerColor}15` : undefined,
123
- borderLeft: isSelected
124
- ? `2px solid ${providerColor}`
125
- : "2px solid transparent",
126
- borderBottom: `1px solid ${ui.colors.border}`,
127
- }}
128
- >
129
- {hasChildren ? (
130
- isExpanded ? (
131
- <IconChevronDown size={12} opacity={0.5} />
132
- ) : (
133
- <IconChevronRight size={12} opacity={0.5} />
134
- )
135
- ) : (
136
- <Box w={12} />
137
- )}
138
- {hasChildren ? (
139
- isExpanded ? (
140
- <IconFolderOpen size={14} opacity={0.5} />
141
- ) : (
142
- <IconFolder size={14} opacity={0.5} />
143
- )
144
- ) : (
145
- <IconMessageCircle size={14} opacity={0.5} />
146
- )}
147
- <Text
148
- size="sm"
149
- style={{ flex: 1 }}
150
- truncate
151
- fw={node.topic ? 500 : 400}
152
- >
153
- {node.name}
154
- </Text>
155
- {node.topic && (
156
- <Badge
157
- size="xs"
158
- variant="light"
159
- color={node.topic.provider === "redis" ? "red" : "green"}
160
- >
161
- {node.topic.provider}
162
- </Badge>
163
- )}
164
- </Flex>
165
- {hasChildren && isExpanded && (
166
- <Box>
167
- {Array.from(node.children.values())
168
- .sort((a, b) => {
169
- // Folders first, then alphabetical
170
- const aHasChildren = a.children.size > 0;
171
- const bHasChildren = b.children.size > 0;
172
- if (aHasChildren && !bHasChildren) return -1;
173
- if (!aHasChildren && bHasChildren) return 1;
174
- return a.name.localeCompare(b.name);
175
- })
176
- .map((child) => (
177
- <TopicTreeItem
178
- key={child.fullPath}
179
- node={child}
180
- depth={depth + 1}
181
- selectedTopic={selectedTopic}
182
- onSelectTopic={onSelectTopic}
183
- expandedNodes={expandedNodes}
184
- onToggleExpand={onToggleExpand}
185
- />
186
- ))}
187
- </Box>
188
- )}
189
- </Box>
190
- );
191
- };
192
-
193
- const TopicSidebar = ({
194
- topics,
195
- selectedTopic,
196
- onSelectTopic,
197
- search,
198
- onSearchChange,
199
- }: {
200
- topics: DevTopicMetadata[];
201
- selectedTopic: DevTopicMetadata | null;
202
- onSelectTopic: (topic: DevTopicMetadata) => void;
203
- search: string;
204
- onSearchChange: (search: string) => void;
205
- }) => {
206
- const [expandedNodes, setExpandedNodes] = useState<Set<string>>(new Set());
207
-
208
- const tree = useMemo(() => buildTopicTree(topics), [topics]);
209
-
210
- // Auto-expand all nodes on first load
211
- useEffect(() => {
212
- const allPaths = new Set<string>();
213
- const collectPaths = (node: TopicTreeNode) => {
214
- if (node.fullPath) allPaths.add(node.fullPath);
215
- for (const child of node.children.values()) {
216
- collectPaths(child);
217
- }
218
- };
219
- collectPaths(tree);
220
- setExpandedNodes(allPaths);
221
- }, [tree]);
222
-
223
- const handleToggleExpand = useCallback((path: string) => {
224
- setExpandedNodes((prev) => {
225
- const next = new Set(prev);
226
- if (next.has(path)) {
227
- next.delete(path);
228
- } else {
229
- next.add(path);
230
- }
231
- return next;
232
- });
233
- }, []);
234
-
235
- return (
236
- <Stack gap={0} h="100%">
237
- <Box p="sm" style={{ borderBottom: `1px solid ${ui.colors.border}` }}>
238
- <TextInput
239
- placeholder="Search topics..."
240
- leftSection={<IconSearch size={14} />}
241
- size="xs"
242
- value={search}
243
- onChange={(e) => onSearchChange(e.target.value)}
244
- />
245
- </Box>
246
- <ScrollArea style={{ flex: 1 }}>
247
- {Array.from(tree.children.values())
248
- .sort((a, b) => a.name.localeCompare(b.name))
249
- .map((node) => (
250
- <TopicTreeItem
251
- key={node.fullPath}
252
- node={node}
253
- depth={0}
254
- selectedTopic={selectedTopic}
255
- onSelectTopic={onSelectTopic}
256
- expandedNodes={expandedNodes}
257
- onToggleExpand={handleToggleExpand}
258
- />
259
- ))}
260
- </ScrollArea>
261
- </Stack>
262
- );
263
- };
264
-
265
- interface FakeMessage {
266
- id: string;
267
- timestamp: Date;
268
- payload: any;
269
- }
270
-
271
- const SchemaTab = ({ topic }: { topic: DevTopicMetadata }) => {
272
- return (
273
- <ScrollArea h="100%" p="md">
274
- <Stack gap="lg">
275
- <Box>
276
- <Text size="sm" fw={600} mb="xs">
277
- Topic Info
278
- </Text>
279
- <Stack gap="xs">
280
- <Flex gap="sm">
281
- <Text size="sm" c="dimmed" w={100}>
282
- Name
283
- </Text>
284
- <Text size="sm" ff="monospace">
285
- {topic.name}
286
- </Text>
287
- </Flex>
288
- <Flex gap="sm">
289
- <Text size="sm" c="dimmed" w={100}>
290
- Provider
291
- </Text>
292
- <Badge
293
- size="xs"
294
- variant="light"
295
- color={topic.provider === "redis" ? "red" : "green"}
296
- >
297
- {topic.provider}
298
- </Badge>
299
- </Flex>
300
- {topic.description && (
301
- <Flex gap="sm">
302
- <Text size="sm" c="dimmed" w={100}>
303
- Description
304
- </Text>
305
- <Text size="sm">{topic.description}</Text>
306
- </Flex>
307
- )}
308
- </Stack>
309
- </Box>
310
-
311
- {topic.schema && (
312
- <Box>
313
- <Text size="sm" fw={600} mb="xs">
314
- Message Schema
315
- </Text>
316
- <Box
317
- p="sm"
318
- style={{
319
- border: `1px solid ${ui.colors.border}`,
320
- borderRadius: 8,
321
- backgroundColor: ui.colors.surface,
322
- }}
323
- >
324
- <JsonViewer data={topic.schema} maxDepth={5} size="xs" />
325
- </Box>
326
- </Box>
327
- )}
328
- </Stack>
329
- </ScrollArea>
330
- );
331
- };
332
-
333
- const MessagesTab = ({ topic }: { topic: DevTopicMetadata }) => {
334
- const [messages, setMessages] = useState<FakeMessage[]>([]);
335
- const [isListening, setIsListening] = useState(false);
336
-
337
- // Generate fake message for demo
338
- const addFakeMessage = useCallback(() => {
339
- const fakePayload = topic.schema
340
- ? {
341
- example: "data",
342
- topic: topic.name,
343
- timestamp: new Date().toISOString(),
344
- }
345
- : { message: `Hello from ${topic.name}` };
346
-
347
- setMessages((prev) => [
348
- {
349
- id: Math.random().toString(36).slice(2),
350
- timestamp: new Date(),
351
- payload: fakePayload,
352
- },
353
- ...prev.slice(0, 49), // Keep last 50 messages
354
- ]);
355
- }, [topic]);
356
-
357
- return (
358
- <Flex direction="column" h="100%">
359
- <Box p="sm" style={{ borderBottom: `1px solid ${ui.colors.border}` }}>
360
- <Flex gap="sm" align="center" justify="space-between">
361
- <Flex gap="sm" align="center">
362
- <Tooltip
363
- label={
364
- isListening
365
- ? "Stop listening (coming soon)"
366
- : "Start listening (coming soon)"
367
- }
368
- >
369
- <Button
370
- size="xs"
371
- variant="light"
372
- color={isListening ? "red" : "green"}
373
- leftSection={<IconPlayerPlay size={14} />}
374
- onClick={() => setIsListening(!isListening)}
375
- disabled
376
- >
377
- {isListening ? "Stop" : "Listen"}
378
- </Button>
379
- </Tooltip>
380
- <Text size="xs" c="dimmed">
381
- {messages.length} messages
382
- </Text>
383
- </Flex>
384
- <Flex gap="sm">
385
- <Tooltip label="Add fake message (for demo)">
386
- <ActionIcon size="sm" variant="light" onClick={addFakeMessage}>
387
- <IconSend size={14} />
388
- </ActionIcon>
389
- </Tooltip>
390
- <Tooltip label="Clear messages">
391
- <ActionIcon
392
- size="sm"
393
- variant="light"
394
- color="gray"
395
- onClick={() => setMessages([])}
396
- >
397
- <IconTrash size={14} />
398
- </ActionIcon>
399
- </Tooltip>
400
- </Flex>
401
- </Flex>
402
- </Box>
403
-
404
- {messages.length === 0 ? (
405
- <Flex align="center" justify="center" style={{ flex: 1 }} c="dimmed">
406
- <Stack align="center" gap="xs">
407
- <IconMessageCircle size={48} opacity={0.3} />
408
- <Text size="sm">No messages yet</Text>
409
- <Text size="xs" c="dimmed">
410
- Click the send icon to add a fake message for demo
411
- </Text>
412
- </Stack>
413
- </Flex>
414
- ) : (
415
- <ScrollArea style={{ flex: 1 }}>
416
- <Stack gap={0}>
417
- {messages.map((msg) => (
418
- <Box
419
- key={msg.id}
420
- p="sm"
421
- style={{
422
- borderBottom: `1px solid ${ui.colors.border}`,
423
- }}
424
- >
425
- <Flex align="center" gap="xs" mb="xs">
426
- <IconClock size={12} opacity={0.5} />
427
- <Text size="xs" c="dimmed">
428
- {msg.timestamp.toLocaleTimeString()}
429
- </Text>
430
- </Flex>
431
- <Box
432
- p="xs"
433
- style={{
434
- backgroundColor: ui.colors.surface,
435
- borderRadius: 4,
436
- }}
437
- >
438
- <JsonViewer data={msg.payload} maxDepth={3} size="xs" />
439
- </Box>
440
- </Box>
441
- ))}
442
- </Stack>
443
- </ScrollArea>
444
- )}
445
- </Flex>
446
- );
447
- };
448
-
449
- const PublishTab = ({ topic }: { topic: DevTopicMetadata }) => {
450
- const [payload, setPayload] = useState(
451
- JSON.stringify(
452
- { message: "Hello", timestamp: new Date().toISOString() },
453
- null,
454
- 2,
455
- ),
456
- );
457
- const [error, setError] = useState<string | null>(null);
458
-
459
- const handlePublish = () => {
460
- try {
461
- JSON.parse(payload);
462
- setError(null);
463
- // Fake publish - would call API in real implementation
464
- alert(`Message would be published to "${topic.name}" (coming soon)`);
465
- } catch {
466
- setError("Invalid JSON");
467
- }
468
- };
469
-
470
- return (
471
- <ScrollArea h="100%" p="md">
472
- <Stack gap="md">
473
- <Box>
474
- <Text size="sm" fw={600} mb="xs">
475
- Publish Message
476
- </Text>
477
- <Text size="xs" c="dimmed" mb="sm">
478
- Send a test message to this topic (coming soon)
479
- </Text>
480
- </Box>
481
-
482
- <Box>
483
- <Text size="sm" c="dimmed" mb="xs">
484
- Payload (JSON)
485
- </Text>
486
- <Textarea
487
- value={payload}
488
- onChange={(e) => setPayload(e.target.value)}
489
- minRows={8}
490
- maxRows={15}
491
- autosize
492
- ff="monospace"
493
- size="xs"
494
- error={error}
495
- styles={{
496
- input: {
497
- backgroundColor: ui.colors.surface,
498
- },
499
- }}
500
- />
501
- </Box>
502
-
503
- <Tooltip label="Publish message (coming soon)">
504
- <Button
505
- size="sm"
506
- variant="light"
507
- leftSection={<IconSend size={16} />}
508
- onClick={handlePublish}
509
- disabled
510
- >
511
- Publish
512
- </Button>
513
- </Tooltip>
514
-
515
- {topic.schema && (
516
- <Box>
517
- <Text size="sm" c="dimmed" mb="xs">
518
- Expected Schema
519
- </Text>
520
- <Box
521
- p="sm"
522
- style={{
523
- border: `1px solid ${ui.colors.border}`,
524
- borderRadius: 8,
525
- backgroundColor: ui.colors.surface,
526
- }}
527
- >
528
- <JsonViewer data={topic.schema} maxDepth={3} size="xs" />
529
- </Box>
530
- </Box>
531
- )}
532
- </Stack>
533
- </ScrollArea>
534
- );
535
- };
536
-
537
- const TopicPanel = ({ topic }: { topic: DevTopicMetadata }) => {
538
- const providerColor = getProviderColor(topic.provider);
539
-
540
- return (
541
- <Flex direction="column" h="100%">
542
- {/* Header */}
543
- <Box
544
- px="md"
545
- py="sm"
546
- style={{
547
- borderBottom: `1px solid ${ui.colors.border}`,
548
- backgroundColor: `${providerColor}08`,
549
- }}
550
- >
551
- <Flex align="center" gap="sm">
552
- <IconMessageCircle size={18} opacity={0.7} />
553
- <Text size="md" fw={600} ff="monospace">
554
- {topic.name}
555
- </Text>
556
- <Badge
557
- size="xs"
558
- variant="light"
559
- color={topic.provider === "redis" ? "red" : "green"}
560
- >
561
- {topic.provider}
562
- </Badge>
563
- </Flex>
564
- {topic.description && (
565
- <Text size="xs" c="dimmed" mt={4}>
566
- {topic.description}
567
- </Text>
568
- )}
569
- </Box>
570
-
571
- {/* Tabs */}
572
- <Tabs
573
- defaultValue="schema"
574
- style={{ flex: 1, display: "flex", flexDirection: "column" }}
575
- >
576
- <Tabs.List px="md">
577
- <Tabs.Tab value="schema">Schema</Tabs.Tab>
578
- <Tabs.Tab value="messages">Messages</Tabs.Tab>
579
- <Tabs.Tab value="publish">Publish</Tabs.Tab>
580
- </Tabs.List>
581
-
582
- <Tabs.Panel value="schema" style={{ flex: 1, overflow: "hidden" }}>
583
- <SchemaTab topic={topic} />
584
- </Tabs.Panel>
585
-
586
- <Tabs.Panel value="messages" style={{ flex: 1, overflow: "hidden" }}>
587
- <MessagesTab topic={topic} />
588
- </Tabs.Panel>
589
-
590
- <Tabs.Panel value="publish" style={{ flex: 1, overflow: "hidden" }}>
591
- <PublishTab topic={topic} />
592
- </Tabs.Panel>
593
- </Tabs>
594
- </Flex>
595
- );
596
- };
597
-
598
- const EmptyState = () => (
599
- <Flex align="center" justify="center" h="100%" c="dimmed">
600
- <Stack align="center" gap="xs">
601
- <IconMessageCircle size={48} opacity={0.3} />
602
- <Text size="sm">Select a topic to view details</Text>
603
- </Stack>
604
- </Flex>
605
- );
606
-
607
- const NoTopicsState = () => (
608
- <Flex align="center" justify="center" h="100%" c="dimmed">
609
- <Stack align="center" gap="xs">
610
- <IconServer size={48} opacity={0.3} />
611
- <Text>No topics found</Text>
612
- <Text size="sm" c="dimmed">
613
- Add topics using $topic primitive to see them here
614
- </Text>
615
- </Stack>
616
- </Flex>
617
- );
618
-
619
- export const DevTopicsViewer = () => {
620
- const http = useInject(HttpClient);
621
- const [topics, setTopics] = useState<DevTopicMetadata[]>([]);
622
- const [loading, setLoading] = useState(true);
623
- const [selectedTopic, setSelectedTopic] = useState<DevTopicMetadata | null>(
624
- null,
625
- );
626
- const [search, setSearch] = useState("");
627
-
628
- // Fetch topics
629
- useEffect(() => {
630
- http
631
- .fetch("/devtools/api/metadata", {
632
- schema: { response: devMetadataSchema },
633
- })
634
- .then((res) => {
635
- setTopics(res.data.topics);
636
- setLoading(false);
637
- });
638
- }, []);
639
-
640
- // Filter topics by search
641
- const filteredTopics = useMemo(() => {
642
- if (!search) return topics;
643
- const searchLower = search.toLowerCase();
644
- return topics.filter((t) => t.name.toLowerCase().includes(searchLower));
645
- }, [topics, search]);
646
-
647
- if (loading) {
648
- return (
649
- <Flex align="center" justify="center" h="100%">
650
- <Text c="dimmed">Loading...</Text>
651
- </Flex>
652
- );
653
- }
654
-
655
- if (topics.length === 0) {
656
- return <NoTopicsState />;
657
- }
658
-
659
- return (
660
- <Flex h="100%" style={{ overflow: "hidden" }}>
661
- {/* Sidebar */}
662
- <Box
663
- w={280}
664
- style={{
665
- borderRight: `1px solid ${ui.colors.border}`,
666
- backgroundColor: ui.colors.surface,
667
- }}
668
- >
669
- <TopicSidebar
670
- topics={filteredTopics}
671
- selectedTopic={selectedTopic}
672
- onSelectTopic={setSelectedTopic}
673
- search={search}
674
- onSearchChange={setSearch}
675
- />
676
- </Box>
677
-
678
- {/* Main content */}
679
- <Box style={{ flex: 1, overflow: "hidden" }}>
680
- {selectedTopic ? <TopicPanel topic={selectedTopic} /> : <EmptyState />}
681
- </Box>
682
- </Flex>
683
- );
684
- };
685
-
686
- export default DevTopicsViewer;