@alepha/ui 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.
Files changed (157) hide show
  1. package/dist/admin/AdminAudits-CwvH8e8c.js +215 -0
  2. package/dist/admin/AdminAudits-CwvH8e8c.js.map +1 -0
  3. package/dist/admin/AdminAudits-Dv8Vk_6r.js +3 -0
  4. package/dist/admin/AdminFiles-5CPA3lQk.js +3 -0
  5. package/dist/admin/{AdminFiles-B_jfB_Py.js → AdminFiles-C_w1tb_x.js} +4 -3
  6. package/dist/admin/AdminFiles-C_w1tb_x.js.map +1 -0
  7. package/dist/admin/AdminLayout-BnSmtA4x.js +3 -0
  8. package/dist/admin/AdminLayout-XiSivwWH.js +39 -0
  9. package/dist/admin/AdminLayout-XiSivwWH.js.map +1 -0
  10. package/dist/admin/AdminNotifications-DLjmZWtf.js +3 -0
  11. package/dist/admin/{AdminNotifications-BFEjqpqx.js → AdminNotifications-DuYy74AN.js} +3 -3
  12. package/dist/admin/AdminNotifications-DuYy74AN.js.map +1 -0
  13. package/dist/admin/AdminParameters-DYg48Jwe.js +3 -0
  14. package/dist/admin/AdminParameters-YagqWTG3.js +575 -0
  15. package/dist/admin/AdminParameters-YagqWTG3.js.map +1 -0
  16. package/dist/admin/{AdminSessions-D7DESfWK.js → AdminSessions-BCjgJ-93.js} +4 -4
  17. package/dist/admin/AdminSessions-BCjgJ-93.js.map +1 -0
  18. package/dist/admin/AdminSessions-DEh2uN-4.js +3 -0
  19. package/dist/admin/AdminUserAudits-B_PUXCKC.js +177 -0
  20. package/dist/admin/AdminUserAudits-B_PUXCKC.js.map +1 -0
  21. package/dist/admin/AdminUserAudits-D7cTcElL.js +3 -0
  22. package/dist/admin/{AdminUserCreate-Bhxsn92l.js → AdminUserCreate-DzfRbGZ4.js} +4 -4
  23. package/dist/admin/AdminUserCreate-DzfRbGZ4.js.map +1 -0
  24. package/dist/admin/{AdminUserCreate-CYI_xW5T.js → AdminUserCreate-oUA1KDIl.js} +1 -1
  25. package/dist/admin/{AdminUserDetails-C2y1Ig4n.js → AdminUserDetails-DeTrJm-t.js} +5 -5
  26. package/dist/admin/AdminUserDetails-DeTrJm-t.js.map +1 -0
  27. package/dist/admin/{AdminUserDetails-Cmzx9HxH.js → AdminUserDetails-y1H5DW8Y.js} +1 -1
  28. package/dist/admin/{AdminUserLayout-sW6cjZL0.js → AdminUserLayout-CsfrrZkD.js} +4 -7
  29. package/dist/admin/AdminUserLayout-CsfrrZkD.js.map +1 -0
  30. package/dist/admin/{AdminUserLayout-DGSf612u.js → AdminUserLayout-Dejnz13m.js} +1 -1
  31. package/dist/admin/AdminUserSessions-Bbhcpz4k.js +3 -0
  32. package/dist/admin/{AdminUserSessions-CvN15wPe.js → AdminUserSessions-DO9H85O-.js} +4 -4
  33. package/dist/admin/AdminUserSessions-DO9H85O-.js.map +1 -0
  34. package/dist/admin/{AdminUserSettings-DvaaxgcV.js → AdminUserSettings-B3jA8g3p.js} +4 -4
  35. package/dist/admin/AdminUserSettings-B3jA8g3p.js.map +1 -0
  36. package/dist/admin/AdminUserSettings-CE0xpbQc.js +3 -0
  37. package/dist/admin/AdminUsers-CegGZDhW.js +3 -0
  38. package/dist/admin/{AdminUsers-BR3C-jrg.js → AdminUsers-ebbrJBT0.js} +13 -17
  39. package/dist/admin/AdminUsers-ebbrJBT0.js.map +1 -0
  40. package/dist/admin/index.d.ts +2044 -1044
  41. package/dist/admin/index.js +65 -62
  42. package/dist/admin/index.js.map +1 -1
  43. package/dist/auth/AuthLayout-BAZJHzDG.js +23 -0
  44. package/dist/auth/AuthLayout-BAZJHzDG.js.map +1 -0
  45. package/dist/auth/{Login-7HlBjDeV.js → Login-CeNZZjrr.js} +80 -44
  46. package/dist/auth/Login-CeNZZjrr.js.map +1 -0
  47. package/dist/auth/Login-hQcu1nlu.js +4 -0
  48. package/dist/auth/Register-B6HBNVHS.js +4 -0
  49. package/dist/auth/{Register-CuQr3kgi.js → Register-s4ENeyiE.js} +131 -91
  50. package/dist/auth/Register-s4ENeyiE.js.map +1 -0
  51. package/dist/auth/ResetPassword-Cjd-W-Nu.js +3 -0
  52. package/dist/auth/ResetPassword-GLIFkJT7.js +278 -0
  53. package/dist/auth/ResetPassword-GLIFkJT7.js.map +1 -0
  54. package/dist/auth/index.d.ts +471 -426
  55. package/dist/auth/index.js +26 -18
  56. package/dist/auth/index.js.map +1 -1
  57. package/dist/core/index.d.ts +400 -130
  58. package/dist/core/index.js +1751 -1369
  59. package/dist/core/index.js.map +1 -1
  60. package/package.json +15 -11
  61. package/src/admin/AdminRouter.ts +70 -16
  62. package/src/admin/components/AdminLayout.tsx +41 -61
  63. package/src/admin/components/audits/AdminAudits.tsx +240 -0
  64. package/src/admin/components/{AdminFiles.tsx → files/AdminFiles.tsx} +1 -1
  65. package/src/admin/components/{AdminJobs.tsx → jobs/AdminJobs.tsx} +1 -1
  66. package/src/admin/components/parameters/AdminParameters.tsx +137 -0
  67. package/src/admin/components/parameters/ParameterDetails.tsx +228 -0
  68. package/src/admin/components/parameters/ParameterHistory.tsx +146 -0
  69. package/src/admin/components/parameters/ParameterTree.tsx +146 -0
  70. package/src/admin/components/parameters/types.ts +35 -0
  71. package/src/admin/components/{AdminSessions.tsx → sessions/AdminSessions.tsx} +1 -1
  72. package/src/admin/components/users/AdminUserAudits.tsx +183 -0
  73. package/src/admin/components/{AdminUserCreate.tsx → users/AdminUserCreate.tsx} +1 -1
  74. package/src/admin/components/{AdminUserLayout.tsx → users/AdminUserLayout.tsx} +1 -4
  75. package/src/admin/components/{AdminUserSettings.tsx → users/AdminUserSettings.tsx} +1 -1
  76. package/src/admin/components/{AdminUsers.tsx → users/AdminUsers.tsx} +10 -12
  77. package/src/admin/index.ts +24 -16
  78. package/src/auth/AuthRouter.ts +23 -17
  79. package/src/auth/components/AuthLayout.tsx +6 -3
  80. package/src/auth/components/Login.tsx +109 -47
  81. package/src/auth/components/Register.tsx +158 -94
  82. package/src/auth/components/ResetPassword.tsx +51 -5
  83. package/src/auth/components/buttons/UserButton.tsx +2 -0
  84. package/src/core/atoms/alephaThemeAtom.ts +13 -0
  85. package/src/core/atoms/alephaThemeListAtom.ts +10 -0
  86. package/src/core/atoms/themes/default.ts +6 -0
  87. package/src/core/{themes → atoms/themes}/midnight.ts +3 -5
  88. package/src/core/components/buttons/ActionButton.tsx +33 -26
  89. package/src/core/components/buttons/DarkModeButton.tsx +0 -1
  90. package/src/core/components/buttons/ThemeButton.tsx +10 -7
  91. package/src/core/components/buttons/ToggleSidebarButton.tsx +19 -16
  92. package/src/core/components/data/ErrorViewer.tsx +171 -0
  93. package/src/core/components/data/JsonViewer.tsx +147 -138
  94. package/src/core/components/form/Control.tsx +95 -18
  95. package/src/core/components/form/ControlArray.tsx +377 -0
  96. package/src/core/components/form/ControlObject.tsx +127 -0
  97. package/src/core/components/form/TypeForm.tsx +99 -37
  98. package/src/core/components/layout/AdminShell.tsx +14 -1
  99. package/src/core/components/layout/AlephaMantineProvider.tsx +7 -3
  100. package/src/core/components/layout/Omnibar.tsx +1 -1
  101. package/src/core/components/layout/Sidebar.tsx +47 -14
  102. package/src/core/components/table/ColumnPicker.tsx +126 -0
  103. package/src/core/components/table/DataTable.tsx +354 -181
  104. package/src/core/components/table/DataTableFilters.tsx +64 -0
  105. package/src/core/components/table/DataTablePagination.tsx +59 -0
  106. package/src/core/components/table/DataTableToolbar.tsx +126 -0
  107. package/src/core/components/table/FilterPicker.tsx +138 -0
  108. package/src/core/components/table/types.ts +199 -0
  109. package/src/core/helpers/isComponentType.ts +9 -0
  110. package/src/core/helpers/renderIcon.tsx +13 -0
  111. package/src/core/hooks/useTheme.ts +24 -18
  112. package/src/core/index.ts +24 -3
  113. package/src/core/interfaces/AlephaTheme.ts +8 -0
  114. package/src/core/providers/ThemeProvider.ts +44 -62
  115. package/src/core/services/DialogService.tsx +24 -0
  116. package/src/core/utils/parseInput.ts +2 -2
  117. package/styles.css +1 -1
  118. package/dist/admin/AdminFiles-B-0UcHVV.js +0 -3
  119. package/dist/admin/AdminFiles-B_jfB_Py.js.map +0 -1
  120. package/dist/admin/AdminLayout-BMtiXAzS.js +0 -396
  121. package/dist/admin/AdminLayout-BMtiXAzS.js.map +0 -1
  122. package/dist/admin/AdminLayout-BNo3GoHR.js +0 -3
  123. package/dist/admin/AdminNotifications-BFEjqpqx.js.map +0 -1
  124. package/dist/admin/AdminNotifications-DJs2ZjNj.js +0 -3
  125. package/dist/admin/AdminSessions-D7DESfWK.js.map +0 -1
  126. package/dist/admin/AdminSessions-PS2M8iXi.js +0 -3
  127. package/dist/admin/AdminUserCreate-Bhxsn92l.js.map +0 -1
  128. package/dist/admin/AdminUserDetails-C2y1Ig4n.js.map +0 -1
  129. package/dist/admin/AdminUserLayout-sW6cjZL0.js.map +0 -1
  130. package/dist/admin/AdminUserSessions-CvN15wPe.js.map +0 -1
  131. package/dist/admin/AdminUserSessions-D-aOcZgV.js +0 -3
  132. package/dist/admin/AdminUserSettings-CEMhIYrI.js +0 -3
  133. package/dist/admin/AdminUserSettings-DvaaxgcV.js.map +0 -1
  134. package/dist/admin/AdminUsers-BR3C-jrg.js.map +0 -1
  135. package/dist/admin/AdminUsers-CMW9vN09.js +0 -3
  136. package/dist/auth/AuthLayout-CzwUKD9y.js +0 -19
  137. package/dist/auth/AuthLayout-CzwUKD9y.js.map +0 -1
  138. package/dist/auth/Login-7HlBjDeV.js.map +0 -1
  139. package/dist/auth/Login-C-e27DGb.js +0 -4
  140. package/dist/auth/Register-CuQr3kgi.js.map +0 -1
  141. package/dist/auth/Register-DbvXwgbG.js +0 -4
  142. package/dist/auth/ResetPassword-BzU-cdd4.js +0 -243
  143. package/dist/auth/ResetPassword-BzU-cdd4.js.map +0 -1
  144. package/dist/auth/ResetPassword-DSvrdpaA.js +0 -3
  145. package/src/admin/AdminSidebar.ts +0 -31
  146. package/src/admin/components/AdminParameters.tsx +0 -24
  147. package/src/core/themes/aurora.ts +0 -107
  148. package/src/core/themes/crystal.ts +0 -107
  149. package/src/core/themes/default.ts +0 -7
  150. package/src/core/themes/ember.ts +0 -107
  151. package/src/core/themes/index.ts +0 -7
  152. package/src/core/themes/remoraid.ts +0 -278
  153. package/src/core/themes/slate.ts +0 -81
  154. /package/src/admin/components/{AdminNotifications.tsx → notifications/AdminNotifications.tsx} +0 -0
  155. /package/src/admin/components/{AdminUserDetails.tsx → users/AdminUserDetails.tsx} +0 -0
  156. /package/src/admin/components/{AdminUserSessions.tsx → users/AdminUserSessions.tsx} +0 -0
  157. /package/src/admin/components/{AdminVerifications.tsx → verifications/AdminVerifications.tsx} +0 -0
