@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
|
@@ -5,7 +5,7 @@ import { type Page, t } from "alepha";
|
|
|
5
5
|
import { dayjs } from "alepha/datetime";
|
|
6
6
|
import { type LogEntry, logEntrySchema } from "alepha/logger";
|
|
7
7
|
import { HttpClient } from "alepha/server";
|
|
8
|
-
import { logs } from "../../entities/logs.ts";
|
|
8
|
+
import { logs } from "../../api/entities/logs.ts";
|
|
9
9
|
|
|
10
10
|
const DevLogViewer = () => {
|
|
11
11
|
const http = useInject(HttpClient);
|
|
@@ -66,188 +66,186 @@ const DevLogViewer = () => {
|
|
|
66
66
|
});
|
|
67
67
|
|
|
68
68
|
return (
|
|
69
|
-
<
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
69
|
+
<DataTable<LogEntry, typeof filters>
|
|
70
|
+
submitOnInit
|
|
71
|
+
infinityScroll
|
|
72
|
+
submitEvery={[10, "seconds"]}
|
|
73
|
+
defaultSize={20}
|
|
74
|
+
typeFormProps={{
|
|
75
|
+
skipSubmitButton: true,
|
|
76
|
+
columns: 2,
|
|
77
|
+
}}
|
|
78
|
+
tableProps={{
|
|
79
|
+
horizontalSpacing: "xs",
|
|
80
|
+
verticalSpacing: 0,
|
|
81
|
+
}}
|
|
82
|
+
onFilterChange={(key, value, form) => {
|
|
83
|
+
if (key === "search" || key === "level") {
|
|
84
|
+
return form.submit();
|
|
85
|
+
}
|
|
86
|
+
}}
|
|
87
|
+
filters={filters}
|
|
88
|
+
tableTrProps={(item) => {
|
|
89
|
+
if (item.level.toLowerCase() === "error") {
|
|
90
|
+
return {
|
|
91
|
+
bg: "rgba(255,0,0,0.1)",
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
if (item.level.toLowerCase() === "warn") {
|
|
95
|
+
return {
|
|
96
|
+
bg: "rgba(255,153,0,0.1)",
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return {};
|
|
100
|
+
}}
|
|
101
|
+
items={async (filters, ctx) => {
|
|
102
|
+
if (filters.search) {
|
|
103
|
+
filters.search = filters.search.replace(
|
|
104
|
+
"now()",
|
|
105
|
+
new Date().toISOString(),
|
|
106
|
+
);
|
|
107
|
+
}
|
|
109
108
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
109
|
+
if (filters.page && filters.page > 0) {
|
|
110
|
+
const next = `timestamp < ${ctx.items[0].timestamp}`;
|
|
111
|
+
if (filters.search) {
|
|
112
|
+
filters.search += `& ${next}`;
|
|
113
|
+
} else {
|
|
114
|
+
filters.search = next;
|
|
117
115
|
}
|
|
116
|
+
}
|
|
118
117
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
118
|
+
if (filters.level) {
|
|
119
|
+
const levelFilter = filters.level
|
|
120
|
+
.map((it) => `level = ${it}`)
|
|
121
|
+
.join(" | ");
|
|
123
122
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
123
|
+
if (filters.search) {
|
|
124
|
+
filters.search += `& (${levelFilter})`;
|
|
125
|
+
} else {
|
|
126
|
+
filters.search = levelFilter;
|
|
129
127
|
}
|
|
128
|
+
}
|
|
130
129
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
130
|
+
const queryParams = new URLSearchParams(
|
|
131
|
+
filters as Record<string, any>,
|
|
132
|
+
).toString();
|
|
134
133
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
},
|
|
141
|
-
},
|
|
142
|
-
);
|
|
134
|
+
const response = await http.fetch(`/devtools/api/logs?${queryParams}`, {
|
|
135
|
+
schema: {
|
|
136
|
+
response: t.page(logEntrySchema),
|
|
137
|
+
},
|
|
138
|
+
});
|
|
143
139
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
140
|
+
return response.data as Page<LogEntry>;
|
|
141
|
+
}}
|
|
142
|
+
columns={{
|
|
143
|
+
timestamp: {
|
|
144
|
+
label: "timestamp",
|
|
145
|
+
value: (item, { form }) => (
|
|
146
|
+
<ActionButton
|
|
147
|
+
intent={"none"}
|
|
148
|
+
variant={"transparent"}
|
|
149
|
+
h={20}
|
|
150
|
+
size={"xs"}
|
|
151
|
+
tooltip={l(item.timestamp, {
|
|
152
|
+
date: "DD MMM YYYY HH:mm:ss.SSS",
|
|
153
|
+
})}
|
|
154
|
+
onClick={() => {
|
|
155
|
+
const before = dayjs(item.timestamp).subtract(1, "minute");
|
|
156
|
+
const after = dayjs(item.timestamp).add(1, "minute");
|
|
157
|
+
form.input.search.set(
|
|
158
|
+
`timestamp >= ${before.toISOString()} & timestamp <= ${after.toISOString()}`,
|
|
159
|
+
);
|
|
160
|
+
}}
|
|
161
|
+
>
|
|
162
|
+
{l(item.timestamp, {
|
|
163
|
+
date: "HH:mm:ss.SSS",
|
|
164
|
+
})}
|
|
165
|
+
</ActionButton>
|
|
166
|
+
),
|
|
167
|
+
},
|
|
168
|
+
level: {
|
|
169
|
+
label: "level",
|
|
170
|
+
value: (item) => renderLevel(item.level),
|
|
171
|
+
},
|
|
172
|
+
app: {
|
|
173
|
+
label: "app",
|
|
174
|
+
value: (item) => item.app,
|
|
175
|
+
},
|
|
176
|
+
context: {
|
|
177
|
+
fit: true,
|
|
178
|
+
label: "context",
|
|
179
|
+
value: (item, { form }) =>
|
|
180
|
+
item.context && (
|
|
150
181
|
<ActionButton
|
|
182
|
+
intent={"none"}
|
|
183
|
+
variant={"transparent"}
|
|
151
184
|
h={20}
|
|
152
|
-
c={"dimmed"}
|
|
153
185
|
size={"xs"}
|
|
154
|
-
tooltip={l(item.timestamp, {
|
|
155
|
-
date: "DD MMM YYYY HH:mm:ss.SSS",
|
|
156
|
-
})}
|
|
157
186
|
onClick={() => {
|
|
158
|
-
|
|
159
|
-
const after = dayjs(item.timestamp).add(1, "minute");
|
|
160
|
-
form.input.search.set(
|
|
161
|
-
`timestamp >= ${before.toISOString()} & timestamp <= ${after.toISOString()}`,
|
|
162
|
-
);
|
|
187
|
+
form.input.search.set(`context = ${item.context}`);
|
|
163
188
|
}}
|
|
164
189
|
>
|
|
165
|
-
{
|
|
166
|
-
|
|
167
|
-
|
|
190
|
+
<Text ff={"monospace"} size={"xs"}>
|
|
191
|
+
{item.context.replaceAll("-", "").slice(0, 10)}
|
|
192
|
+
</Text>
|
|
168
193
|
</ActionButton>
|
|
169
194
|
),
|
|
195
|
+
},
|
|
196
|
+
service: {
|
|
197
|
+
fit: true,
|
|
198
|
+
label: "service",
|
|
199
|
+
value: (item) => (
|
|
200
|
+
<Flex align={"center"} justify={"end"}>
|
|
201
|
+
{item.module && (
|
|
202
|
+
<Text c="dimmed" size={"xs"}>
|
|
203
|
+
{item.module}.
|
|
204
|
+
</Text>
|
|
205
|
+
)}
|
|
206
|
+
<Text size={"sm"}>{item.service}</Text>
|
|
207
|
+
</Flex>
|
|
208
|
+
),
|
|
209
|
+
},
|
|
210
|
+
message: {
|
|
211
|
+
label: "message",
|
|
212
|
+
value: (item) => {
|
|
213
|
+
if (item.data?.ms && item.data.status) {
|
|
214
|
+
return `${item.message} - ${item.data.status} [${item.data.ms}ms]`;
|
|
215
|
+
}
|
|
216
|
+
if (item.data?.path && item.data.method) {
|
|
217
|
+
return `${item.message} - ${item.data.method} ${item.data.path}`;
|
|
218
|
+
}
|
|
219
|
+
return item.message;
|
|
170
220
|
},
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
},
|
|
179
|
-
context: {
|
|
180
|
-
fit: true,
|
|
181
|
-
label: "context",
|
|
182
|
-
value: (item, { form }) =>
|
|
183
|
-
item.context && (
|
|
184
|
-
<ActionButton
|
|
185
|
-
h={20}
|
|
186
|
-
size={"xs"}
|
|
187
|
-
onClick={() => {
|
|
188
|
-
form.input.search.set(`context = ${item.context}`);
|
|
189
|
-
}}
|
|
190
|
-
>
|
|
191
|
-
<Text ff={"monospace"} size={"xs"} c="dimmed">
|
|
192
|
-
{item.context.replaceAll("-", "").slice(0, 10)}
|
|
193
|
-
</Text>
|
|
194
|
-
</ActionButton>
|
|
195
|
-
),
|
|
196
|
-
},
|
|
197
|
-
service: {
|
|
198
|
-
fit: true,
|
|
199
|
-
label: "service",
|
|
200
|
-
value: (item) => (
|
|
201
|
-
<Flex align={"center"} justify={"end"}>
|
|
202
|
-
{item.module && (
|
|
203
|
-
<Text c="dimmed" size={"xs"}>
|
|
204
|
-
{item.module}.
|
|
205
|
-
</Text>
|
|
206
|
-
)}
|
|
207
|
-
<Text size={"sm"}>{item.service}</Text>
|
|
208
|
-
</Flex>
|
|
209
|
-
),
|
|
210
|
-
},
|
|
211
|
-
message: {
|
|
212
|
-
label: "message",
|
|
213
|
-
value: (item) => {
|
|
214
|
-
if (item.data?.ms && item.data.status) {
|
|
215
|
-
return `${item.message} - ${item.data.status} [${item.data.ms}ms]`;
|
|
216
|
-
}
|
|
217
|
-
if (item.data?.path && item.data.method) {
|
|
218
|
-
return `${item.message} - ${item.data.method} ${item.data.path}`;
|
|
219
|
-
}
|
|
220
|
-
return item.message;
|
|
221
|
-
},
|
|
222
|
-
},
|
|
223
|
-
data: {
|
|
224
|
-
label: "more",
|
|
225
|
-
value: (item, { alepha }) => {
|
|
226
|
-
if (!item.data) {
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
221
|
+
},
|
|
222
|
+
data: {
|
|
223
|
+
label: "more",
|
|
224
|
+
value: (item, { alepha }) => {
|
|
225
|
+
if (!item.data) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
229
228
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
229
|
+
if (Object.keys(item.data).length === 0) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
233
232
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
},
|
|
233
|
+
return (
|
|
234
|
+
<ActionButton
|
|
235
|
+
opacity={0.5}
|
|
236
|
+
h={20}
|
|
237
|
+
px={4}
|
|
238
|
+
size={"xs"}
|
|
239
|
+
fw={"bold"}
|
|
240
|
+
onClick={() => alepha.inject(DialogService).json(item.data)}
|
|
241
|
+
>
|
|
242
|
+
{"{ ... }"}
|
|
243
|
+
</ActionButton>
|
|
244
|
+
);
|
|
247
245
|
},
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
|
|
246
|
+
},
|
|
247
|
+
}}
|
|
248
|
+
/>
|
|
251
249
|
);
|
|
252
250
|
};
|
|
253
251
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Queue Monitor
|
|
3
|
+
*
|
|
4
|
+
* Real-time view of background job queues.
|
|
5
|
+
*
|
|
6
|
+
* Features to implement:
|
|
7
|
+
* - List all queues with their provider (redis, memory)
|
|
8
|
+
* - For each queue show:
|
|
9
|
+
* - Pending count
|
|
10
|
+
* - Active count (currently processing)
|
|
11
|
+
* - Completed count (last hour/day)
|
|
12
|
+
* - Failed count with error preview
|
|
13
|
+
* - Click queue to see recent jobs:
|
|
14
|
+
* - Job ID, status, created, started, completed, duration
|
|
15
|
+
* - Payload (JSON viewer)
|
|
16
|
+
* - Error message and stack trace for failed jobs
|
|
17
|
+
* - Actions:
|
|
18
|
+
* - Retry single failed job
|
|
19
|
+
* - Retry all failed jobs
|
|
20
|
+
* - Purge completed jobs
|
|
21
|
+
* - Pause/resume queue
|
|
22
|
+
* - Auto-refresh toggle (poll every 5s)
|
|
23
|
+
* - Filter by status (pending, active, completed, failed)
|
|
24
|
+
*
|
|
25
|
+
* Data source: Need new endpoints in DevToolsProvider
|
|
26
|
+
* - GET /devtools/api/queues -> queue stats
|
|
27
|
+
* - GET /devtools/api/queues/:name/jobs -> job list
|
|
28
|
+
* - POST /devtools/api/queues/:name/jobs/:id/retry
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { Flex, Text, ThemeIcon } from "@mantine/core";
|
|
32
|
+
import { IconStack2 } from "@tabler/icons-react";
|
|
33
|
+
|
|
34
|
+
export const DevQueueMonitor = () => {
|
|
35
|
+
return (
|
|
36
|
+
<Flex direction="column" gap="xl" w="100%" p={"xl"}>
|
|
37
|
+
<Flex align="center" gap="sm">
|
|
38
|
+
<ThemeIcon size={32} variant="light" color="orange">
|
|
39
|
+
<IconStack2 size={20} />
|
|
40
|
+
</ThemeIcon>
|
|
41
|
+
<Text size="xl" fw={600}>
|
|
42
|
+
Queue Monitor
|
|
43
|
+
</Text>
|
|
44
|
+
</Flex>
|
|
45
|
+
|
|
46
|
+
<Text c="dimmed">Background job queues - coming soon</Text>
|
|
47
|
+
</Flex>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export default DevQueueMonitor;
|