@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,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
|
+
};
|