@alepha/devtools 0.13.6 → 0.13.8
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/assets/devtools/asset.BZV40eAE.css +1 -0
- package/assets/devtools/asset.Bhpm0ujk.css +1 -0
- package/assets/devtools/chunk.17gtbQUO.js +1 -0
- package/assets/devtools/chunk.1mem8WHh.js +1 -0
- package/assets/devtools/chunk.B0aYes_4.js +1 -0
- package/assets/devtools/chunk.BULoWCgJ.js +7 -0
- package/assets/devtools/chunk.BYrPfJRg.js +1 -0
- package/assets/devtools/chunk.BjFrJKj1.js +1 -0
- package/assets/devtools/chunk.BlqFPyLh.js +1 -0
- package/assets/devtools/chunk.BmJ7-uBd.js +1 -0
- package/assets/devtools/chunk.BzE7YYkj.js +6 -0
- package/assets/devtools/chunk.C49FcqzR.js +1 -0
- package/assets/devtools/chunk.CBGZOsrp.js +9 -0
- package/assets/devtools/chunk.CHRbA_gU.js +1 -0
- package/assets/devtools/chunk.Ct58VlQl.js +1 -0
- package/assets/devtools/chunk.Cw2RCl4F.js +1 -0
- package/assets/devtools/chunk.CyQeq1kA.js +1 -0
- package/assets/devtools/chunk.CzbujtK7.js +1 -0
- package/assets/devtools/chunk.DA9XnVAa.js +1 -0
- package/assets/devtools/chunk.DEUHUxKv.js +1 -0
- package/assets/devtools/chunk.DGW-W4Kc.js +1 -0
- package/assets/devtools/chunk.DHWcJNNS.js +1 -0
- package/assets/devtools/chunk.DIfRZc20.js +1 -0
- package/assets/devtools/chunk.DJJIo7HU.js +1 -0
- package/assets/devtools/chunk.DJOi4_So.js +1 -0
- package/assets/devtools/chunk.DR0SHXXd.js +1 -0
- package/assets/devtools/chunk.DinJSUfH.js +1 -0
- package/assets/devtools/chunk.DooL4OcT.js +1 -0
- package/assets/devtools/chunk.Dry2LXOT.js +1 -0
- package/assets/devtools/chunk.IVvrfXp1.js +1 -0
- package/assets/devtools/chunk.OlMI8g2F.js +1 -0
- package/assets/devtools/chunk.Pj_uCbSv.js +1 -0
- package/assets/devtools/chunk.RTodzvo0.js +2 -0
- package/assets/devtools/chunk.YXYL4YAO.js +2 -0
- package/assets/devtools/chunk.fpKvkQeU.js +1 -0
- package/assets/devtools/chunk.qDx9cjbN.js +1 -0
- package/assets/devtools/chunk.rJToME5k.js +1 -0
- package/assets/devtools/chunk.rohGhT-A.js +1 -0
- package/assets/devtools/chunk.uyVen0u2.js +1 -0
- package/assets/devtools/entry.BY4L2Uc6.js +75 -0
- package/assets/devtools/index.html +11 -0
- package/dist/index.d.ts +249 -32
- package/dist/index.js +253 -22
- package/dist/index.js.map +1 -1
- package/package.json +18 -12
- 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,37 @@
|
|
|
1
|
+
import { ui } from "@alepha/ui";
|
|
2
|
+
import { Accordion, Badge, Box, Group, Text } from "@mantine/core";
|
|
3
|
+
import { IconChevronRight } from "@tabler/icons-react";
|
|
4
|
+
import type { DevActionMetadata } from "../../../api/schemas/DevActionMetadata.ts";
|
|
5
|
+
import { ActionItem } from "./ActionItem.tsx";
|
|
6
|
+
|
|
7
|
+
interface ActionGroupProps {
|
|
8
|
+
group: string;
|
|
9
|
+
actions: DevActionMetadata[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const ActionGroup = ({ group, actions }: ActionGroupProps) => (
|
|
13
|
+
<Box>
|
|
14
|
+
<Group gap="xs" mb="xs">
|
|
15
|
+
<IconChevronRight size={14} opacity={0.5} />
|
|
16
|
+
<Text size="xs" fw={600} tt="uppercase" c="dimmed">
|
|
17
|
+
{group}
|
|
18
|
+
</Text>
|
|
19
|
+
<Badge variant="light" color="gray" size="xs">
|
|
20
|
+
{actions.length}
|
|
21
|
+
</Badge>
|
|
22
|
+
</Group>
|
|
23
|
+
<Accordion
|
|
24
|
+
variant="separated"
|
|
25
|
+
styles={{
|
|
26
|
+
item: {
|
|
27
|
+
backgroundColor: ui.colors.surface,
|
|
28
|
+
border: `1px solid ${ui.colors.border}`,
|
|
29
|
+
},
|
|
30
|
+
}}
|
|
31
|
+
>
|
|
32
|
+
{actions.map((action) => (
|
|
33
|
+
<ActionItem key={action.fullPath} action={action} />
|
|
34
|
+
))}
|
|
35
|
+
</Accordion>
|
|
36
|
+
</Box>
|
|
37
|
+
);
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Accordion,
|
|
3
|
+
ActionIcon,
|
|
4
|
+
Badge,
|
|
5
|
+
Box,
|
|
6
|
+
Code,
|
|
7
|
+
CopyButton,
|
|
8
|
+
Group,
|
|
9
|
+
Stack,
|
|
10
|
+
Tabs,
|
|
11
|
+
Text,
|
|
12
|
+
Tooltip,
|
|
13
|
+
} from "@mantine/core";
|
|
14
|
+
import {
|
|
15
|
+
IconCheck,
|
|
16
|
+
IconCopy,
|
|
17
|
+
IconLock,
|
|
18
|
+
IconTerminal,
|
|
19
|
+
} from "@tabler/icons-react";
|
|
20
|
+
import { useMemo } from "react";
|
|
21
|
+
import type { DevActionMetadata } from "../../../api/schemas/DevActionMetadata.ts";
|
|
22
|
+
import { generateCurl } from "./helpers.ts";
|
|
23
|
+
import { MethodBadge } from "./MethodBadge.tsx";
|
|
24
|
+
import { SchemaViewer } from "./SchemaViewer.tsx";
|
|
25
|
+
import { TryItPanel } from "./TryItPanel.tsx";
|
|
26
|
+
|
|
27
|
+
interface ActionItemProps {
|
|
28
|
+
action: DevActionMetadata;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const ActionItem = ({ action }: ActionItemProps) => {
|
|
32
|
+
const curl = useMemo(() => generateCurl(action), [action]);
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<Accordion.Item value={action.fullPath} opacity={action.disabled ? 0.5 : 1}>
|
|
36
|
+
<Accordion.Control>
|
|
37
|
+
<Group gap="sm" wrap="nowrap">
|
|
38
|
+
<MethodBadge method={action.method} />
|
|
39
|
+
<Text size="sm" ff="monospace" style={{ wordBreak: "break-all" }}>
|
|
40
|
+
{action.fullPath}
|
|
41
|
+
</Text>
|
|
42
|
+
{action.secure && (
|
|
43
|
+
<Tooltip label="Requires authentication">
|
|
44
|
+
<IconLock size={14} opacity={0.5} />
|
|
45
|
+
</Tooltip>
|
|
46
|
+
)}
|
|
47
|
+
{action.disabled && (
|
|
48
|
+
<Badge size="xs" variant="light" color="gray">
|
|
49
|
+
disabled
|
|
50
|
+
</Badge>
|
|
51
|
+
)}
|
|
52
|
+
</Group>
|
|
53
|
+
</Accordion.Control>
|
|
54
|
+
<Accordion.Panel>
|
|
55
|
+
<Stack gap="md">
|
|
56
|
+
{(action.description || action.summary) && (
|
|
57
|
+
<Text size="sm" c="dimmed">
|
|
58
|
+
{action.description || action.summary}
|
|
59
|
+
</Text>
|
|
60
|
+
)}
|
|
61
|
+
|
|
62
|
+
<Tabs defaultValue="schema" variant="outline">
|
|
63
|
+
<Tabs.List>
|
|
64
|
+
<Tabs.Tab value="schema" size="xs">
|
|
65
|
+
Schema
|
|
66
|
+
</Tabs.Tab>
|
|
67
|
+
<Tabs.Tab value="try" size="xs">
|
|
68
|
+
Try It
|
|
69
|
+
</Tabs.Tab>
|
|
70
|
+
<Tabs.Tab
|
|
71
|
+
value="curl"
|
|
72
|
+
size="xs"
|
|
73
|
+
leftSection={<IconTerminal size={12} />}
|
|
74
|
+
>
|
|
75
|
+
cURL
|
|
76
|
+
</Tabs.Tab>
|
|
77
|
+
</Tabs.List>
|
|
78
|
+
|
|
79
|
+
<Tabs.Panel value="schema" pt="md">
|
|
80
|
+
<Stack gap="md">
|
|
81
|
+
<Group gap="xl" align="flex-start">
|
|
82
|
+
<SchemaViewer
|
|
83
|
+
schema={action.params}
|
|
84
|
+
label="Path Parameters"
|
|
85
|
+
/>
|
|
86
|
+
<SchemaViewer
|
|
87
|
+
schema={action.query}
|
|
88
|
+
label="Query Parameters"
|
|
89
|
+
/>
|
|
90
|
+
</Group>
|
|
91
|
+
<SchemaViewer schema={action.body} label="Request Body" />
|
|
92
|
+
<SchemaViewer schema={action.response} label="Response" />
|
|
93
|
+
{!action.params &&
|
|
94
|
+
!action.query &&
|
|
95
|
+
!action.body &&
|
|
96
|
+
!action.response && (
|
|
97
|
+
<Text size="sm" c="dimmed">
|
|
98
|
+
No schema defined
|
|
99
|
+
</Text>
|
|
100
|
+
)}
|
|
101
|
+
</Stack>
|
|
102
|
+
</Tabs.Panel>
|
|
103
|
+
|
|
104
|
+
<Tabs.Panel value="try" pt="md">
|
|
105
|
+
<TryItPanel action={action} />
|
|
106
|
+
</Tabs.Panel>
|
|
107
|
+
|
|
108
|
+
<Tabs.Panel value="curl" pt="md">
|
|
109
|
+
<Box pos="relative">
|
|
110
|
+
<CopyButton value={curl}>
|
|
111
|
+
{({ copied, copy }) => (
|
|
112
|
+
<ActionIcon
|
|
113
|
+
size="sm"
|
|
114
|
+
variant="subtle"
|
|
115
|
+
onClick={copy}
|
|
116
|
+
pos="absolute"
|
|
117
|
+
top={8}
|
|
118
|
+
right={8}
|
|
119
|
+
>
|
|
120
|
+
{copied ? (
|
|
121
|
+
<IconCheck size={14} />
|
|
122
|
+
) : (
|
|
123
|
+
<IconCopy size={14} />
|
|
124
|
+
)}
|
|
125
|
+
</ActionIcon>
|
|
126
|
+
)}
|
|
127
|
+
</CopyButton>
|
|
128
|
+
<Code block style={{ fontSize: 11 }}>
|
|
129
|
+
{curl}
|
|
130
|
+
</Code>
|
|
131
|
+
</Box>
|
|
132
|
+
</Tabs.Panel>
|
|
133
|
+
</Tabs>
|
|
134
|
+
</Stack>
|
|
135
|
+
</Accordion.Panel>
|
|
136
|
+
</Accordion.Item>
|
|
137
|
+
);
|
|
138
|
+
};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { useInject } from "@alepha/react";
|
|
2
|
+
import {
|
|
3
|
+
Badge,
|
|
4
|
+
Flex,
|
|
5
|
+
Group,
|
|
6
|
+
Loader,
|
|
7
|
+
ScrollArea,
|
|
8
|
+
SegmentedControl,
|
|
9
|
+
Stack,
|
|
10
|
+
Text,
|
|
11
|
+
TextInput,
|
|
12
|
+
} from "@mantine/core";
|
|
13
|
+
import { IconApi, IconSearch } from "@tabler/icons-react";
|
|
14
|
+
import { HttpClient } from "alepha/server";
|
|
15
|
+
import { useEffect, useMemo, useState } from "react";
|
|
16
|
+
import type { DevActionMetadata } from "../../../api/schemas/DevActionMetadata.ts";
|
|
17
|
+
import { devMetadataSchema } from "../../../api/schemas/DevMetadata.ts";
|
|
18
|
+
import { ActionGroup } from "./ActionGroup.tsx";
|
|
19
|
+
|
|
20
|
+
export const DevActionsExplorer = () => {
|
|
21
|
+
const http = useInject(HttpClient);
|
|
22
|
+
const [actions, setActions] = useState<DevActionMetadata[]>([]);
|
|
23
|
+
const [loading, setLoading] = useState(true);
|
|
24
|
+
const [search, setSearch] = useState("");
|
|
25
|
+
const [methodFilter, setMethodFilter] = useState("all");
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
http
|
|
29
|
+
.fetch("/devtools/api/metadata", {
|
|
30
|
+
schema: { response: devMetadataSchema },
|
|
31
|
+
})
|
|
32
|
+
.then((res) => {
|
|
33
|
+
setActions(res.data.actions);
|
|
34
|
+
setLoading(false);
|
|
35
|
+
});
|
|
36
|
+
}, []);
|
|
37
|
+
|
|
38
|
+
const filteredActions = useMemo(() => {
|
|
39
|
+
return actions.filter((action) => {
|
|
40
|
+
if (action.hide) return false;
|
|
41
|
+
if (
|
|
42
|
+
methodFilter !== "all" &&
|
|
43
|
+
action.method.toUpperCase() !== methodFilter
|
|
44
|
+
) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
if (search) {
|
|
48
|
+
const searchLower = search.toLowerCase();
|
|
49
|
+
return (
|
|
50
|
+
action.fullPath.toLowerCase().includes(searchLower) ||
|
|
51
|
+
action.name.toLowerCase().includes(searchLower) ||
|
|
52
|
+
action.group.toLowerCase().includes(searchLower) ||
|
|
53
|
+
action.description?.toLowerCase().includes(searchLower)
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
57
|
+
});
|
|
58
|
+
}, [actions, search, methodFilter]);
|
|
59
|
+
|
|
60
|
+
const groupedActions = useMemo(() => {
|
|
61
|
+
const groups: Record<string, DevActionMetadata[]> = {};
|
|
62
|
+
for (const action of filteredActions) {
|
|
63
|
+
const group = action.group || "Other";
|
|
64
|
+
if (!groups[group]) groups[group] = [];
|
|
65
|
+
groups[group].push(action);
|
|
66
|
+
}
|
|
67
|
+
return Object.entries(groups).sort(([a], [b]) => a.localeCompare(b));
|
|
68
|
+
}, [filteredActions]);
|
|
69
|
+
|
|
70
|
+
if (loading) {
|
|
71
|
+
return (
|
|
72
|
+
<Flex align="center" justify="center" h="100%">
|
|
73
|
+
<Loader size="sm" />
|
|
74
|
+
</Flex>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<Flex direction="column" gap="md" w="100%" h="100%" p={"xl"}>
|
|
80
|
+
<Group justify="space-between" wrap="nowrap">
|
|
81
|
+
<Group gap="sm">
|
|
82
|
+
<IconApi size={24} opacity={0.7} />
|
|
83
|
+
<Text size="lg" fw={500}>
|
|
84
|
+
Actions
|
|
85
|
+
</Text>
|
|
86
|
+
<Badge variant="light" color="gray" size="sm">
|
|
87
|
+
{filteredActions.length}
|
|
88
|
+
</Badge>
|
|
89
|
+
</Group>
|
|
90
|
+
</Group>
|
|
91
|
+
|
|
92
|
+
<Group gap="sm">
|
|
93
|
+
<TextInput
|
|
94
|
+
placeholder="Search actions..."
|
|
95
|
+
leftSection={<IconSearch size={14} />}
|
|
96
|
+
value={search}
|
|
97
|
+
onChange={(e) => setSearch(e.currentTarget.value)}
|
|
98
|
+
style={{ flex: 1 }}
|
|
99
|
+
size="sm"
|
|
100
|
+
/>
|
|
101
|
+
<SegmentedControl
|
|
102
|
+
size="xs"
|
|
103
|
+
value={methodFilter}
|
|
104
|
+
onChange={setMethodFilter}
|
|
105
|
+
data={[
|
|
106
|
+
{ label: "All", value: "all" },
|
|
107
|
+
{ label: "GET", value: "GET" },
|
|
108
|
+
{ label: "POST", value: "POST" },
|
|
109
|
+
{ label: "PUT", value: "PUT" },
|
|
110
|
+
{ label: "DELETE", value: "DELETE" },
|
|
111
|
+
]}
|
|
112
|
+
/>
|
|
113
|
+
</Group>
|
|
114
|
+
|
|
115
|
+
<ScrollArea flex={1} offsetScrollbars>
|
|
116
|
+
{groupedActions.length === 0 ? (
|
|
117
|
+
<Flex align="center" justify="center" h={200}>
|
|
118
|
+
<Text c="dimmed">No actions found</Text>
|
|
119
|
+
</Flex>
|
|
120
|
+
) : (
|
|
121
|
+
<Stack gap="md">
|
|
122
|
+
{groupedActions.map(([group, groupActions]) => (
|
|
123
|
+
<ActionGroup key={group} group={group} actions={groupActions} />
|
|
124
|
+
))}
|
|
125
|
+
</Stack>
|
|
126
|
+
)}
|
|
127
|
+
</ScrollArea>
|
|
128
|
+
</Flex>
|
|
129
|
+
);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export default DevActionsExplorer;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Badge } from "@mantine/core";
|
|
2
|
+
import { METHOD_COLORS } from "./constants.ts";
|
|
3
|
+
|
|
4
|
+
interface MethodBadgeProps {
|
|
5
|
+
method: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const MethodBadge = ({ method }: MethodBadgeProps) => (
|
|
9
|
+
<Badge
|
|
10
|
+
variant="light"
|
|
11
|
+
color={METHOD_COLORS[method.toUpperCase()] || "gray"}
|
|
12
|
+
size="sm"
|
|
13
|
+
radius="sm"
|
|
14
|
+
w={60}
|
|
15
|
+
>
|
|
16
|
+
{method.toUpperCase()}
|
|
17
|
+
</Badge>
|
|
18
|
+
);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Box, Code, Text } from "@mantine/core";
|
|
2
|
+
|
|
3
|
+
interface SchemaViewerProps {
|
|
4
|
+
schema: unknown;
|
|
5
|
+
label: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const SchemaViewer = ({ schema, label }: SchemaViewerProps) => {
|
|
9
|
+
if (!schema) return null;
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<Box>
|
|
13
|
+
<Text size="xs" fw={500} c="dimmed" mb={4}>
|
|
14
|
+
{label}
|
|
15
|
+
</Text>
|
|
16
|
+
<Code block style={{ fontSize: 11, maxHeight: 200, overflow: "auto" }}>
|
|
17
|
+
{JSON.stringify(schema, null, 2)}
|
|
18
|
+
</Code>
|
|
19
|
+
</Box>
|
|
20
|
+
);
|
|
21
|
+
};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { useInject } from "@alepha/react";
|
|
2
|
+
import {
|
|
3
|
+
ActionIcon,
|
|
4
|
+
Box,
|
|
5
|
+
Button,
|
|
6
|
+
Code,
|
|
7
|
+
CopyButton,
|
|
8
|
+
Group,
|
|
9
|
+
JsonInput,
|
|
10
|
+
Loader,
|
|
11
|
+
Paper,
|
|
12
|
+
ScrollArea,
|
|
13
|
+
Stack,
|
|
14
|
+
Text,
|
|
15
|
+
} from "@mantine/core";
|
|
16
|
+
import { IconCheck, IconCopy, IconPlayerPlay } from "@tabler/icons-react";
|
|
17
|
+
import { HttpClient } from "alepha/server";
|
|
18
|
+
import { useCallback, useState } from "react";
|
|
19
|
+
import type { DevActionMetadata } from "../../../api/schemas/DevActionMetadata.ts";
|
|
20
|
+
|
|
21
|
+
interface TryItPanelProps {
|
|
22
|
+
action: DevActionMetadata;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const TryItPanel = ({ action }: TryItPanelProps) => {
|
|
26
|
+
const http = useInject(HttpClient);
|
|
27
|
+
const [body, setBody] = useState("{}");
|
|
28
|
+
const [response, setResponse] = useState<string | null>(null);
|
|
29
|
+
const [loading, setLoading] = useState(false);
|
|
30
|
+
const [timing, setTiming] = useState<number | null>(null);
|
|
31
|
+
const [error, setError] = useState<string | null>(null);
|
|
32
|
+
|
|
33
|
+
const execute = useCallback(async () => {
|
|
34
|
+
setLoading(true);
|
|
35
|
+
setError(null);
|
|
36
|
+
setResponse(null);
|
|
37
|
+
|
|
38
|
+
const start = performance.now();
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
let parsedBody: unknown;
|
|
42
|
+
if (action.body && body) {
|
|
43
|
+
try {
|
|
44
|
+
parsedBody = JSON.parse(body);
|
|
45
|
+
} catch {
|
|
46
|
+
setError("Invalid JSON body");
|
|
47
|
+
setLoading(false);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const res = await http.fetch(action.fullPath, {
|
|
53
|
+
method: action.method.toUpperCase() as
|
|
54
|
+
| "GET"
|
|
55
|
+
| "POST"
|
|
56
|
+
| "PUT"
|
|
57
|
+
| "DELETE"
|
|
58
|
+
| "PATCH",
|
|
59
|
+
body: parsedBody ? JSON.stringify(parsedBody) : undefined,
|
|
60
|
+
headers: parsedBody
|
|
61
|
+
? { "Content-Type": "application/json" }
|
|
62
|
+
: undefined,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
setTiming(Math.round(performance.now() - start));
|
|
66
|
+
setResponse(JSON.stringify(res.data, null, 2));
|
|
67
|
+
} catch (err) {
|
|
68
|
+
setTiming(Math.round(performance.now() - start));
|
|
69
|
+
setError(err instanceof Error ? err.message : "Request failed");
|
|
70
|
+
} finally {
|
|
71
|
+
setLoading(false);
|
|
72
|
+
}
|
|
73
|
+
}, [http, action, body]);
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<Stack gap="md">
|
|
77
|
+
{action.body && (
|
|
78
|
+
<JsonInput
|
|
79
|
+
label="Request Body"
|
|
80
|
+
placeholder='{"key": "value"}'
|
|
81
|
+
value={body}
|
|
82
|
+
onChange={setBody}
|
|
83
|
+
minRows={4}
|
|
84
|
+
maxRows={10}
|
|
85
|
+
autosize
|
|
86
|
+
formatOnBlur
|
|
87
|
+
validationError="Invalid JSON"
|
|
88
|
+
/>
|
|
89
|
+
)}
|
|
90
|
+
|
|
91
|
+
<Group>
|
|
92
|
+
<Button
|
|
93
|
+
leftSection={
|
|
94
|
+
loading ? <Loader size={14} /> : <IconPlayerPlay size={14} />
|
|
95
|
+
}
|
|
96
|
+
onClick={execute}
|
|
97
|
+
disabled={loading}
|
|
98
|
+
size="sm"
|
|
99
|
+
>
|
|
100
|
+
Execute
|
|
101
|
+
</Button>
|
|
102
|
+
{timing !== null && (
|
|
103
|
+
<Text size="xs" c="dimmed">
|
|
104
|
+
{timing}ms
|
|
105
|
+
</Text>
|
|
106
|
+
)}
|
|
107
|
+
</Group>
|
|
108
|
+
|
|
109
|
+
{error && (
|
|
110
|
+
<Paper p="sm" bg="var(--mantine-color-red-light)">
|
|
111
|
+
<Text size="sm" c="red">
|
|
112
|
+
{error}
|
|
113
|
+
</Text>
|
|
114
|
+
</Paper>
|
|
115
|
+
)}
|
|
116
|
+
|
|
117
|
+
{response && (
|
|
118
|
+
<Box>
|
|
119
|
+
<Group justify="space-between" mb={4}>
|
|
120
|
+
<Text size="xs" fw={500} c="dimmed">
|
|
121
|
+
Response
|
|
122
|
+
</Text>
|
|
123
|
+
<CopyButton value={response}>
|
|
124
|
+
{({ copied, copy }) => (
|
|
125
|
+
<ActionIcon size="xs" variant="subtle" onClick={copy}>
|
|
126
|
+
{copied ? <IconCheck size={12} /> : <IconCopy size={12} />}
|
|
127
|
+
</ActionIcon>
|
|
128
|
+
)}
|
|
129
|
+
</CopyButton>
|
|
130
|
+
</Group>
|
|
131
|
+
<ScrollArea h={200}>
|
|
132
|
+
<Code block style={{ fontSize: 11 }}>
|
|
133
|
+
{response}
|
|
134
|
+
</Code>
|
|
135
|
+
</ScrollArea>
|
|
136
|
+
</Box>
|
|
137
|
+
)}
|
|
138
|
+
</Stack>
|
|
139
|
+
);
|
|
140
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { DevActionMetadata } from "../../../api/schemas/DevActionMetadata.ts";
|
|
2
|
+
|
|
3
|
+
export const generateCurl = (
|
|
4
|
+
action: DevActionMetadata,
|
|
5
|
+
body?: string,
|
|
6
|
+
): string => {
|
|
7
|
+
const parts = [`curl -X ${action.method.toUpperCase()}`];
|
|
8
|
+
parts.push(` '${window.location.origin}${action.fullPath}'`);
|
|
9
|
+
|
|
10
|
+
if (action.body && body) {
|
|
11
|
+
parts.push(
|
|
12
|
+
` -H 'Content-Type: ${action.bodyContentType || "application/json"}'`,
|
|
13
|
+
);
|
|
14
|
+
parts.push(` -d '${body}'`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return parts.join(" \\\n");
|
|
18
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { ActionGroup } from "./ActionGroup.tsx";
|
|
2
|
+
export { ActionItem } from "./ActionItem.tsx";
|
|
3
|
+
export { METHOD_COLORS } from "./constants.ts";
|
|
4
|
+
export { DevActionsExplorer } from "./DevActionsExplorer.tsx";
|
|
5
|
+
export { generateCurl } from "./helpers.ts";
|
|
6
|
+
export { MethodBadge } from "./MethodBadge.tsx";
|
|
7
|
+
export { SchemaViewer } from "./SchemaViewer.tsx";
|
|
8
|
+
export { TryItPanel } from "./TryItPanel.tsx";
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Badge, Tooltip } from "@mantine/core";
|
|
2
|
+
import type { DevEntityColumn } from "./types.ts";
|
|
3
|
+
|
|
4
|
+
interface ColumnBadgeProps {
|
|
5
|
+
column: DevEntityColumn;
|
|
6
|
+
size?: "xs" | "sm";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const ColumnBadge = ({ column, size = "xs" }: ColumnBadgeProps) => {
|
|
10
|
+
const badges: Array<{ label: string; tooltip: string }> = [];
|
|
11
|
+
|
|
12
|
+
if (column.primaryKey) {
|
|
13
|
+
badges.push({ label: "PK", tooltip: "Primary Key" });
|
|
14
|
+
}
|
|
15
|
+
if (column.identity) {
|
|
16
|
+
badges.push({ label: "ID", tooltip: "Auto-increment Identity" });
|
|
17
|
+
}
|
|
18
|
+
if (column.ref) {
|
|
19
|
+
badges.push({
|
|
20
|
+
label: "FK",
|
|
21
|
+
tooltip: `Foreign Key → ${column.ref.entity}.${column.ref.column}`,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
if (column.createdAt) {
|
|
25
|
+
badges.push({ label: "C", tooltip: "Created At (auto-set on insert)" });
|
|
26
|
+
}
|
|
27
|
+
if (column.updatedAt) {
|
|
28
|
+
badges.push({ label: "U", tooltip: "Updated At (auto-set on update)" });
|
|
29
|
+
}
|
|
30
|
+
if (column.deletedAt) {
|
|
31
|
+
badges.push({ label: "D", tooltip: "Deleted At (soft delete)" });
|
|
32
|
+
}
|
|
33
|
+
if (column.version) {
|
|
34
|
+
badges.push({ label: "V", tooltip: "Version (optimistic locking)" });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<>
|
|
39
|
+
{badges.map((b) => (
|
|
40
|
+
<Tooltip key={b.label} label={b.tooltip}>
|
|
41
|
+
<Badge size={size} variant="light" color="blue">
|
|
42
|
+
{b.label}
|
|
43
|
+
</Badge>
|
|
44
|
+
</Tooltip>
|
|
45
|
+
))}
|
|
46
|
+
{column.nullable && (
|
|
47
|
+
<Tooltip label="Nullable">
|
|
48
|
+
<Badge size={size} variant="outline" color="gray">
|
|
49
|
+
?
|
|
50
|
+
</Badge>
|
|
51
|
+
</Tooltip>
|
|
52
|
+
)}
|
|
53
|
+
</>
|
|
54
|
+
);
|
|
55
|
+
};
|