@alepha/devtools 0.13.5 → 0.13.7
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/{index.d.mts → index.d.ts} +250 -32
- package/dist/{index.mjs → index.js} +254 -22
- package/dist/index.js.map +1 -0
- package/package.json +12 -6
- package/src/{DevToolsProvider.ts → api/DevToolsProvider.ts} +29 -1
- package/src/{providers → api/providers}/DevToolsMetadataProvider.ts +210 -2
- package/src/api/schemas/DevAtomMetadata.ts +26 -0
- package/src/api/schemas/DevCommandMetadata.ts +9 -0
- package/src/api/schemas/DevEntityMetadata.ts +57 -0
- package/src/api/schemas/DevEnvMetadata.ts +22 -0
- package/src/{schemas → api/schemas}/DevMetadata.ts +10 -1
- package/src/api/schemas/DevRouteMetadata.ts +8 -0
- package/src/index.ts +23 -16
- package/src/ui/AppRouter.tsx +85 -2
- package/src/ui/components/DevAtomsViewer.tsx +636 -0
- package/src/ui/components/DevCacheInspector.tsx +423 -0
- package/src/ui/components/DevDashboard.tsx +188 -0
- package/src/ui/components/DevEnvExplorer.tsx +462 -0
- package/src/ui/components/DevLayout.tsx +65 -4
- package/src/ui/components/DevLogViewer.tsx +161 -163
- package/src/ui/components/DevQueueMonitor.tsx +51 -0
- package/src/ui/components/DevTopicsViewer.tsx +690 -0
- package/src/ui/components/actions/ActionGroup.tsx +37 -0
- package/src/ui/components/actions/ActionItem.tsx +138 -0
- package/src/ui/components/actions/DevActionsExplorer.tsx +132 -0
- package/src/ui/components/actions/MethodBadge.tsx +18 -0
- package/src/ui/components/actions/SchemaViewer.tsx +21 -0
- package/src/ui/components/actions/TryItPanel.tsx +140 -0
- package/src/ui/components/actions/constants.ts +7 -0
- package/src/ui/components/actions/helpers.ts +18 -0
- package/src/ui/components/actions/index.ts +8 -0
- package/src/ui/components/db/ColumnBadge.tsx +55 -0
- package/src/ui/components/db/DevDbStudio.tsx +485 -0
- package/src/ui/components/db/constants.ts +11 -0
- package/src/ui/components/db/index.ts +4 -0
- package/src/ui/components/db/types.ts +7 -0
- package/src/ui/components/graph/DevDependencyGraph.tsx +358 -0
- package/src/ui/components/graph/GraphControls.tsx +162 -0
- package/src/ui/components/graph/NodeDetails.tsx +181 -0
- package/src/ui/components/graph/ProviderNode.tsx +97 -0
- package/src/ui/components/graph/constants.ts +35 -0
- package/src/ui/components/graph/helpers.ts +443 -0
- package/src/ui/components/graph/index.ts +7 -0
- package/src/ui/components/graph/types.ts +28 -0
- package/src/ui/styles.css +0 -6
- package/src/ui/resources/wotfardregular/stylesheet.css +0 -12
- package/src/ui/resources/wotfardregular/wotfard-regular-webfont.eot +0 -0
- package/src/ui/resources/wotfardregular/wotfard-regular-webfont.ttf +0 -0
- package/src/ui/resources/wotfardregular/wotfard-regular-webfont.woff2 +0 -0
- /package/src/{entities → api/entities}/logs.ts +0 -0
- /package/src/{providers → api/providers}/DevToolsDatabaseProvider.ts +0 -0
- /package/src/{repositories → api/repositories}/LogRepository.ts +0 -0
- /package/src/{schemas → api/schemas}/DevActionMetadata.ts +0 -0
- /package/src/{schemas → api/schemas}/DevBucketMetadata.ts +0 -0
- /package/src/{schemas → api/schemas}/DevCacheMetadata.ts +0 -0
- /package/src/{schemas → api/schemas}/DevModuleMetadata.ts +0 -0
- /package/src/{schemas → api/schemas}/DevPageMetadata.ts +0 -0
- /package/src/{schemas → api/schemas}/DevProviderMetadata.ts +0 -0
- /package/src/{schemas → api/schemas}/DevQueueMetadata.ts +0 -0
- /package/src/{schemas → api/schemas}/DevRealmMetadata.ts +0 -0
- /package/src/{schemas → api/schemas}/DevSchedulerMetadata.ts +0 -0
- /package/src/{schemas → api/schemas}/DevTopicMetadata.ts +0 -0
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
import { useInject } from "@alepha/react";
|
|
2
|
+
import { ui } from "@alepha/ui";
|
|
3
|
+
import {
|
|
4
|
+
Badge,
|
|
5
|
+
Box,
|
|
6
|
+
Flex,
|
|
7
|
+
ScrollArea,
|
|
8
|
+
Stack,
|
|
9
|
+
Table,
|
|
10
|
+
Tabs,
|
|
11
|
+
Text,
|
|
12
|
+
TextInput,
|
|
13
|
+
} from "@mantine/core";
|
|
14
|
+
import { IconDatabase, IconSearch, IconTable } from "@tabler/icons-react";
|
|
15
|
+
import { HttpClient } from "alepha/server";
|
|
16
|
+
import { useEffect, useMemo, useState } from "react";
|
|
17
|
+
import { devMetadataSchema } from "../../../api/schemas/DevMetadata.ts";
|
|
18
|
+
import { ColumnBadge } from "./ColumnBadge.tsx";
|
|
19
|
+
import { getProviderColor } from "./constants.ts";
|
|
20
|
+
import type { DevEntityMetadata } from "./types.ts";
|
|
21
|
+
|
|
22
|
+
const EntitySidebar = ({
|
|
23
|
+
entities,
|
|
24
|
+
selectedEntity,
|
|
25
|
+
onSelectEntity,
|
|
26
|
+
search,
|
|
27
|
+
onSearchChange,
|
|
28
|
+
}: {
|
|
29
|
+
entities: DevEntityMetadata[];
|
|
30
|
+
selectedEntity: DevEntityMetadata | null;
|
|
31
|
+
onSelectEntity: (entity: DevEntityMetadata) => void;
|
|
32
|
+
search: string;
|
|
33
|
+
onSearchChange: (search: string) => void;
|
|
34
|
+
}) => {
|
|
35
|
+
// Group entities by provider
|
|
36
|
+
const grouped = useMemo(() => {
|
|
37
|
+
const groups: Record<string, DevEntityMetadata[]> = {};
|
|
38
|
+
for (const entity of entities) {
|
|
39
|
+
const provider = entity.provider.replace("Provider", "");
|
|
40
|
+
if (!groups[provider]) groups[provider] = [];
|
|
41
|
+
groups[provider].push(entity);
|
|
42
|
+
}
|
|
43
|
+
return groups;
|
|
44
|
+
}, [entities]);
|
|
45
|
+
|
|
46
|
+
const providers = Object.keys(grouped).sort();
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<Stack gap={0} h="100%">
|
|
50
|
+
<Box p="sm" style={{ borderBottom: `1px solid ${ui.colors.border}` }}>
|
|
51
|
+
<TextInput
|
|
52
|
+
placeholder="Search tables..."
|
|
53
|
+
leftSection={<IconSearch size={14} />}
|
|
54
|
+
size="xs"
|
|
55
|
+
value={search}
|
|
56
|
+
onChange={(e) => onSearchChange(e.target.value)}
|
|
57
|
+
/>
|
|
58
|
+
</Box>
|
|
59
|
+
<ScrollArea style={{ flex: 1 }}>
|
|
60
|
+
{providers.map((provider) => (
|
|
61
|
+
<Box key={provider}>
|
|
62
|
+
<Text
|
|
63
|
+
size="xs"
|
|
64
|
+
fw={600}
|
|
65
|
+
c="dimmed"
|
|
66
|
+
px="sm"
|
|
67
|
+
py="xs"
|
|
68
|
+
style={{
|
|
69
|
+
backgroundColor: ui.colors.background,
|
|
70
|
+
borderBottom: `1px solid ${ui.colors.border}`,
|
|
71
|
+
textTransform: "uppercase",
|
|
72
|
+
letterSpacing: "0.05em",
|
|
73
|
+
}}
|
|
74
|
+
>
|
|
75
|
+
{provider}
|
|
76
|
+
</Text>
|
|
77
|
+
{grouped[provider].map((entity) => {
|
|
78
|
+
const isSelected = selectedEntity?.name === entity.name;
|
|
79
|
+
const providerColor = getProviderColor(entity.provider);
|
|
80
|
+
return (
|
|
81
|
+
<Flex
|
|
82
|
+
key={entity.name}
|
|
83
|
+
align="center"
|
|
84
|
+
gap="xs"
|
|
85
|
+
px="sm"
|
|
86
|
+
py={6}
|
|
87
|
+
onClick={() => onSelectEntity(entity)}
|
|
88
|
+
style={{
|
|
89
|
+
cursor: "pointer",
|
|
90
|
+
backgroundColor: isSelected
|
|
91
|
+
? `${providerColor}15`
|
|
92
|
+
: undefined,
|
|
93
|
+
borderLeft: isSelected
|
|
94
|
+
? `2px solid ${providerColor}`
|
|
95
|
+
: "2px solid transparent",
|
|
96
|
+
borderBottom: `1px solid ${ui.colors.border}`,
|
|
97
|
+
}}
|
|
98
|
+
>
|
|
99
|
+
<IconTable size={14} opacity={0.5} />
|
|
100
|
+
<Text size="sm" style={{ flex: 1 }} truncate>
|
|
101
|
+
{entity.name}
|
|
102
|
+
</Text>
|
|
103
|
+
<Text size="xs" c="dimmed">
|
|
104
|
+
{entity.columns.length}
|
|
105
|
+
</Text>
|
|
106
|
+
</Flex>
|
|
107
|
+
);
|
|
108
|
+
})}
|
|
109
|
+
</Box>
|
|
110
|
+
))}
|
|
111
|
+
</ScrollArea>
|
|
112
|
+
</Stack>
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const StructureTab = ({ entity }: { entity: DevEntityMetadata }) => {
|
|
117
|
+
return (
|
|
118
|
+
<ScrollArea h="100%" p="md">
|
|
119
|
+
<Stack gap="lg">
|
|
120
|
+
{/* Columns */}
|
|
121
|
+
<Box>
|
|
122
|
+
<Text size="sm" fw={600} mb="xs">
|
|
123
|
+
Columns
|
|
124
|
+
</Text>
|
|
125
|
+
<Table striped highlightOnHover withTableBorder>
|
|
126
|
+
<Table.Thead>
|
|
127
|
+
<Table.Tr>
|
|
128
|
+
<Table.Th>Name</Table.Th>
|
|
129
|
+
<Table.Th>Type</Table.Th>
|
|
130
|
+
<Table.Th>Attributes</Table.Th>
|
|
131
|
+
</Table.Tr>
|
|
132
|
+
</Table.Thead>
|
|
133
|
+
<Table.Tbody>
|
|
134
|
+
{entity.columns.map((col) => (
|
|
135
|
+
<Table.Tr key={col.name}>
|
|
136
|
+
<Table.Td>
|
|
137
|
+
<Text
|
|
138
|
+
size="sm"
|
|
139
|
+
ff="monospace"
|
|
140
|
+
fw={col.primaryKey ? 600 : 400}
|
|
141
|
+
>
|
|
142
|
+
{col.name}
|
|
143
|
+
</Text>
|
|
144
|
+
</Table.Td>
|
|
145
|
+
<Table.Td>
|
|
146
|
+
<Text size="sm" ff="monospace" c="dimmed">
|
|
147
|
+
{col.type}
|
|
148
|
+
</Text>
|
|
149
|
+
</Table.Td>
|
|
150
|
+
<Table.Td>
|
|
151
|
+
<Flex gap={4} wrap="wrap">
|
|
152
|
+
<ColumnBadge column={col} />
|
|
153
|
+
</Flex>
|
|
154
|
+
</Table.Td>
|
|
155
|
+
</Table.Tr>
|
|
156
|
+
))}
|
|
157
|
+
</Table.Tbody>
|
|
158
|
+
</Table>
|
|
159
|
+
</Box>
|
|
160
|
+
|
|
161
|
+
{/* Indexes */}
|
|
162
|
+
{entity.indexes.length > 0 && (
|
|
163
|
+
<Box>
|
|
164
|
+
<Text size="sm" fw={600} mb="xs">
|
|
165
|
+
Indexes
|
|
166
|
+
</Text>
|
|
167
|
+
<Table striped highlightOnHover withTableBorder>
|
|
168
|
+
<Table.Thead>
|
|
169
|
+
<Table.Tr>
|
|
170
|
+
<Table.Th>Name</Table.Th>
|
|
171
|
+
<Table.Th>Columns</Table.Th>
|
|
172
|
+
<Table.Th>Unique</Table.Th>
|
|
173
|
+
</Table.Tr>
|
|
174
|
+
</Table.Thead>
|
|
175
|
+
<Table.Tbody>
|
|
176
|
+
{entity.indexes.map((idx, i) => (
|
|
177
|
+
<Table.Tr key={idx.name || i}>
|
|
178
|
+
<Table.Td>
|
|
179
|
+
<Text size="sm" ff="monospace">
|
|
180
|
+
{idx.name || "-"}
|
|
181
|
+
</Text>
|
|
182
|
+
</Table.Td>
|
|
183
|
+
<Table.Td>
|
|
184
|
+
<Flex gap={4}>
|
|
185
|
+
{idx.columns.map((c) => (
|
|
186
|
+
<Badge key={c} size="xs" variant="light">
|
|
187
|
+
{c}
|
|
188
|
+
</Badge>
|
|
189
|
+
))}
|
|
190
|
+
</Flex>
|
|
191
|
+
</Table.Td>
|
|
192
|
+
<Table.Td>
|
|
193
|
+
{idx.unique && (
|
|
194
|
+
<Badge size="xs" color="yellow">
|
|
195
|
+
UNIQUE
|
|
196
|
+
</Badge>
|
|
197
|
+
)}
|
|
198
|
+
</Table.Td>
|
|
199
|
+
</Table.Tr>
|
|
200
|
+
))}
|
|
201
|
+
</Table.Tbody>
|
|
202
|
+
</Table>
|
|
203
|
+
</Box>
|
|
204
|
+
)}
|
|
205
|
+
|
|
206
|
+
{/* Foreign Keys */}
|
|
207
|
+
{entity.foreignKeys.length > 0 && (
|
|
208
|
+
<Box>
|
|
209
|
+
<Text size="sm" fw={600} mb="xs">
|
|
210
|
+
Foreign Keys
|
|
211
|
+
</Text>
|
|
212
|
+
<Table striped highlightOnHover withTableBorder>
|
|
213
|
+
<Table.Thead>
|
|
214
|
+
<Table.Tr>
|
|
215
|
+
<Table.Th>Name</Table.Th>
|
|
216
|
+
<Table.Th>Column</Table.Th>
|
|
217
|
+
<Table.Th>References</Table.Th>
|
|
218
|
+
</Table.Tr>
|
|
219
|
+
</Table.Thead>
|
|
220
|
+
<Table.Tbody>
|
|
221
|
+
{entity.foreignKeys.map((fk, i) => (
|
|
222
|
+
<Table.Tr key={fk.name || i}>
|
|
223
|
+
<Table.Td>
|
|
224
|
+
<Text size="sm" ff="monospace">
|
|
225
|
+
{fk.name || "-"}
|
|
226
|
+
</Text>
|
|
227
|
+
</Table.Td>
|
|
228
|
+
<Table.Td>
|
|
229
|
+
<Text size="sm" ff="monospace">
|
|
230
|
+
{fk.columns.join(", ")}
|
|
231
|
+
</Text>
|
|
232
|
+
</Table.Td>
|
|
233
|
+
<Table.Td>
|
|
234
|
+
<Text size="sm" ff="monospace" c="pink">
|
|
235
|
+
{fk.foreignEntity}.{fk.foreignColumns.join(", ")}
|
|
236
|
+
</Text>
|
|
237
|
+
</Table.Td>
|
|
238
|
+
</Table.Tr>
|
|
239
|
+
))}
|
|
240
|
+
</Table.Tbody>
|
|
241
|
+
</Table>
|
|
242
|
+
</Box>
|
|
243
|
+
)}
|
|
244
|
+
|
|
245
|
+
{/* Alepha Column References */}
|
|
246
|
+
{entity.columns.some((c) => c.ref) && (
|
|
247
|
+
<Box>
|
|
248
|
+
<Text size="sm" fw={600} mb="xs">
|
|
249
|
+
Column References
|
|
250
|
+
</Text>
|
|
251
|
+
<Table striped highlightOnHover withTableBorder>
|
|
252
|
+
<Table.Thead>
|
|
253
|
+
<Table.Tr>
|
|
254
|
+
<Table.Th>Column</Table.Th>
|
|
255
|
+
<Table.Th>References</Table.Th>
|
|
256
|
+
</Table.Tr>
|
|
257
|
+
</Table.Thead>
|
|
258
|
+
<Table.Tbody>
|
|
259
|
+
{entity.columns
|
|
260
|
+
.filter((c) => c.ref)
|
|
261
|
+
.map((col) => (
|
|
262
|
+
<Table.Tr key={col.name}>
|
|
263
|
+
<Table.Td>
|
|
264
|
+
<Text size="sm" ff="monospace">
|
|
265
|
+
{col.name}
|
|
266
|
+
</Text>
|
|
267
|
+
</Table.Td>
|
|
268
|
+
<Table.Td>
|
|
269
|
+
<Text size="sm" ff="monospace" c="pink">
|
|
270
|
+
{col.ref?.entity}.{col.ref?.column}
|
|
271
|
+
</Text>
|
|
272
|
+
</Table.Td>
|
|
273
|
+
</Table.Tr>
|
|
274
|
+
))}
|
|
275
|
+
</Table.Tbody>
|
|
276
|
+
</Table>
|
|
277
|
+
</Box>
|
|
278
|
+
)}
|
|
279
|
+
|
|
280
|
+
{/* Constraints */}
|
|
281
|
+
{entity.constraints.length > 0 && (
|
|
282
|
+
<Box>
|
|
283
|
+
<Text size="sm" fw={600} mb="xs">
|
|
284
|
+
Constraints
|
|
285
|
+
</Text>
|
|
286
|
+
<Table striped highlightOnHover withTableBorder>
|
|
287
|
+
<Table.Thead>
|
|
288
|
+
<Table.Tr>
|
|
289
|
+
<Table.Th>Name</Table.Th>
|
|
290
|
+
<Table.Th>Columns</Table.Th>
|
|
291
|
+
<Table.Th>Type</Table.Th>
|
|
292
|
+
</Table.Tr>
|
|
293
|
+
</Table.Thead>
|
|
294
|
+
<Table.Tbody>
|
|
295
|
+
{entity.constraints.map((c, i) => (
|
|
296
|
+
<Table.Tr key={c.name || i}>
|
|
297
|
+
<Table.Td>
|
|
298
|
+
<Text size="sm" ff="monospace">
|
|
299
|
+
{c.name || "-"}
|
|
300
|
+
</Text>
|
|
301
|
+
</Table.Td>
|
|
302
|
+
<Table.Td>
|
|
303
|
+
<Flex gap={4}>
|
|
304
|
+
{c.columns.map((col) => (
|
|
305
|
+
<Badge key={col} size="xs" variant="outline">
|
|
306
|
+
{col}
|
|
307
|
+
</Badge>
|
|
308
|
+
))}
|
|
309
|
+
</Flex>
|
|
310
|
+
</Table.Td>
|
|
311
|
+
<Table.Td>
|
|
312
|
+
<Flex gap={4}>
|
|
313
|
+
{c.unique && (
|
|
314
|
+
<Badge size="xs" color="yellow">
|
|
315
|
+
UNIQUE
|
|
316
|
+
</Badge>
|
|
317
|
+
)}
|
|
318
|
+
{c.hasCheck && (
|
|
319
|
+
<Badge size="xs" color="blue">
|
|
320
|
+
CHECK
|
|
321
|
+
</Badge>
|
|
322
|
+
)}
|
|
323
|
+
</Flex>
|
|
324
|
+
</Table.Td>
|
|
325
|
+
</Table.Tr>
|
|
326
|
+
))}
|
|
327
|
+
</Table.Tbody>
|
|
328
|
+
</Table>
|
|
329
|
+
</Box>
|
|
330
|
+
)}
|
|
331
|
+
</Stack>
|
|
332
|
+
</ScrollArea>
|
|
333
|
+
);
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
const DataTab = () => {
|
|
337
|
+
return (
|
|
338
|
+
<Flex align="center" justify="center" h="100%" c="dimmed">
|
|
339
|
+
<Stack align="center" gap="xs">
|
|
340
|
+
<IconDatabase size={48} opacity={0.3} />
|
|
341
|
+
<Text size="sm">Data browser coming soon</Text>
|
|
342
|
+
</Stack>
|
|
343
|
+
</Flex>
|
|
344
|
+
);
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
const EntityPanel = ({ entity }: { entity: DevEntityMetadata }) => {
|
|
348
|
+
const providerColor = getProviderColor(entity.provider);
|
|
349
|
+
|
|
350
|
+
return (
|
|
351
|
+
<Flex direction="column" h="100%">
|
|
352
|
+
{/* Header */}
|
|
353
|
+
<Box
|
|
354
|
+
px="md"
|
|
355
|
+
py="sm"
|
|
356
|
+
style={{
|
|
357
|
+
borderBottom: `1px solid ${ui.colors.border}`,
|
|
358
|
+
backgroundColor: `${providerColor}08`,
|
|
359
|
+
}}
|
|
360
|
+
>
|
|
361
|
+
<Flex align="center" gap="sm">
|
|
362
|
+
<IconTable size={18} opacity={0.7} />
|
|
363
|
+
<Text size="md" fw={600}>
|
|
364
|
+
{entity.name}
|
|
365
|
+
</Text>
|
|
366
|
+
<Badge size="xs" variant="light" color="gray">
|
|
367
|
+
{entity.columns.length} columns
|
|
368
|
+
</Badge>
|
|
369
|
+
</Flex>
|
|
370
|
+
</Box>
|
|
371
|
+
|
|
372
|
+
{/* Tabs */}
|
|
373
|
+
<Tabs
|
|
374
|
+
defaultValue="structure"
|
|
375
|
+
style={{ flex: 1, display: "flex", flexDirection: "column" }}
|
|
376
|
+
>
|
|
377
|
+
<Tabs.List px="md">
|
|
378
|
+
<Tabs.Tab value="structure">Structure</Tabs.Tab>
|
|
379
|
+
<Tabs.Tab value="data">Data</Tabs.Tab>
|
|
380
|
+
</Tabs.List>
|
|
381
|
+
|
|
382
|
+
<Tabs.Panel value="structure" style={{ flex: 1, overflow: "hidden" }}>
|
|
383
|
+
<StructureTab entity={entity} />
|
|
384
|
+
</Tabs.Panel>
|
|
385
|
+
|
|
386
|
+
<Tabs.Panel value="data" style={{ flex: 1, overflow: "hidden" }}>
|
|
387
|
+
<DataTab />
|
|
388
|
+
</Tabs.Panel>
|
|
389
|
+
</Tabs>
|
|
390
|
+
</Flex>
|
|
391
|
+
);
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
const EmptyState = () => (
|
|
395
|
+
<Flex align="center" justify="center" h="100%" c="dimmed">
|
|
396
|
+
<Stack align="center" gap="xs">
|
|
397
|
+
<IconTable size={48} opacity={0.3} />
|
|
398
|
+
<Text size="sm">Select a table to view its structure</Text>
|
|
399
|
+
</Stack>
|
|
400
|
+
</Flex>
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
const NoEntitiesState = () => (
|
|
404
|
+
<Flex align="center" justify="center" h="100%" c="dimmed">
|
|
405
|
+
<Stack align="center" gap="xs">
|
|
406
|
+
<IconDatabase size={48} opacity={0.3} />
|
|
407
|
+
<Text>No entities found</Text>
|
|
408
|
+
<Text size="sm" c="dimmed">
|
|
409
|
+
Add entities using $entity primitive to see them here
|
|
410
|
+
</Text>
|
|
411
|
+
</Stack>
|
|
412
|
+
</Flex>
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
export const DevDbStudio = () => {
|
|
416
|
+
const http = useInject(HttpClient);
|
|
417
|
+
const [entities, setEntities] = useState<DevEntityMetadata[]>([]);
|
|
418
|
+
const [loading, setLoading] = useState(true);
|
|
419
|
+
const [selectedEntity, setSelectedEntity] =
|
|
420
|
+
useState<DevEntityMetadata | null>(null);
|
|
421
|
+
const [search, setSearch] = useState("");
|
|
422
|
+
|
|
423
|
+
// Fetch entities
|
|
424
|
+
useEffect(() => {
|
|
425
|
+
http
|
|
426
|
+
.fetch("/devtools/api/metadata", {
|
|
427
|
+
schema: { response: devMetadataSchema },
|
|
428
|
+
})
|
|
429
|
+
.then((res) => {
|
|
430
|
+
setEntities(res.data.entities);
|
|
431
|
+
setLoading(false);
|
|
432
|
+
});
|
|
433
|
+
}, []);
|
|
434
|
+
|
|
435
|
+
// Filter entities by search
|
|
436
|
+
const filteredEntities = useMemo(() => {
|
|
437
|
+
if (!search) return entities;
|
|
438
|
+
const searchLower = search.toLowerCase();
|
|
439
|
+
return entities.filter((e) => e.name.toLowerCase().includes(searchLower));
|
|
440
|
+
}, [entities, search]);
|
|
441
|
+
|
|
442
|
+
if (loading) {
|
|
443
|
+
return (
|
|
444
|
+
<Flex align="center" justify="center" h="100%">
|
|
445
|
+
<Text c="dimmed">Loading...</Text>
|
|
446
|
+
</Flex>
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (entities.length === 0) {
|
|
451
|
+
return <NoEntitiesState />;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return (
|
|
455
|
+
<Flex h="100%" style={{ overflow: "hidden" }}>
|
|
456
|
+
{/* Sidebar */}
|
|
457
|
+
<Box
|
|
458
|
+
w={240}
|
|
459
|
+
style={{
|
|
460
|
+
borderRight: `1px solid ${ui.colors.border}`,
|
|
461
|
+
backgroundColor: ui.colors.surface,
|
|
462
|
+
}}
|
|
463
|
+
>
|
|
464
|
+
<EntitySidebar
|
|
465
|
+
entities={filteredEntities}
|
|
466
|
+
selectedEntity={selectedEntity}
|
|
467
|
+
onSelectEntity={setSelectedEntity}
|
|
468
|
+
search={search}
|
|
469
|
+
onSearchChange={setSearch}
|
|
470
|
+
/>
|
|
471
|
+
</Box>
|
|
472
|
+
|
|
473
|
+
{/* Main content */}
|
|
474
|
+
<Box style={{ flex: 1, overflow: "hidden" }}>
|
|
475
|
+
{selectedEntity ? (
|
|
476
|
+
<EntityPanel entity={selectedEntity} />
|
|
477
|
+
) : (
|
|
478
|
+
<EmptyState />
|
|
479
|
+
)}
|
|
480
|
+
</Box>
|
|
481
|
+
</Flex>
|
|
482
|
+
);
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
export default DevDbStudio;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Provider colors
|
|
2
|
+
export const PROVIDER_COLORS: Record<string, string> = {
|
|
3
|
+
PostgresProvider: "#336791",
|
|
4
|
+
SqliteProvider: "#003B57",
|
|
5
|
+
DatabaseProvider: "#495057",
|
|
6
|
+
default: "#495057",
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const getProviderColor = (provider: string): string => {
|
|
10
|
+
return PROVIDER_COLORS[provider] ?? PROVIDER_COLORS.default;
|
|
11
|
+
};
|