@alepha/ui 0.16.0 → 0.16.2
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/admin/{AdminApiKeys-GMORg-1l.js → AdminApiKeys-CoTOTfgU.js} +4 -3
- package/dist/admin/{AdminApiKeys-GMORg-1l.js.map → AdminApiKeys-CoTOTfgU.js.map} +1 -1
- package/dist/admin/{AdminAudits-pkWrjq1Z.js → AdminAudits-BmsxFbDa.js} +4 -3
- package/dist/admin/{AdminAudits-pkWrjq1Z.js.map → AdminAudits-BmsxFbDa.js.map} +1 -1
- package/dist/admin/{AdminFiles-WeQbsCsl.js → AdminFiles-BBB8knca.js} +4 -3
- package/dist/admin/{AdminFiles-WeQbsCsl.js.map → AdminFiles-BBB8knca.js.map} +1 -1
- package/dist/admin/{AdminJobs-B-q9iGO3.js → AdminJobs-C604joTz.js} +4 -3
- package/dist/admin/{AdminJobs-B-q9iGO3.js.map → AdminJobs-C604joTz.js.map} +1 -1
- package/dist/admin/{AdminLayout-D8yZ-8lG.js → AdminLayout-CsjvpeD1.js} +6 -10
- package/dist/admin/AdminLayout-CsjvpeD1.js.map +1 -0
- package/dist/admin/{AdminNotifications-Ds5Un0NJ.js → AdminNotifications-LwR6RKrx.js} +4 -3
- package/dist/admin/{AdminNotifications-Ds5Un0NJ.js.map → AdminNotifications-LwR6RKrx.js.map} +1 -1
- package/dist/admin/AdminParameters-B_83Vie9.js +767 -0
- package/dist/admin/AdminParameters-B_83Vie9.js.map +1 -0
- package/dist/admin/{AdminSessions-DzIOxM3b.js → AdminSessions-CWnPosdd.js} +4 -3
- package/dist/admin/{AdminSessions-DzIOxM3b.js.map → AdminSessions-CWnPosdd.js.map} +1 -1
- package/dist/admin/{AdminUserAudits-CiUPN2BC.js → AdminUserAudits-nHv636E_.js} +4 -3
- package/dist/admin/{AdminUserAudits-CiUPN2BC.js.map → AdminUserAudits-nHv636E_.js.map} +1 -1
- package/dist/admin/{AdminUserCreate-BwQKr4xE.js → AdminUserCreate-CjYD3Kjc.js} +4 -3
- package/dist/admin/{AdminUserCreate-BwQKr4xE.js.map → AdminUserCreate-CjYD3Kjc.js.map} +1 -1
- package/dist/admin/{AdminUserDetails-uqtC5aJ1.js → AdminUserDetails-Ccq-LsZ0.js} +4 -3
- package/dist/admin/{AdminUserDetails-uqtC5aJ1.js.map → AdminUserDetails-Ccq-LsZ0.js.map} +1 -1
- package/dist/admin/{AdminUserLayout-CiPay35T.js → AdminUserLayout-7s41DiF_.js} +6 -7
- package/dist/admin/AdminUserLayout-7s41DiF_.js.map +1 -0
- package/dist/admin/{AdminUserSessions-DAE8Nf1F.js → AdminUserSessions-Ds3ODq_d.js} +4 -3
- package/dist/admin/{AdminUserSessions-DAE8Nf1F.js.map → AdminUserSessions-Ds3ODq_d.js.map} +1 -1
- package/dist/admin/{AdminUserSettings-EbahaV2a.js → AdminUserSettings-CGh4gROo.js} +4 -3
- package/dist/admin/{AdminUserSettings-EbahaV2a.js.map → AdminUserSettings-CGh4gROo.js.map} +1 -1
- package/dist/admin/{AdminUsers-Dcjh0KNW.js → AdminUsers-CvPiBzQK.js} +4 -3
- package/dist/admin/{AdminUsers-Dcjh0KNW.js.map → AdminUsers-CvPiBzQK.js.map} +1 -1
- package/dist/admin/index.d.ts +22 -10
- package/dist/admin/index.d.ts.map +1 -1
- package/dist/admin/index.js +47 -48
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/rolldown-runtime-CjeV3_4I.js +18 -0
- package/dist/auth/{AuthLayout-mFOWbiSP.js → AuthLayout-CdJcrPs4.js} +2 -4
- package/dist/auth/AuthLayout-CdJcrPs4.js.map +1 -0
- package/dist/{demo/IconGoogle-CbBF8Hqq.js → auth/IconGoogle-Bm18QD2q.js} +2 -4
- package/dist/auth/{IconGoogle-DpSlPZ1u.js.map → IconGoogle-Bm18QD2q.js.map} +1 -1
- package/dist/auth/{Login-BBqTosqZ.js → Login-DS_OqA0G.js} +7 -6
- package/dist/auth/Login-DS_OqA0G.js.map +1 -0
- package/dist/auth/{Profile-Bxj8Nwom.js → Profile-Di7N7HZL.js} +2 -3
- package/dist/auth/{Profile-Bxj8Nwom.js.map → Profile-Di7N7HZL.js.map} +1 -1
- package/dist/auth/{Register-Ce675Crg.js → Register-BRR2_gux.js} +7 -6
- package/dist/auth/Register-BRR2_gux.js.map +1 -0
- package/dist/auth/{ResetPassword-DWdt7c40.js → ResetPassword-oQu72lod.js} +4 -3
- package/dist/auth/{ResetPassword-DWdt7c40.js.map → ResetPassword-oQu72lod.js.map} +1 -1
- package/dist/auth/{VerifyEmail-CI4JwByV.js → VerifyEmail-DC6HPZjd.js} +4 -3
- package/dist/auth/{VerifyEmail-CI4JwByV.js.map → VerifyEmail-DC6HPZjd.js.map} +1 -1
- package/dist/auth/index.d.ts +14 -14
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +13 -17
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/rolldown-runtime-CjeV3_4I.js +18 -0
- package/dist/core/index.d.ts +147 -68
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +349 -287
- package/dist/core/index.js.map +1 -1
- package/dist/demo/{DemoDataTable-CguplbR7.js → DemoDataTable-DCsJq8v5.js} +4 -5
- package/dist/demo/DemoDataTable-DCsJq8v5.js.map +1 -0
- package/dist/demo/{DemoHome-Cce2bWmg.js → DemoHome-DpRrPlBC.js} +4 -3
- package/dist/demo/{DemoHome-Cce2bWmg.js.map → DemoHome-DpRrPlBC.js.map} +1 -1
- package/dist/demo/{DemoJsonViewer-Dgdk3Txb.js → DemoJsonViewer-zeucGKHV.js} +6 -5
- package/dist/demo/DemoJsonViewer-zeucGKHV.js.map +1 -0
- package/dist/demo/{DemoLayout-B20TEuhV.js → DemoLayout-PhgbAAiQ.js} +6 -5
- package/dist/demo/DemoLayout-PhgbAAiQ.js.map +1 -0
- package/dist/demo/{DemoLogin-CvCG2WVh.js → DemoLogin-DSzP0Lkv.js} +8 -10
- package/dist/demo/DemoLogin-DSzP0Lkv.js.map +1 -0
- package/dist/demo/{DemoRegister-CmeHbOAs.js → DemoRegister-DavFBsCz.js} +8 -10
- package/dist/demo/DemoRegister-DavFBsCz.js.map +1 -0
- package/dist/demo/{DemoResetPassword-CKO5iA_6.js → DemoResetPassword-BS2rIAQK.js} +5 -7
- package/dist/demo/DemoResetPassword-BS2rIAQK.js.map +1 -0
- package/dist/demo/{DemoSidebar-MVmQKfMt.js → DemoSidebar-zNkUmHRl.js} +4 -5
- package/dist/demo/DemoSidebar-zNkUmHRl.js.map +1 -0
- package/dist/demo/{DemoTypeForm-w-qtfRlC.js → DemoTypeForm-B9q7oT0b.js} +4 -5
- package/dist/demo/DemoTypeForm-B9q7oT0b.js.map +1 -0
- package/dist/demo/{DemoVerifyEmail-C8FFJT5A.js → DemoVerifyEmail-Bi4SdWz0.js} +5 -7
- package/dist/demo/DemoVerifyEmail-Bi4SdWz0.js.map +1 -0
- package/dist/{auth/IconGoogle-DpSlPZ1u.js → demo/IconGoogle-CTeZyrek.js} +2 -4
- package/dist/demo/{IconGoogle-CbBF8Hqq.js.map → IconGoogle-CTeZyrek.js.map} +1 -1
- package/dist/demo/{Showcase-CQrMWars.js → Showcase-C9btr_SJ.js} +3 -5
- package/dist/demo/Showcase-C9btr_SJ.js.map +1 -0
- package/dist/demo/index.d.ts +2 -2
- package/dist/demo/index.d.ts.map +1 -1
- package/dist/demo/index.js +15 -15
- package/dist/demo/rolldown-runtime-CjeV3_4I.js +18 -0
- package/package.json +5 -3
- package/src/admin/AdminRouter.ts +15 -24
- package/src/admin/components/AdminLayout.tsx +6 -10
- package/src/admin/components/parameters/AdminParameters.tsx +154 -76
- package/src/admin/components/parameters/ParameterDetails.tsx +153 -93
- package/src/admin/components/parameters/ParameterEmptyState.tsx +27 -0
- package/src/admin/components/parameters/ParameterHistory.tsx +15 -20
- package/src/admin/components/parameters/ParameterTree.tsx +280 -104
- package/src/admin/components/parameters/types.ts +3 -3
- package/src/admin/primitives/$uiAdmin.ts +2 -2
- package/src/auth/AuthRouter.ts +1 -4
- package/src/auth/components/AuthLayout.tsx +0 -1
- package/src/core/components/buttons/ActionButton.tsx +4 -15
- package/src/core/components/buttons/DarkModeButton.tsx +8 -4
- package/src/core/components/buttons/ToggleSidebarButton.tsx +3 -5
- package/src/core/components/form/Control.tsx +10 -32
- package/src/core/components/form/ControlArray.tsx +200 -89
- package/src/core/components/form/TypeForm.browser.spec.tsx +727 -0
- package/src/core/components/layout/AlephaMantineProvider.tsx +1 -0
- package/src/core/components/layout/Breadcrumb.tsx +91 -0
- package/src/core/components/layout/{AdminShell.tsx → DashboardShell.tsx} +77 -32
- package/src/core/components/layout/Sidebar.tsx +58 -18
- package/src/core/constants/ui.ts +1 -1
- package/src/core/helpers/renderIcon.tsx +5 -2
- package/src/core/index.ts +9 -5
- package/src/core/styles.css +7 -6
- package/src/core/utils/string.ts +28 -4
- package/src/demo/components/DemoLayout.tsx +6 -2
- package/dist/admin/AdminApiKeys-DsmGnHNh.js +0 -3
- package/dist/admin/AdminAudits-8SM96viT.js +0 -3
- package/dist/admin/AdminFiles-B56ocq4H.js +0 -3
- package/dist/admin/AdminJobs-CED1syCn.js +0 -3
- package/dist/admin/AdminLayout-D8yZ-8lG.js.map +0 -1
- package/dist/admin/AdminNotifications-B0B1rdc4.js +0 -3
- package/dist/admin/AdminParameters-BU3lATdJ.js +0 -3
- package/dist/admin/AdminParameters-CfDUpc78.js +0 -575
- package/dist/admin/AdminParameters-CfDUpc78.js.map +0 -1
- package/dist/admin/AdminSessions-BDGK2MS6.js +0 -3
- package/dist/admin/AdminUserAudits-Cj79gENT.js +0 -3
- package/dist/admin/AdminUserCreate-Cq-mUmBs.js +0 -3
- package/dist/admin/AdminUserDetails-DRjVAPFd.js +0 -3
- package/dist/admin/AdminUserLayout-CGzmHHby.js +0 -3
- package/dist/admin/AdminUserLayout-CiPay35T.js.map +0 -1
- package/dist/admin/AdminUserSessions-DcdzuNZ9.js +0 -3
- package/dist/admin/AdminUserSettings-D7V6-ceX.js +0 -3
- package/dist/admin/AdminUsers-D9nyzGqQ.js +0 -3
- package/dist/auth/AuthLayout-mFOWbiSP.js.map +0 -1
- package/dist/auth/Login-BBqTosqZ.js.map +0 -1
- package/dist/auth/Login-CoU63mMR.js +0 -4
- package/dist/auth/Register-BV_oa_AK.js +0 -4
- package/dist/auth/Register-Ce675Crg.js.map +0 -1
- package/dist/auth/ResetPassword-D5wC8GAA.js +0 -3
- package/dist/auth/VerifyEmail-DAfqVm5s.js +0 -3
- package/dist/demo/DemoDataTable-CguplbR7.js.map +0 -1
- package/dist/demo/DemoHome-DC9qkMNe.js +0 -3
- package/dist/demo/DemoJsonViewer-DIssGVlJ.js +0 -4
- package/dist/demo/DemoJsonViewer-Dgdk3Txb.js.map +0 -1
- package/dist/demo/DemoLayout-B20TEuhV.js.map +0 -1
- package/dist/demo/DemoLayout-DSRyf4qJ.js +0 -3
- package/dist/demo/DemoLogin-CvCG2WVh.js.map +0 -1
- package/dist/demo/DemoRegister-CmeHbOAs.js.map +0 -1
- package/dist/demo/DemoResetPassword-CKO5iA_6.js.map +0 -1
- package/dist/demo/DemoSidebar-MVmQKfMt.js.map +0 -1
- package/dist/demo/DemoTypeForm-w-qtfRlC.js.map +0 -1
- package/dist/demo/DemoVerifyEmail-C8FFJT5A.js.map +0 -1
- package/dist/demo/Showcase-CQrMWars.js.map +0 -1
|
@@ -1,61 +1,115 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { Text, useToast } from "@alepha/ui";
|
|
2
|
+
import { Card, Flex, Stack } from "@mantine/core";
|
|
3
3
|
import { IconSettings } from "@tabler/icons-react";
|
|
4
4
|
import type {
|
|
5
|
-
|
|
6
|
-
ConfigTreeNode,
|
|
5
|
+
AdminParameterController,
|
|
7
6
|
Parameter,
|
|
7
|
+
ParameterTreeNode,
|
|
8
8
|
} from "alepha/api/parameters";
|
|
9
9
|
import { useClient } from "alepha/react";
|
|
10
10
|
import { useCallback, useEffect, useState } from "react";
|
|
11
11
|
import ParameterDetails from "./ParameterDetails.tsx";
|
|
12
|
+
import ParameterEmptyState from "./ParameterEmptyState.tsx";
|
|
12
13
|
import ParameterHistory from "./ParameterHistory.tsx";
|
|
13
14
|
import ParameterTree from "./ParameterTree.tsx";
|
|
14
|
-
import type {
|
|
15
|
+
import type { ParameterValue } from "./types.ts";
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
export interface AdminParametersProps {
|
|
18
|
+
treeData: ParameterTreeNode[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const AdminParameters = ({
|
|
22
|
+
treeData: initialTreeData,
|
|
23
|
+
}: AdminParametersProps) => {
|
|
24
|
+
const client = useClient<AdminParameterController>();
|
|
25
|
+
const toast = useToast();
|
|
18
26
|
|
|
19
27
|
// State
|
|
20
|
-
const [treeData, setTreeData] =
|
|
28
|
+
const [treeData, setTreeData] =
|
|
29
|
+
useState<ParameterTreeNode[]>(initialTreeData);
|
|
21
30
|
const [selectedConfig, setSelectedConfig] = useState<string | null>(null);
|
|
22
|
-
const [configValue, setConfigValue] = useState<
|
|
31
|
+
const [configValue, setConfigValue] = useState<ParameterValue | null>(null);
|
|
23
32
|
const [history, setHistory] = useState<Parameter[]>([]);
|
|
24
|
-
const [loading, setLoading] = useState(true);
|
|
25
33
|
const [loadingConfig, setLoadingConfig] = useState(false);
|
|
26
34
|
const [loadingHistory, setLoadingHistory] = useState(false);
|
|
35
|
+
const [saving, setSaving] = useState(false);
|
|
27
36
|
|
|
28
|
-
//
|
|
29
|
-
const
|
|
37
|
+
// Refresh tree data
|
|
38
|
+
const handleRefresh = useCallback(async () => {
|
|
30
39
|
try {
|
|
31
|
-
const tree = await client.
|
|
32
|
-
setTreeData(tree as
|
|
33
|
-
}
|
|
34
|
-
|
|
40
|
+
const tree = await client.getParameterTree({});
|
|
41
|
+
setTreeData(tree as ParameterTreeNode[]);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
toast.danger({
|
|
44
|
+
title: "Failed to refresh parameters",
|
|
45
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
46
|
+
});
|
|
35
47
|
}
|
|
36
|
-
}, []);
|
|
48
|
+
}, [client, toast]);
|
|
37
49
|
|
|
38
50
|
// Load config value and history when selection changes
|
|
39
|
-
const loadConfigDetails = useCallback(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
const loadConfigDetails = useCallback(
|
|
52
|
+
async (name: string) => {
|
|
53
|
+
setLoadingConfig(true);
|
|
54
|
+
setLoadingHistory(true);
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const [currentResponse, historyResponse] = await Promise.all([
|
|
58
|
+
client.getCurrent({ params: { name } }),
|
|
59
|
+
client.getHistory({ params: { name } }),
|
|
60
|
+
]);
|
|
61
|
+
setConfigValue(currentResponse);
|
|
62
|
+
setHistory(historyResponse.versions);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
toast.danger({
|
|
65
|
+
title: "Failed to load configuration",
|
|
66
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
67
|
+
});
|
|
68
|
+
setConfigValue(null);
|
|
69
|
+
setHistory([]);
|
|
70
|
+
} finally {
|
|
71
|
+
setLoadingConfig(false);
|
|
72
|
+
setLoadingHistory(false);
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
[client, toast],
|
|
76
|
+
);
|
|
54
77
|
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
78
|
+
// Handle save
|
|
79
|
+
const handleSave = useCallback(
|
|
80
|
+
async (values: Record<string, unknown>) => {
|
|
81
|
+
if (!selectedConfig || !configValue) return;
|
|
82
|
+
|
|
83
|
+
setSaving(true);
|
|
84
|
+
try {
|
|
85
|
+
await client.createVersion({
|
|
86
|
+
params: { name: selectedConfig },
|
|
87
|
+
body: {
|
|
88
|
+
content: values,
|
|
89
|
+
schemaHash: "", // Schema hash is computed server-side when empty
|
|
90
|
+
changeDescription: "Updated via admin UI",
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
toast.success({
|
|
95
|
+
title: "Configuration saved",
|
|
96
|
+
message: `${selectedConfig} has been updated`,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Reload details
|
|
100
|
+
await loadConfigDetails(selectedConfig);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
toast.danger({
|
|
103
|
+
title: "Failed to save configuration",
|
|
104
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
105
|
+
});
|
|
106
|
+
throw error;
|
|
107
|
+
} finally {
|
|
108
|
+
setSaving(false);
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
[client, selectedConfig, configValue, loadConfigDetails, toast],
|
|
112
|
+
);
|
|
59
113
|
|
|
60
114
|
// Load details when selection changes
|
|
61
115
|
useEffect(() => {
|
|
@@ -68,25 +122,32 @@ const AdminParameters = () => {
|
|
|
68
122
|
}, [selectedConfig, loadConfigDetails]);
|
|
69
123
|
|
|
70
124
|
// Handle rollback
|
|
71
|
-
const handleRollback =
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
125
|
+
const handleRollback = useCallback(
|
|
126
|
+
async (version: number) => {
|
|
127
|
+
if (!selectedConfig) return;
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
await client.rollback({
|
|
131
|
+
params: { name: selectedConfig },
|
|
132
|
+
body: { targetVersion: version },
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
toast.success({
|
|
136
|
+
title: "Rollback successful",
|
|
137
|
+
message: `${selectedConfig} rolled back to version ${version}`,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Reload details
|
|
141
|
+
await loadConfigDetails(selectedConfig);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
toast.danger({
|
|
144
|
+
title: "Rollback failed",
|
|
145
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
[client, selectedConfig, loadConfigDetails, toast],
|
|
150
|
+
);
|
|
90
151
|
|
|
91
152
|
// Empty state when no configs exist
|
|
92
153
|
if (treeData.length === 0) {
|
|
@@ -100,7 +161,7 @@ const AdminParameters = () => {
|
|
|
100
161
|
/>
|
|
101
162
|
<Text c="dimmed">No Parameters Found</Text>
|
|
102
163
|
<Text size="xs" c="dimmed" ta="center" maw={400}>
|
|
103
|
-
Define parameters using the $
|
|
164
|
+
Define parameters using the $parameter primitive to manage dynamic
|
|
104
165
|
application settings. Parameters will appear here once created.
|
|
105
166
|
</Text>
|
|
106
167
|
</Stack>
|
|
@@ -109,26 +170,43 @@ const AdminParameters = () => {
|
|
|
109
170
|
}
|
|
110
171
|
|
|
111
172
|
return (
|
|
112
|
-
<Flex flex={1}
|
|
113
|
-
<
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
173
|
+
<Flex flex={1} p="md">
|
|
174
|
+
<Card
|
|
175
|
+
withBorder
|
|
176
|
+
p={0}
|
|
177
|
+
w={"100%"}
|
|
178
|
+
style={{
|
|
179
|
+
flexDirection: "row",
|
|
180
|
+
}}
|
|
181
|
+
>
|
|
182
|
+
<ParameterTree
|
|
183
|
+
treeData={treeData}
|
|
184
|
+
selectedConfig={selectedConfig}
|
|
185
|
+
onSelect={setSelectedConfig}
|
|
186
|
+
onRefresh={handleRefresh}
|
|
187
|
+
/>
|
|
188
|
+
|
|
189
|
+
{selectedConfig ? (
|
|
190
|
+
<>
|
|
191
|
+
<ParameterDetails
|
|
192
|
+
selectedConfig={selectedConfig}
|
|
193
|
+
configValue={configValue}
|
|
194
|
+
loading={loadingConfig}
|
|
195
|
+
saving={saving}
|
|
196
|
+
onSave={handleSave}
|
|
197
|
+
/>
|
|
198
|
+
|
|
199
|
+
<ParameterHistory
|
|
200
|
+
selectedConfig={selectedConfig}
|
|
201
|
+
history={history}
|
|
202
|
+
loading={loadingHistory}
|
|
203
|
+
onRollback={handleRollback}
|
|
204
|
+
/>
|
|
205
|
+
</>
|
|
206
|
+
) : (
|
|
207
|
+
<ParameterEmptyState />
|
|
208
|
+
)}
|
|
209
|
+
</Card>
|
|
132
210
|
</Flex>
|
|
133
211
|
);
|
|
134
212
|
};
|
|
@@ -1,32 +1,56 @@
|
|
|
1
|
-
import { Flex, Text, TypeForm } from "@alepha/ui";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Card,
|
|
6
|
-
Code,
|
|
7
|
-
Group,
|
|
8
|
-
Loader,
|
|
9
|
-
ScrollArea,
|
|
10
|
-
Stack,
|
|
11
|
-
} from "@mantine/core";
|
|
12
|
-
import { IconClock, IconSettings } from "@tabler/icons-react";
|
|
13
|
-
import { jsonSchemaToTypeBox, type TObject } from "alepha";
|
|
1
|
+
import { ActionButton, Flex, Text, TypeForm } from "@alepha/ui";
|
|
2
|
+
import { Box, Card, Code, Group, Loader, Stack } from "@mantine/core";
|
|
3
|
+
import { IconClock } from "@tabler/icons-react";
|
|
4
|
+
import { jsonSchemaToTypeBox, type TObject, t } from "alepha";
|
|
14
5
|
import { useForm } from "alepha/react/form";
|
|
15
6
|
import { useI18n } from "alepha/react/i18n";
|
|
16
7
|
import { useMemo } from "react";
|
|
17
|
-
import { type
|
|
8
|
+
import { formatJson, type ParameterValue } from "./types.ts";
|
|
18
9
|
|
|
19
10
|
export interface ParameterDetailsProps {
|
|
20
11
|
selectedConfig: string | null;
|
|
21
|
-
configValue:
|
|
12
|
+
configValue: ParameterValue | null;
|
|
22
13
|
loading: boolean;
|
|
14
|
+
saving: boolean;
|
|
15
|
+
onSave: (values: Record<string, unknown>) => Promise<void>;
|
|
23
16
|
}
|
|
24
17
|
|
|
25
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Loading state.
|
|
20
|
+
*/
|
|
21
|
+
const LoadingState = () => (
|
|
22
|
+
<Box
|
|
23
|
+
flex={1}
|
|
24
|
+
h="100%"
|
|
25
|
+
p="md"
|
|
26
|
+
style={{
|
|
27
|
+
overflow: "hidden",
|
|
28
|
+
minWidth: 0,
|
|
29
|
+
display: "flex",
|
|
30
|
+
}}
|
|
31
|
+
>
|
|
32
|
+
<Flex flex={1} justify="center" align="center" h="100%">
|
|
33
|
+
<Loader size="sm" />
|
|
34
|
+
</Flex>
|
|
35
|
+
</Box>
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
interface ConfigFormProps {
|
|
39
|
+
selectedConfig: string;
|
|
40
|
+
configValue: ParameterValue | null;
|
|
41
|
+
saving: boolean;
|
|
42
|
+
onSave: (values: Record<string, unknown>) => Promise<void>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The actual form component - only rendered when a config is selected.
|
|
47
|
+
*/
|
|
48
|
+
const ConfigForm = ({
|
|
26
49
|
selectedConfig,
|
|
27
50
|
configValue,
|
|
28
|
-
|
|
29
|
-
|
|
51
|
+
saving,
|
|
52
|
+
onSave,
|
|
53
|
+
}: ConfigFormProps) => {
|
|
30
54
|
const { l } = useI18n();
|
|
31
55
|
|
|
32
56
|
// Get the current value to display (from saved version or default)
|
|
@@ -43,17 +67,21 @@ const ParameterDetails = ({
|
|
|
43
67
|
// Convert JSON Schema from API to TypeBox schema
|
|
44
68
|
const schemaForForm = useMemo(() => {
|
|
45
69
|
if (!configValue?.schema) {
|
|
46
|
-
return
|
|
70
|
+
return t.object({});
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
return jsonSchemaToTypeBox(configValue.schema) as TObject;
|
|
74
|
+
} catch {
|
|
75
|
+
return t.object({});
|
|
47
76
|
}
|
|
48
|
-
return jsonSchemaToTypeBox(configValue.schema) as TObject;
|
|
49
77
|
}, [configValue?.schema]);
|
|
50
78
|
|
|
51
79
|
const form = useForm(
|
|
52
80
|
{
|
|
53
81
|
schema: schemaForForm,
|
|
54
82
|
initialValues: (currentContent ?? {}) as Record<string, unknown>,
|
|
55
|
-
handler: async () => {
|
|
56
|
-
|
|
83
|
+
handler: async (values) => {
|
|
84
|
+
await onSave(values as Record<string, unknown>);
|
|
57
85
|
},
|
|
58
86
|
},
|
|
59
87
|
[selectedConfig, schemaForForm, currentContent],
|
|
@@ -66,89 +94,68 @@ const ParameterDetails = ({
|
|
|
66
94
|
schema &&
|
|
67
95
|
typeof schema === "object" &&
|
|
68
96
|
"properties" in schema &&
|
|
69
|
-
Object.keys(schema.properties
|
|
97
|
+
Object.keys(schema.properties as object).length > 0
|
|
70
98
|
);
|
|
71
99
|
}, [configValue?.schema]);
|
|
72
100
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
</Stack>
|
|
87
|
-
</Flex>
|
|
88
|
-
</Card>
|
|
89
|
-
);
|
|
90
|
-
}
|
|
101
|
+
// Count the number of fields to determine column layout
|
|
102
|
+
const fieldCount = useMemo(() => {
|
|
103
|
+
const schema = configValue?.schema;
|
|
104
|
+
if (
|
|
105
|
+
schema &&
|
|
106
|
+
typeof schema === "object" &&
|
|
107
|
+
"properties" in schema &&
|
|
108
|
+
schema.properties
|
|
109
|
+
) {
|
|
110
|
+
return Object.keys(schema.properties as object).length;
|
|
111
|
+
}
|
|
112
|
+
return 0;
|
|
113
|
+
}, [configValue?.schema]);
|
|
91
114
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
</Card>
|
|
99
|
-
);
|
|
100
|
-
}
|
|
115
|
+
// Determine optimal column count based on field count
|
|
116
|
+
const columns = useMemo(() => {
|
|
117
|
+
if (fieldCount <= 2) return 1;
|
|
118
|
+
if (fieldCount <= 6) return 2;
|
|
119
|
+
return 3;
|
|
120
|
+
}, [fieldCount]);
|
|
101
121
|
|
|
102
122
|
return (
|
|
103
|
-
<
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
{configValue.next && (
|
|
116
|
-
<Badge size="xs" color="blue" variant="light">
|
|
117
|
-
Next: v{configValue.next.version}
|
|
118
|
-
</Badge>
|
|
119
|
-
)}
|
|
120
|
-
</Group>
|
|
121
|
-
)}
|
|
122
|
-
{!configValue?.current &&
|
|
123
|
-
configValue?.currentValue !== undefined && (
|
|
124
|
-
<Badge size="xs" color="yellow" variant="light">
|
|
125
|
-
Default
|
|
126
|
-
</Badge>
|
|
127
|
-
)}
|
|
128
|
-
</Stack>
|
|
129
|
-
</Group>
|
|
130
|
-
|
|
131
|
-
<ScrollArea flex={1} offsetScrollbars>
|
|
123
|
+
<Box
|
|
124
|
+
flex={1}
|
|
125
|
+
h="100%"
|
|
126
|
+
style={{
|
|
127
|
+
overflow: "hidden",
|
|
128
|
+
minWidth: 0,
|
|
129
|
+
display: "flex",
|
|
130
|
+
}}
|
|
131
|
+
>
|
|
132
|
+
<Flex direction="column" h="100%" w="100%" style={{ minHeight: 0 }}>
|
|
133
|
+
{/* Content */}
|
|
134
|
+
<Box flex={1} p="md" className="overflow-auto" style={{ minHeight: 0 }}>
|
|
132
135
|
{currentContent !== null ? (
|
|
133
|
-
<Stack gap="
|
|
136
|
+
<Stack gap="lg">
|
|
137
|
+
{/* Form or JSON view */}
|
|
134
138
|
<Box>
|
|
135
|
-
<Text size="xs" c="dimmed" mb={4}>
|
|
136
|
-
Current Value
|
|
137
|
-
</Text>
|
|
138
139
|
{hasValidSchema ? (
|
|
139
140
|
<TypeForm
|
|
140
141
|
form={form}
|
|
141
|
-
columns={
|
|
142
|
+
columns={columns}
|
|
142
143
|
skipSubmitButton
|
|
143
|
-
|
|
144
|
+
fill={false}
|
|
144
145
|
/>
|
|
145
146
|
) : (
|
|
146
|
-
<
|
|
147
|
-
{
|
|
148
|
-
|
|
147
|
+
<Box>
|
|
148
|
+
<Text size="xs" c="dimmed" mb={4}>
|
|
149
|
+
Current Value
|
|
150
|
+
</Text>
|
|
151
|
+
<Code block style={{ whiteSpace: "pre-wrap" }}>
|
|
152
|
+
{formatJson(currentContent)}
|
|
153
|
+
</Code>
|
|
154
|
+
</Box>
|
|
149
155
|
)}
|
|
150
156
|
</Box>
|
|
151
157
|
|
|
158
|
+
{/* Metadata */}
|
|
152
159
|
{configValue?.current?.changeDescription && (
|
|
153
160
|
<Box>
|
|
154
161
|
<Text size="xs" c="dimmed" mb={4}>
|
|
@@ -187,15 +194,16 @@ const ParameterDetails = ({
|
|
|
187
194
|
</Text>
|
|
188
195
|
)}
|
|
189
196
|
|
|
197
|
+
{/* Scheduled update preview */}
|
|
190
198
|
{configValue?.next && (
|
|
191
|
-
<Card withBorder
|
|
199
|
+
<Card withBorder p="sm" bg="var(--mantine-color-blue-light)">
|
|
192
200
|
<Stack gap="xs">
|
|
193
201
|
<Group gap="xs">
|
|
194
202
|
<IconClock
|
|
195
203
|
size={14}
|
|
196
204
|
color="var(--mantine-color-blue-6)"
|
|
197
205
|
/>
|
|
198
|
-
<Text size="xs" fw={500} c="blue
|
|
206
|
+
<Text size="xs" fw={500} c="blue">
|
|
199
207
|
Scheduled Update (v{configValue.next.version})
|
|
200
208
|
</Text>
|
|
201
209
|
</Group>
|
|
@@ -219,9 +227,61 @@ const ParameterDetails = ({
|
|
|
219
227
|
</Text>
|
|
220
228
|
</Flex>
|
|
221
229
|
)}
|
|
222
|
-
</
|
|
223
|
-
|
|
224
|
-
|
|
230
|
+
</Box>
|
|
231
|
+
|
|
232
|
+
{/* Footer with actions */}
|
|
233
|
+
{hasValidSchema && currentContent !== null && (
|
|
234
|
+
<Box
|
|
235
|
+
p="md"
|
|
236
|
+
style={{
|
|
237
|
+
flexShrink: 0,
|
|
238
|
+
borderTop: "1px solid var(--mantine-color-default-border)",
|
|
239
|
+
}}
|
|
240
|
+
>
|
|
241
|
+
<Group justify="flex-end" gap="sm">
|
|
242
|
+
<ActionButton
|
|
243
|
+
variant="subtle"
|
|
244
|
+
onClick={() => form.reset({} as any)}
|
|
245
|
+
disabled={saving}
|
|
246
|
+
>
|
|
247
|
+
Reset
|
|
248
|
+
</ActionButton>
|
|
249
|
+
<ActionButton intent="primary" form={form} loading={saving}>
|
|
250
|
+
Save Changes
|
|
251
|
+
</ActionButton>
|
|
252
|
+
</Group>
|
|
253
|
+
</Box>
|
|
254
|
+
)}
|
|
255
|
+
</Flex>
|
|
256
|
+
</Box>
|
|
257
|
+
);
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Parameter details panel.
|
|
262
|
+
* Shows loading state or the config form.
|
|
263
|
+
* Note: Empty state is handled by parent (AdminParameters).
|
|
264
|
+
*/
|
|
265
|
+
const ParameterDetails = ({
|
|
266
|
+
selectedConfig,
|
|
267
|
+
configValue,
|
|
268
|
+
loading,
|
|
269
|
+
saving,
|
|
270
|
+
onSave,
|
|
271
|
+
}: ParameterDetailsProps) => {
|
|
272
|
+
// Loading state
|
|
273
|
+
if (loading) {
|
|
274
|
+
return <LoadingState />;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Config form (selectedConfig is guaranteed to be non-null by parent)
|
|
278
|
+
return (
|
|
279
|
+
<ConfigForm
|
|
280
|
+
selectedConfig={selectedConfig!}
|
|
281
|
+
configValue={configValue}
|
|
282
|
+
saving={saving}
|
|
283
|
+
onSave={onSave}
|
|
284
|
+
/>
|
|
225
285
|
);
|
|
226
286
|
};
|
|
227
287
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Text } from "@alepha/ui";
|
|
2
|
+
import { Flex, Stack } from "@mantine/core";
|
|
3
|
+
import { IconArrowLeft } from "@tabler/icons-react";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Empty state displayed when no parameter is selected.
|
|
7
|
+
* Invites user to select a parameter from the tree.
|
|
8
|
+
*/
|
|
9
|
+
const ParameterEmptyState = () => {
|
|
10
|
+
return (
|
|
11
|
+
<Flex flex={1} p={"xl"} align="center">
|
|
12
|
+
<Stack align="center" gap="md">
|
|
13
|
+
<IconArrowLeft size={32} color="var(--mantine-color-dimmed)" />
|
|
14
|
+
<Stack align="center" gap={4}>
|
|
15
|
+
<Text fw={500} c="dimmed">
|
|
16
|
+
No Parameter Selected
|
|
17
|
+
</Text>
|
|
18
|
+
<Text size="xs" c="dimmed" ta="center" maw={240}>
|
|
19
|
+
Choose a parameter from the tree to view and edit its configuration
|
|
20
|
+
</Text>
|
|
21
|
+
</Stack>
|
|
22
|
+
</Stack>
|
|
23
|
+
</Flex>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export default ParameterEmptyState;
|