@@ -0,0 +1,137 @@
1
+ import { useClient } from "@alepha/react";
2
+ import { Flex, Text } from "@alepha/ui";
3
+ import { Loader, Stack } from "@mantine/core";
4
+ import { IconSettings } from "@tabler/icons-react";
5
+ import type {
6
+ ConfigController,
7
+ ConfigTreeNode,
8
+ Parameter,
9
+ } from "alepha/api/parameters";
10
+ import { useCallback, useEffect, useState } from "react";
11
+ import ParameterDetails from "./ParameterDetails.tsx";
12
+ import ParameterHistory from "./ParameterHistory.tsx";
13
+ import ParameterTree from "./ParameterTree.tsx";
14
+ import type { ConfigValue } from "./types.ts";
15
+
16
+ const AdminParameters = () => {
17
+ const client = useClient<ConfigController>();
18
+
19
+ // State
20
+ const [treeData, setTreeData] = useState<ConfigTreeNode[]>([]);
21
+ const [selectedConfig, setSelectedConfig] = useState<string | null>(null);
22
+ const [configValue, setConfigValue] = useState<ConfigValue | null>(null);
23
+ const [history, setHistory] = useState<Parameter[]>([]);
24
+ const [loading, setLoading] = useState(true);
25
+ const [loadingConfig, setLoadingConfig] = useState(false);
26
+ const [loadingHistory, setLoadingHistory] = useState(false);
27
+
28
+ // Load tree data
29
+ const loadTree = useCallback(async () => {
30
+ try {
31
+ const tree = await client.getConfigTree({});
32
+ setTreeData(tree as ConfigTreeNode[]);
33
+ } finally {
34
+ setLoading(false);
35
+ }
36
+ }, []);
37
+
38
+ // Load config value and history when selection changes
39
+ const loadConfigDetails = useCallback(async (name: string) => {
40
+ setLoadingConfig(true);
41
+ setLoadingHistory(true);
42
+
43
+ try {
44
+ const [current, historyData] = await Promise.all([
45
+ client.getCurrent({ params: { name } }),
46
+ client.getHistory({ params: { name } }),
47
+ ]);
48
+ setConfigValue(current);
49
+ setHistory(historyData.versions);
50
+ } finally {
51
+ setLoadingConfig(false);
52
+ setLoadingHistory(false);
53
+ }
54
+ }, []);
55
+
56
+ // Initial load
57
+ useEffect(() => {
58
+ loadTree();
59
+ }, [loadTree]);
60
+
61
+ // Load details when selection changes
62
+ useEffect(() => {
63
+ if (selectedConfig) {
64
+ loadConfigDetails(selectedConfig);
65
+ } else {
66
+ setConfigValue(null);
67
+ setHistory([]);
68
+ }
69
+ }, [selectedConfig, loadConfigDetails]);
70
+
71
+ // Handle rollback
72
+ const handleRollback = async (version: number) => {
73
+ if (!selectedConfig) return;
74
+
75
+ await client.rollback({
76
+ params: { name: selectedConfig },
77
+ body: { targetVersion: version },
78
+ });
79
+
80
+ // Reload details
81
+ await loadConfigDetails(selectedConfig);
82
+ };
83
+
84
+ if (loading) {
85
+ return (
86
+ <Flex flex={1} justify="center" align="center">
87
+ <Loader />
88
+ </Flex>
89
+ );
90
+ }
91
+
92
+ // Empty state when no configs exist
93
+ if (treeData.length === 0) {
94
+ return (
95
+ <Flex flex={1} justify="center" align="center">
96
+ <Stack align="center" gap="xs">
97
+ <IconSettings
98
+ size={48}
99
+ stroke={1.5}
100
+ color="var(--mantine-color-dimmed)"
101
+ />
102
+ <Text c="dimmed">No Parameters Found</Text>
103
+ <Text size="xs" c="dimmed" ta="center" maw={400}>
104
+ Define parameters using the $config primitive to manage dynamic
105
+ application settings. Parameters will appear here once created.
106
+ </Text>
107
+ </Stack>
108
+ </Flex>
109
+ );
110
+ }
111
+
112
+ return (
113
+ <Flex flex={1} gap={"xs"} h="100%">
114
+ <ParameterTree
115
+ treeData={treeData}
116
+ selectedConfig={selectedConfig}
117
+ onSelect={setSelectedConfig}
118
+ onRefresh={loadTree}
119
+ />
120
+
121
+ <ParameterDetails
122
+ selectedConfig={selectedConfig}
123
+ configValue={configValue}
124
+ loading={loadingConfig}
125
+ />
126
+
127
+ <ParameterHistory
128
+ selectedConfig={selectedConfig}
129
+ history={history}
130
+ loading={loadingHistory}
131
+ onRollback={handleRollback}
132
+ />
133
+ </Flex>
134
+ );
135
+ };
136
+
137
+ export default AdminParameters;
@@ -0,0 +1,228 @@
1
+ import { useForm } from "@alepha/react/form";
2
+ import { useI18n } from "@alepha/react/i18n";
3
+ import { Flex, Text, TypeForm } from "@alepha/ui";
4
+ import {
5
+ Badge,
6
+ Box,
7
+ Card,
8
+ Code,
9
+ Group,
10
+ Loader,
11
+ ScrollArea,
12
+ Stack,
13
+ } from "@mantine/core";
14
+ import { IconClock, IconSettings } from "@tabler/icons-react";
15
+ import { jsonSchemaToTypeBox, type TObject } from "alepha";
16
+ import { useMemo } from "react";
17
+ import { type ConfigValue, formatJson } from "./types.ts";
18
+
19
+ export interface ParameterDetailsProps {
20
+ selectedConfig: string | null;
21
+ configValue: ConfigValue | null;
22
+ loading: boolean;
23
+ }
24
+
25
+ const ParameterDetails = ({
26
+ selectedConfig,
27
+ configValue,
28
+ loading,
29
+ }: ParameterDetailsProps) => {
30
+ const { l } = useI18n();
31
+
32
+ // Get the current value to display (from saved version or default)
33
+ const currentContent = useMemo(() => {
34
+ if (configValue?.current?.content) {
35
+ return configValue.current.content;
36
+ }
37
+ if (configValue?.currentValue !== undefined) {
38
+ return configValue.currentValue;
39
+ }
40
+ return null;
41
+ }, [configValue]);
42
+
43
+ // Convert JSON Schema from API to TypeBox schema
44
+ const schemaForForm = useMemo(() => {
45
+ if (!configValue?.schema) {
46
+ return { type: "object", properties: {} } as unknown as TObject;
47
+ }
48
+ return jsonSchemaToTypeBox(configValue.schema) as TObject;
49
+ }, [configValue?.schema]);
50
+
51
+ const form = useForm(
52
+ {
53
+ schema: schemaForForm,
54
+ initialValues: (currentContent ?? {}) as Record<string, unknown>,
55
+ handler: async () => {
56
+ // Read-only for now
57
+ },
58
+ },
59
+ [selectedConfig, schemaForForm, currentContent],
60
+ );
61
+
62
+ // Check if we have a valid schema with properties
63
+ const hasValidSchema = useMemo(() => {
64
+ const schema = configValue?.schema;
65
+ return (
66
+ schema &&
67
+ typeof schema === "object" &&
68
+ "properties" in schema &&
69
+ Object.keys(schema.properties ?? {}).length > 0
70
+ );
71
+ }, [configValue?.schema]);
72
+
73
+ if (!selectedConfig) {
74
+ return (
75
+ <Card withBorder flex={1} h="100%" style={{ overflow: "hidden" }}>
76
+ <Flex flex={1} justify="center" align="center" h="100%">
77
+ <Stack align="center" gap="xs">
78
+ <IconSettings
79
+ size={32}
80
+ stroke={1.5}
81
+ color="var(--mantine-color-dimmed)"
82
+ />
83
+ <Text c="dimmed" size="sm">
84
+ Select a parameter to view its value
85
+ </Text>
86
+ </Stack>
87
+ </Flex>
88
+ </Card>
89
+ );
90
+ }
91
+
92
+ if (loading) {
93
+ return (
94
+ <Card withBorder flex={1} h="100%" style={{ overflow: "hidden" }}>
95
+ <Flex flex={1} justify="center" align="center" h="100%">
96
+ <Loader size="sm" />
97
+ </Flex>
98
+ </Card>
99
+ );
100
+ }
101
+
102
+ return (
103
+ <Card withBorder flex={1} h="100%" style={{ overflow: "hidden" }}>
104
+ <Stack gap="md" h="100%">
105
+ <Group justify="space-between">
106
+ <Stack gap={2}>
107
+ <Text size="sm" fw={500}>
108
+ {selectedConfig}
109
+ </Text>
110
+ {configValue?.current && (
111
+ <Group gap="xs">
112
+ <Badge size="xs" color="green" variant="light">
113
+ v{configValue.current.version}
114
+ </Badge>
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>
132
+ {currentContent !== null ? (
133
+ <Stack gap="md">
134
+ <Box>
135
+ <Text size="xs" c="dimmed" mb={4}>
136
+ Current Value
137
+ </Text>
138
+ {hasValidSchema ? (
139
+ <TypeForm
140
+ form={form}
141
+ columns={1}
142
+ skipSubmitButton
143
+ skipFormElement
144
+ />
145
+ ) : (
146
+ <Code block style={{ whiteSpace: "pre-wrap" }}>
147
+ {formatJson(currentContent)}
148
+ </Code>
149
+ )}
150
+ </Box>
151
+
152
+ {configValue?.current?.changeDescription && (
153
+ <Box>
154
+ <Text size="xs" c="dimmed" mb={4}>
155
+ Change Description
156
+ </Text>
157
+ <Text size="sm">{configValue.current.changeDescription}</Text>
158
+ </Box>
159
+ )}
160
+
161
+ {configValue?.current && (
162
+ <Group gap="xl">
163
+ <Box>
164
+ <Text size="xs" c="dimmed" mb={2}>
165
+ Updated
166
+ </Text>
167
+ <Text size="sm">
168
+ {l(configValue.current.updatedAt, { date: "fromNow" })}
169
+ </Text>
170
+ </Box>
171
+ {configValue.current.creatorName && (
172
+ <Box>
173
+ <Text size="xs" c="dimmed" mb={2}>
174
+ Updated By
175
+ </Text>
176
+ <Text size="sm">{configValue.current.creatorName}</Text>
177
+ </Box>
178
+ )}
179
+ </Group>
180
+ )}
181
+
182
+ {!configValue?.current &&
183
+ configValue?.currentValue !== undefined && (
184
+ <Text size="xs" c="dimmed">
185
+ This configuration is using its default value. No versions
186
+ have been saved to the database yet.
187
+ </Text>
188
+ )}
189
+
190
+ {configValue?.next && (
191
+ <Card withBorder bg="blue.0" p="sm">
192
+ <Stack gap="xs">
193
+ <Group gap="xs">
194
+ <IconClock
195
+ size={14}
196
+ color="var(--mantine-color-blue-6)"
197
+ />
198
+ <Text size="xs" fw={500} c="blue.7">
199
+ Scheduled Update (v{configValue.next.version})
200
+ </Text>
201
+ </Group>
202
+ <Text size="xs" c="dimmed">
203
+ Activates{" "}
204
+ {l(configValue.next.activationDate, {
205
+ date: "fromNow",
206
+ })}
207
+ </Text>
208
+ <Code block style={{ whiteSpace: "pre-wrap" }} fz="xs">
209
+ {formatJson(configValue.next.content)}
210
+ </Code>
211
+ </Stack>
212
+ </Card>
213
+ )}
214
+ </Stack>
215
+ ) : (
216
+ <Flex justify="center" align="center" h={200}>
217
+ <Text c="dimmed" size="sm">
218
+ No current value
219
+ </Text>
220
+ </Flex>
221
+ )}
222
+ </ScrollArea>
223
+ </Stack>
224
+ </Card>
225
+ );
226
+ };
227
+
228
+ export default ParameterDetails;
@@ -0,0 +1,146 @@
1
+ import { useI18n } from "@alepha/react/i18n";
2
+ import { ActionButton, Flex, Text } from "@alepha/ui";
3
+ import {
4
+ Badge,
5
+ Card,
6
+ Group,
7
+ Loader,
8
+ ScrollArea,
9
+ Stack,
10
+ Timeline,
11
+ } from "@mantine/core";
12
+ import { IconHistory } from "@tabler/icons-react";
13
+ import type { Parameter } from "alepha/api/parameters";
14
+ import { getStatusColor } from "./types.ts";
15
+
16
+ export interface ParameterHistoryProps {
17
+ selectedConfig: string | null;
18
+ history: Parameter[];
19
+ loading: boolean;
20
+ onRollback: (version: number) => void;
21
+ }
22
+
23
+ const ParameterHistory = ({
24
+ selectedConfig,
25
+ history,
26
+ loading,
27
+ onRollback,
28
+ }: ParameterHistoryProps) => {
29
+ const { l } = useI18n();
30
+
31
+ const renderContent = () => {
32
+ if (!selectedConfig) {
33
+ return (
34
+ <Flex flex={1} justify="center" align="center">
35
+ <Text c="dimmed" size="xs">
36
+ Select a parameter
37
+ </Text>
38
+ </Flex>
39
+ );
40
+ }
41
+
42
+ if (loading) {
43
+ return (
44
+ <Flex flex={1} justify="center" align="center">
45
+ <Loader size="sm" />
46
+ </Flex>
47
+ );
48
+ }
49
+
50
+ if (history.length === 0) {
51
+ return (
52
+ <Flex flex={1} justify="center" align="center">
53
+ <Text c="dimmed" size="xs">
54
+ No history
55
+ </Text>
56
+ </Flex>
57
+ );
58
+ }
59
+
60
+ return (
61
+ <ScrollArea flex={1} offsetScrollbars>
62
+ <Timeline
63
+ active={history.findIndex((h) => h.status === "current")}
64
+ bulletSize={24}
65
+ lineWidth={2}
66
+ >
67
+ {history.map((version) => (
68
+ <Timeline.Item
69
+ key={version.id}
70
+ bullet={
71
+ <Text size="xs" fw={500}>
72
+ {version.version}
73
+ </Text>
74
+ }
75
+ title={
76
+ <Group gap="xs">
77
+ <Text size="xs" fw={500}>
78
+ Version {version.version}
79
+ </Text>
80
+ <Badge
81
+ size="xs"
82
+ variant="light"
83
+ color={getStatusColor(version.status)}
84
+ >
85
+ {version.status}
86
+ </Badge>
87
+ </Group>
88
+ }
89
+ >
90
+ <Stack gap={4} mt={4}>
91
+ <Text size="xs" c="dimmed">
92
+ {l(version.createdAt, { date: "fromNow" })}
93
+ </Text>
94
+ {version.changeDescription && (
95
+ <Text size="xs" lineClamp={2}>
96
+ {version.changeDescription}
97
+ </Text>
98
+ )}
99
+ {version.creatorName && (
100
+ <Text size="xs" c="dimmed">
101
+ by {version.creatorName}
102
+ </Text>
103
+ )}
104
+ {version.migrationLog && (
105
+ <Badge size="xs" variant="outline" color="orange">
106
+ Schema Changed
107
+ </Badge>
108
+ )}
109
+ {version.status === "expired" && (
110
+ <ActionButton
111
+ size="compact-xs"
112
+ variant="subtle"
113
+ onClick={() => onRollback(version.version)}
114
+ >
115
+ Rollback to this version
116
+ </ActionButton>
117
+ )}
118
+ </Stack>
119
+ </Timeline.Item>
120
+ ))}
121
+ </Timeline>
122
+ </ScrollArea>
123
+ );
124
+ };
125
+
126
+ return (
127
+ <Card
128
+ withBorder
129
+ w={300}
130
+ h="100%"
131
+ style={{ flexShrink: 0, overflow: "hidden" }}
132
+ >
133
+ <Stack gap="xs" h="100%">
134
+ <Group gap="xs">
135
+ <IconHistory size={16} color="var(--mantine-color-dimmed)" />
136
+ <Text size="sm" fw={500}>
137
+ Version History
138
+ </Text>
139
+ </Group>
140
+ {renderContent()}
141
+ </Stack>
142
+ </Card>
143
+ );
144
+ };
145
+
146
+ export default ParameterHistory;
@@ -0,0 +1,146 @@
1
+ import { ActionButton, Text } from "@alepha/ui";
2
+ import {
3
+ Box,
4
+ Card,
5
+ Group,
6
+ ScrollArea,
7
+ Stack,
8
+ Tooltip,
9
+ Tree,
10
+ type TreeNodeData,
11
+ useTree,
12
+ } from "@mantine/core";
13
+ import {
14
+ IconChevronDown,
15
+ IconChevronRight,
16
+ IconFolder,
17
+ IconFolderOpen,
18
+ IconRefresh,
19
+ IconSettings,
20
+ } from "@tabler/icons-react";
21
+ import type { ConfigTreeNode } from "alepha/api/parameters";
22
+ import { type HTMLAttributes, useMemo } from "react";
23
+
24
+ export interface ParameterTreeProps {
25
+ treeData: ConfigTreeNode[];
26
+ selectedConfig: string | null;
27
+ onSelect: (name: string) => void;
28
+ onRefresh: () => void;
29
+ }
30
+
31
+ const ParameterTree = ({
32
+ treeData,
33
+ selectedConfig,
34
+ onSelect,
35
+ onRefresh,
36
+ }: ParameterTreeProps) => {
37
+ const tree = useTree({
38
+ initialExpandedState: {},
39
+ });
40
+
41
+ const mantineTreeData = useMemo((): TreeNodeData[] => {
42
+ const convert = (nodes: ConfigTreeNode[]): TreeNodeData[] => {
43
+ return nodes.map((node) => ({
44
+ value: node.path,
45
+ label: node.name,
46
+ children: node.children.length > 0 ? convert(node.children) : undefined,
47
+ }));
48
+ };
49
+ return convert(treeData);
50
+ }, [treeData]);
51
+
52
+ const renderNode = ({
53
+ node,
54
+ expanded,
55
+ hasChildren,
56
+ elementProps,
57
+ }: {
58
+ node: TreeNodeData;
59
+ expanded: boolean;
60
+ hasChildren: boolean;
61
+ elementProps: HTMLAttributes<HTMLDivElement>;
62
+ }) => {
63
+ const isLeaf = !hasChildren;
64
+ const isSelected = selectedConfig === node.value;
65
+
66
+ return (
67
+ <Group
68
+ gap="xs"
69
+ wrap="nowrap"
70
+ {...elementProps}
71
+ onClick={(e) => {
72
+ elementProps.onClick?.(e);
73
+ if (isLeaf) {
74
+ onSelect(node.value);
75
+ }
76
+ }}
77
+ style={{
78
+ ...elementProps.style,
79
+ cursor: isLeaf ? "pointer" : "default",
80
+ backgroundColor: isSelected
81
+ ? "var(--mantine-color-blue-light)"
82
+ : undefined,
83
+ borderRadius: "var(--mantine-radius-sm)",
84
+ paddingTop: 4,
85
+ paddingBottom: 4,
86
+ paddingRight: 8,
87
+ }}
88
+ >
89
+ {hasChildren ? (
90
+ <>
91
+ {expanded ? (
92
+ <IconChevronDown size={14} color="var(--mantine-color-dimmed)" />
93
+ ) : (
94
+ <IconChevronRight size={14} color="var(--mantine-color-dimmed)" />
95
+ )}
96
+ {expanded ? (
97
+ <IconFolderOpen size={16} color="var(--mantine-color-blue-6)" />
98
+ ) : (
99
+ <IconFolder size={16} color="var(--mantine-color-blue-6)" />
100
+ )}
101
+ </>
102
+ ) : (
103
+ <>
104
+ <Box w={14} />
105
+ <IconSettings size={16} color="var(--mantine-color-gray-6)" />
106
+ </>
107
+ )}
108
+ <Text size="sm" fw={isSelected ? 500 : 400}>
109
+ {node.label}
110
+ </Text>
111
+ </Group>
112
+ );
113
+ };
114
+
115
+ return (
116
+ <Card withBorder w={280} h="100%" style={{ flexShrink: 0 }}>
117
+ <Stack gap="xs" h="100%">
118
+ <Group justify="space-between">
119
+ <Text size="sm" fw={500}>
120
+ Parameters
121
+ </Text>
122
+ <Tooltip label="Refresh">
123
+ <ActionButton
124
+ variant="subtle"
125
+ size="compact-xs"
126
+ onClick={onRefresh}
127
+ >
128
+ <IconRefresh size={14} />
129
+ </ActionButton>
130
+ </Tooltip>
131
+ </Group>
132
+ <ScrollArea flex={1} offsetScrollbars>
133
+ <Tree
134
+ data={mantineTreeData}
135
+ tree={tree}
136
+ levelOffset={20}
137
+ expandOnClick
138
+ renderNode={renderNode}
139
+ />
140
+ </ScrollArea>
141
+ </Stack>
142
+ </Card>
143
+ );
144
+ };
145
+
146
+ export default ParameterTree;
@@ -0,0 +1,35 @@
1
+ import type { Parameter } from "alepha/api/parameters";
2
+
3
+ export interface ConfigValue {
4
+ current?: Parameter;
5
+ next?: Parameter;
6
+ /** Default value from the registered $config primitive */
7
+ defaultValue?: unknown;
8
+ /** Current in-memory value (may be default if never saved) */
9
+ currentValue?: unknown;
10
+ /** TypeBox/JSON schema for the configuration (as JSON from API) */
11
+ schema?: Record<string, unknown>;
12
+ }
13
+
14
+ export const getStatusColor = (status: string) => {
15
+ switch (status) {
16
+ case "current":
17
+ return "green";
18
+ case "next":
19
+ return "blue";
20
+ case "future":
21
+ return "cyan";
22
+ case "expired":
23
+ return "gray";
24
+ default:
25
+ return "gray";
26
+ }
27
+ };
28
+
29
+ export const formatJson = (obj: unknown): string => {
30
+ try {
31
+ return JSON.stringify(obj, null, 2);
32
+ } catch {
33
+ return String(obj);
34
+ }
35
+ };
@@ -15,7 +15,7 @@ import {
15
15
  sessions,
16
16
  } from "alepha/api/users";
17
17
  import { useState } from "react";
18
- import type { AdminRouter } from "../AdminRouter.ts";
18
+ import type { AdminRouter } from "../../AdminRouter.ts";
19
19
 
20
20
  export interface AdminSessionsProps {
21
21
  userRealmName?: string;