@alepha/ui 0.14.2 → 0.14.3
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/AdminAudits-DIrCCPk3.js.map +1 -1
- package/dist/admin/AdminNotifications-cIbywWKi.js.map +1 -1
- package/dist/admin/AdminParameters-D-q3Qmhv.js.map +1 -1
- package/dist/admin/AdminSessions-vOgkrQ2U.js.map +1 -1
- package/dist/admin/AdminUserAudits-CSsN1fIC.js.map +1 -1
- package/dist/admin/AdminUserCreate-B72nu-3W.js.map +1 -1
- package/dist/admin/AdminUserDetails-CKM2IEMr.js +475 -0
- package/dist/admin/AdminUserDetails-CKM2IEMr.js.map +1 -0
- package/dist/admin/{AdminUserDetails-z1y8kJeB.js → AdminUserDetails-Zib_B6Al.js} +1 -1
- package/dist/admin/{AdminUserLayout-DyQYacQQ.js → AdminUserLayout-BNBOEiAO.js} +1 -1
- package/dist/admin/AdminUserLayout-D7En9UBq.js +334 -0
- package/dist/admin/AdminUserLayout-D7En9UBq.js.map +1 -0
- package/dist/admin/AdminUserSessions-DEaGu6n6.js.map +1 -1
- package/dist/admin/{AdminUserSettings-CR7MxX_R.js → AdminUserSettings-Di73D7g2.js} +6 -5
- package/dist/admin/AdminUserSettings-Di73D7g2.js.map +1 -0
- package/dist/admin/AdminUserSettings-yI-JECf5.js +3 -0
- package/dist/admin/AdminUsers-BnGIRvmV.js.map +1 -1
- package/dist/admin/index.d.ts +10 -10
- package/dist/admin/index.d.ts.map +1 -1
- package/dist/admin/index.js +17 -17
- package/dist/admin/index.js.map +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +6 -5
- package/dist/core/index.js.map +1 -1
- package/package.json +11 -11
- package/src/admin/AdminRouter.ts +22 -19
- package/src/admin/MainRouter.ts +1 -1
- package/src/admin/components/audits/AdminAudits.tsx +2 -2
- package/src/admin/components/jobs/AdminJobs.tsx +2 -2
- package/src/admin/components/notifications/AdminNotifications.tsx +2 -2
- package/src/admin/components/parameters/AdminParameters.tsx +2 -2
- package/src/admin/components/sessions/AdminSessions.tsx +2 -2
- package/src/admin/components/shared/AdminResourceHeader.tsx +281 -0
- package/src/admin/components/shared/AdminResourceTabs.tsx +94 -0
- package/src/admin/components/shared/index.ts +10 -0
- package/src/admin/components/users/AdminUserAudits.tsx +2 -2
- package/src/admin/components/users/AdminUserCreate.tsx +2 -2
- package/src/admin/components/users/AdminUserDetails.tsx +337 -85
- package/src/admin/components/users/AdminUserLayout.tsx +164 -108
- package/src/admin/components/users/AdminUserSessions.tsx +2 -2
- package/src/admin/components/users/AdminUserSettings.tsx +10 -5
- package/src/admin/components/users/AdminUsers.tsx +6 -2
- package/src/core/components/form/TypeForm.tsx +3 -2
- package/src/core/components/layout/AlephaMantineProvider.tsx +5 -1
- package/src/core/components/layout/Sidebar.tsx +9 -6
- package/dist/admin/AdminUserDetails-BCt8Su-4.js +0 -222
- package/dist/admin/AdminUserDetails-BCt8Su-4.js.map +0 -1
- package/dist/admin/AdminUserLayout-Ck0GLRE5.js +0 -151
- package/dist/admin/AdminUserLayout-Ck0GLRE5.js.map +0 -1
- package/dist/admin/AdminUserSettings-CE66UTIP.js +0 -3
- package/dist/admin/AdminUserSettings-CR7MxX_R.js.map +0 -1
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"mantine"
|
|
8
8
|
],
|
|
9
9
|
"author": "Nicolas Foures",
|
|
10
|
-
"version": "0.14.
|
|
10
|
+
"version": "0.14.3",
|
|
11
11
|
"type": "module",
|
|
12
12
|
"engines": {
|
|
13
13
|
"node": ">=22.0.0"
|
|
@@ -22,16 +22,16 @@
|
|
|
22
22
|
"styles.css"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@alepha/react": "0.14.
|
|
26
|
-
"@mantine/core": "^8.3.
|
|
27
|
-
"@mantine/dates": "^8.3.
|
|
28
|
-
"@mantine/hooks": "^8.3.
|
|
29
|
-
"@mantine/modals": "^8.3.
|
|
30
|
-
"@mantine/notifications": "^8.3.
|
|
31
|
-
"@mantine/nprogress": "^8.3.
|
|
32
|
-
"@mantine/spotlight": "^8.3.
|
|
25
|
+
"@alepha/react": "0.14.3",
|
|
26
|
+
"@mantine/core": "^8.3.11",
|
|
27
|
+
"@mantine/dates": "^8.3.11",
|
|
28
|
+
"@mantine/hooks": "^8.3.11",
|
|
29
|
+
"@mantine/modals": "^8.3.11",
|
|
30
|
+
"@mantine/notifications": "^8.3.11",
|
|
31
|
+
"@mantine/nprogress": "^8.3.11",
|
|
32
|
+
"@mantine/spotlight": "^8.3.11",
|
|
33
33
|
"@tabler/icons-react": "^3.36.1",
|
|
34
|
-
"alepha": "0.14.
|
|
34
|
+
"alepha": "0.14.3",
|
|
35
35
|
"dayjs": "^1.11.19"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"react": "^19.2.3",
|
|
40
40
|
"react-dom": "^19.2.3",
|
|
41
41
|
"typescript": "^5.9.3",
|
|
42
|
-
"vite": "^7.3.
|
|
42
|
+
"vite": "^7.3.1",
|
|
43
43
|
"vitest": "^4.0.16"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
package/src/admin/AdminRouter.ts
CHANGED
|
@@ -13,23 +13,26 @@ import {
|
|
|
13
13
|
IconUsers,
|
|
14
14
|
} from "@tabler/icons-react";
|
|
15
15
|
import { $inject } from "alepha";
|
|
16
|
-
import type {
|
|
16
|
+
import type { AdminAuditController } from "alepha/api/audits";
|
|
17
17
|
import type { FileController } from "alepha/api/files";
|
|
18
|
-
import type {
|
|
19
|
-
import type {
|
|
20
|
-
import type {
|
|
18
|
+
import type { AdminNotificationController } from "alepha/api/notifications";
|
|
19
|
+
import type { AdminConfigController } from "alepha/api/parameters";
|
|
20
|
+
import type {
|
|
21
|
+
AdminSessionController,
|
|
22
|
+
AdminUserController,
|
|
23
|
+
} from "alepha/api/users";
|
|
21
24
|
import { $client } from "alepha/server/links";
|
|
22
25
|
|
|
23
26
|
export class AdminRouter {
|
|
24
27
|
protected readonly router = $inject(ReactRouter);
|
|
25
28
|
protected readonly authRouter = $inject(AuthRouter);
|
|
26
29
|
protected readonly auth = $inject(ReactAuth);
|
|
27
|
-
protected readonly userCtrl = $client<
|
|
28
|
-
protected readonly sessionCtrl = $client<
|
|
29
|
-
protected readonly notificationCtrl = $client<
|
|
30
|
+
protected readonly userCtrl = $client<AdminUserController>();
|
|
31
|
+
protected readonly sessionCtrl = $client<AdminSessionController>();
|
|
32
|
+
protected readonly notificationCtrl = $client<AdminNotificationController>();
|
|
30
33
|
protected readonly fileCtrl = $client<FileController>();
|
|
31
|
-
protected readonly configCtrl = $client<
|
|
32
|
-
protected readonly auditCtrl = $client<
|
|
34
|
+
protected readonly configCtrl = $client<AdminConfigController>();
|
|
35
|
+
protected readonly auditCtrl = $client<AdminAuditController>();
|
|
33
36
|
|
|
34
37
|
protected adminShellProps(): AdminShellProps {
|
|
35
38
|
return {};
|
|
@@ -49,10 +52,10 @@ export class AdminRouter {
|
|
|
49
52
|
// Layout
|
|
50
53
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
51
54
|
|
|
52
|
-
public readonly
|
|
53
|
-
name: "AdminLayout",
|
|
55
|
+
public readonly adminLayout = $page({
|
|
54
56
|
path: "/admin",
|
|
55
57
|
label: "Admin",
|
|
58
|
+
can: () => this.userCtrl.findUsers.can(),
|
|
56
59
|
lazy: () => import("./components/AdminLayout.tsx"),
|
|
57
60
|
props: () => ({
|
|
58
61
|
adminShellProps: this.adminShellProps(),
|
|
@@ -71,7 +74,7 @@ export class AdminRouter {
|
|
|
71
74
|
|
|
72
75
|
public readonly adminUsers = $page({
|
|
73
76
|
icon: IconUsers,
|
|
74
|
-
parent: this.
|
|
77
|
+
parent: this.adminLayout,
|
|
75
78
|
path: "/users",
|
|
76
79
|
label: "Users",
|
|
77
80
|
description: "Manage application users and their roles.",
|
|
@@ -81,7 +84,7 @@ export class AdminRouter {
|
|
|
81
84
|
|
|
82
85
|
public readonly adminUserCreate = $page({
|
|
83
86
|
icon: IconPlus,
|
|
84
|
-
parent: this.
|
|
87
|
+
parent: this.adminLayout,
|
|
85
88
|
path: "/users/create",
|
|
86
89
|
label: "Create User",
|
|
87
90
|
description: "Create a new user account.",
|
|
@@ -91,7 +94,7 @@ export class AdminRouter {
|
|
|
91
94
|
|
|
92
95
|
public readonly adminUserLayout = $page({
|
|
93
96
|
icon: IconUser,
|
|
94
|
-
parent: this.
|
|
97
|
+
parent: this.adminLayout,
|
|
95
98
|
path: "/users/:userId",
|
|
96
99
|
label: "User",
|
|
97
100
|
lazy: () => import("./components/users/AdminUserLayout.tsx"),
|
|
@@ -133,7 +136,7 @@ export class AdminRouter {
|
|
|
133
136
|
|
|
134
137
|
public readonly adminAudits = $page({
|
|
135
138
|
icon: IconHistory,
|
|
136
|
-
parent: this.
|
|
139
|
+
parent: this.adminLayout,
|
|
137
140
|
path: "/audits",
|
|
138
141
|
label: "Audit Log",
|
|
139
142
|
description: "View system-wide audit trail and activity logs.",
|
|
@@ -147,7 +150,7 @@ export class AdminRouter {
|
|
|
147
150
|
|
|
148
151
|
public readonly adminSessions = $page({
|
|
149
152
|
icon: IconDevices,
|
|
150
|
-
parent: this.
|
|
153
|
+
parent: this.adminLayout,
|
|
151
154
|
path: "/sessions",
|
|
152
155
|
label: "Sessions",
|
|
153
156
|
description: "View and manage all active sessions.",
|
|
@@ -161,7 +164,7 @@ export class AdminRouter {
|
|
|
161
164
|
|
|
162
165
|
public readonly adminNotifications = $page({
|
|
163
166
|
icon: IconBell,
|
|
164
|
-
parent: this.
|
|
167
|
+
parent: this.adminLayout,
|
|
165
168
|
path: "/notifications",
|
|
166
169
|
label: "Notifications",
|
|
167
170
|
description: "View notification history and status.",
|
|
@@ -175,7 +178,7 @@ export class AdminRouter {
|
|
|
175
178
|
|
|
176
179
|
public readonly adminFiles = $page({
|
|
177
180
|
icon: IconFile,
|
|
178
|
-
parent: this.
|
|
181
|
+
parent: this.adminLayout,
|
|
179
182
|
path: "/files",
|
|
180
183
|
label: "Files",
|
|
181
184
|
description: "Manage uploaded files and storage.",
|
|
@@ -189,7 +192,7 @@ export class AdminRouter {
|
|
|
189
192
|
|
|
190
193
|
public readonly adminParameters = $page({
|
|
191
194
|
icon: IconSettings,
|
|
192
|
-
parent: this.
|
|
195
|
+
parent: this.adminLayout,
|
|
193
196
|
path: "/parameters",
|
|
194
197
|
label: "Parameters",
|
|
195
198
|
description: "View and manage application configuration parameters.",
|
package/src/admin/MainRouter.ts
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
IconX,
|
|
12
12
|
} from "@tabler/icons-react";
|
|
13
13
|
import { type Page, t } from "alepha";
|
|
14
|
-
import type {
|
|
14
|
+
import type { AdminAuditController, AuditEntity } from "alepha/api/audits";
|
|
15
15
|
import type { AdminRouter } from "../../AdminRouter.ts";
|
|
16
16
|
|
|
17
17
|
export interface AdminAuditsProps {
|
|
@@ -62,7 +62,7 @@ const getTypeColor = (type: string) => {
|
|
|
62
62
|
};
|
|
63
63
|
|
|
64
64
|
const AdminAudits = (props: AdminAuditsProps) => {
|
|
65
|
-
const client = useClient<
|
|
65
|
+
const client = useClient<AdminAuditController>();
|
|
66
66
|
const router = useRouter<AdminRouter>();
|
|
67
67
|
const { l } = useI18n();
|
|
68
68
|
|
|
@@ -10,13 +10,13 @@ import {
|
|
|
10
10
|
} from "@tabler/icons-react";
|
|
11
11
|
import { type Page, t } from "alepha";
|
|
12
12
|
import {
|
|
13
|
-
type
|
|
13
|
+
type AdminJobController,
|
|
14
14
|
type JobExecutionEntity,
|
|
15
15
|
jobExecutions,
|
|
16
16
|
} from "alepha/api/jobs";
|
|
17
17
|
|
|
18
18
|
const AdminJobs = () => {
|
|
19
|
-
const client = useClient<
|
|
19
|
+
const client = useClient<AdminJobController>();
|
|
20
20
|
const { l } = useI18n();
|
|
21
21
|
|
|
22
22
|
const filters = t.object({
|
|
@@ -11,12 +11,12 @@ import {
|
|
|
11
11
|
} from "@tabler/icons-react";
|
|
12
12
|
import { type Page, t } from "alepha";
|
|
13
13
|
import type {
|
|
14
|
-
|
|
14
|
+
AdminNotificationController,
|
|
15
15
|
NotificationEntity,
|
|
16
16
|
} from "alepha/api/notifications";
|
|
17
17
|
|
|
18
18
|
const AdminNotifications = () => {
|
|
19
|
-
const client = useClient<
|
|
19
|
+
const client = useClient<AdminNotificationController>();
|
|
20
20
|
const { l } = useI18n();
|
|
21
21
|
|
|
22
22
|
const filters = t.object({
|
|
@@ -3,7 +3,7 @@ import { Flex, Text } from "@alepha/ui";
|
|
|
3
3
|
import { Loader, Stack } from "@mantine/core";
|
|
4
4
|
import { IconSettings } from "@tabler/icons-react";
|
|
5
5
|
import type {
|
|
6
|
-
|
|
6
|
+
AdminConfigController,
|
|
7
7
|
ConfigTreeNode,
|
|
8
8
|
Parameter,
|
|
9
9
|
} from "alepha/api/parameters";
|
|
@@ -14,7 +14,7 @@ import ParameterTree from "./ParameterTree.tsx";
|
|
|
14
14
|
import type { ConfigValue } from "./types.ts";
|
|
15
15
|
|
|
16
16
|
const AdminParameters = () => {
|
|
17
|
-
const client = useClient<
|
|
17
|
+
const client = useClient<AdminConfigController>();
|
|
18
18
|
|
|
19
19
|
// State
|
|
20
20
|
const [treeData, setTreeData] = useState<ConfigTreeNode[]>([]);
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
} from "@tabler/icons-react";
|
|
12
12
|
import { type Page, t } from "alepha";
|
|
13
13
|
import {
|
|
14
|
-
type
|
|
14
|
+
type AdminSessionController,
|
|
15
15
|
type SessionEntity,
|
|
16
16
|
sessions,
|
|
17
17
|
} from "alepha/api/users";
|
|
@@ -23,7 +23,7 @@ export interface AdminSessionsProps {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
const AdminSessions = (props: AdminSessionsProps) => {
|
|
26
|
-
const client = useClient<
|
|
26
|
+
const client = useClient<AdminSessionController>();
|
|
27
27
|
const router = useRouter<AdminRouter>();
|
|
28
28
|
const { l } = useI18n();
|
|
29
29
|
const [refreshKey, setRefreshKey] = useState(0);
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { useRouter } from "@alepha/react/router";
|
|
2
|
+
import { ActionButton } from "@alepha/ui";
|
|
3
|
+
import {
|
|
4
|
+
ActionIcon,
|
|
5
|
+
Avatar,
|
|
6
|
+
Badge,
|
|
7
|
+
Button,
|
|
8
|
+
Group,
|
|
9
|
+
Menu,
|
|
10
|
+
Stack,
|
|
11
|
+
Text,
|
|
12
|
+
Tooltip,
|
|
13
|
+
} from "@mantine/core";
|
|
14
|
+
import {
|
|
15
|
+
IconChevronDown,
|
|
16
|
+
IconChevronLeft,
|
|
17
|
+
IconExternalLink,
|
|
18
|
+
} from "@tabler/icons-react";
|
|
19
|
+
import type { ComponentType, ReactNode } from "react";
|
|
20
|
+
|
|
21
|
+
export interface AdminResourceAction {
|
|
22
|
+
label: string;
|
|
23
|
+
icon?: ComponentType<{ size?: number }>;
|
|
24
|
+
onClick?: () => void;
|
|
25
|
+
href?: string;
|
|
26
|
+
color?: string;
|
|
27
|
+
disabled?: boolean;
|
|
28
|
+
loading?: boolean;
|
|
29
|
+
variant?: "filled" | "light" | "outline" | "subtle";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface AdminResourceHeaderProps {
|
|
33
|
+
/**
|
|
34
|
+
* Back navigation URL
|
|
35
|
+
*/
|
|
36
|
+
backHref?: string;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Back navigation label
|
|
40
|
+
*/
|
|
41
|
+
backLabel?: string;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Avatar content (letter, image URL, or custom node)
|
|
45
|
+
*/
|
|
46
|
+
avatar?: string | ReactNode;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Avatar color
|
|
50
|
+
*/
|
|
51
|
+
avatarColor?: string;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Resource title (e.g., user name)
|
|
55
|
+
*/
|
|
56
|
+
title: string;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Secondary text (e.g., email)
|
|
60
|
+
*/
|
|
61
|
+
subtitle?: string;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Tertiary identifier to copy (e.g., user ID)
|
|
65
|
+
*/
|
|
66
|
+
identifier?: string;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Label for the identifier tooltip
|
|
70
|
+
*/
|
|
71
|
+
identifierLabel?: string;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Status badge
|
|
75
|
+
*/
|
|
76
|
+
status?: {
|
|
77
|
+
label: string;
|
|
78
|
+
color: "green" | "red" | "yellow" | "blue" | "gray";
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Additional badges (e.g., roles)
|
|
83
|
+
*/
|
|
84
|
+
badges?: Array<{
|
|
85
|
+
label: string;
|
|
86
|
+
color?: string;
|
|
87
|
+
variant?: "filled" | "light" | "outline" | "dot";
|
|
88
|
+
}>;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Primary action button
|
|
92
|
+
*/
|
|
93
|
+
primaryAction?: AdminResourceAction;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Menu actions (shown in dropdown)
|
|
97
|
+
*/
|
|
98
|
+
menuActions?: AdminResourceAction[];
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* External link URL
|
|
102
|
+
*/
|
|
103
|
+
externalUrl?: string;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Loading state
|
|
107
|
+
*/
|
|
108
|
+
loading?: boolean;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const ActionMenuItem = (props: { action: AdminResourceAction }) => {
|
|
112
|
+
const { action } = props;
|
|
113
|
+
const router = useRouter();
|
|
114
|
+
|
|
115
|
+
const menuItemProps: Record<string, unknown> = {};
|
|
116
|
+
if (action.href) {
|
|
117
|
+
Object.assign(menuItemProps, router.anchor(action.href));
|
|
118
|
+
} else if (action.onClick) {
|
|
119
|
+
menuItemProps.onClick = action.onClick;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<Menu.Item
|
|
124
|
+
leftSection={action.icon ? <action.icon size={16} /> : undefined}
|
|
125
|
+
color={action.color}
|
|
126
|
+
disabled={action.disabled}
|
|
127
|
+
{...menuItemProps}
|
|
128
|
+
>
|
|
129
|
+
{action.label}
|
|
130
|
+
</Menu.Item>
|
|
131
|
+
);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const AdminResourceHeader = (props: AdminResourceHeaderProps) => {
|
|
135
|
+
const {
|
|
136
|
+
backHref,
|
|
137
|
+
backLabel = "Back",
|
|
138
|
+
avatar,
|
|
139
|
+
avatarColor = "blue",
|
|
140
|
+
title,
|
|
141
|
+
subtitle,
|
|
142
|
+
identifier,
|
|
143
|
+
identifierLabel = "ID",
|
|
144
|
+
status,
|
|
145
|
+
badges = [],
|
|
146
|
+
primaryAction,
|
|
147
|
+
menuActions = [],
|
|
148
|
+
externalUrl,
|
|
149
|
+
} = props;
|
|
150
|
+
|
|
151
|
+
const renderAvatar = () => {
|
|
152
|
+
if (typeof avatar === "string") {
|
|
153
|
+
if (avatar.startsWith("http") || avatar.startsWith("/")) {
|
|
154
|
+
return (
|
|
155
|
+
<Avatar src={avatar} size={56} radius="md" color={avatarColor} />
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
return (
|
|
159
|
+
<Avatar size={56} radius="md" color={avatarColor}>
|
|
160
|
+
{avatar}
|
|
161
|
+
</Avatar>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
if (avatar) {
|
|
165
|
+
return avatar;
|
|
166
|
+
}
|
|
167
|
+
return (
|
|
168
|
+
<Avatar size={56} radius="md" color={avatarColor}>
|
|
169
|
+
{title.charAt(0).toUpperCase()}
|
|
170
|
+
</Avatar>
|
|
171
|
+
);
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
return (
|
|
175
|
+
<Stack gap="xs">
|
|
176
|
+
{/* Breadcrumb / Back navigation */}
|
|
177
|
+
{backHref && (
|
|
178
|
+
<Group>
|
|
179
|
+
<ActionButton
|
|
180
|
+
variant="subtle"
|
|
181
|
+
size="xs"
|
|
182
|
+
href={backHref}
|
|
183
|
+
leftSection={<IconChevronLeft size={14} />}
|
|
184
|
+
c="dimmed"
|
|
185
|
+
>
|
|
186
|
+
{backLabel}
|
|
187
|
+
</ActionButton>
|
|
188
|
+
</Group>
|
|
189
|
+
)}
|
|
190
|
+
|
|
191
|
+
{/* Main header */}
|
|
192
|
+
<Group justify="space-between" align="flex-start" wrap="nowrap">
|
|
193
|
+
{/* Left: Avatar + Info */}
|
|
194
|
+
<Group gap="md" wrap="nowrap">
|
|
195
|
+
{renderAvatar()}
|
|
196
|
+
|
|
197
|
+
<Stack gap={2} justify="center" style={{ minHeight: 56 }}>
|
|
198
|
+
{/* Title row */}
|
|
199
|
+
<Group gap="xs" align="center">
|
|
200
|
+
<Text size="md" fw={600} lh={1.2}>
|
|
201
|
+
{title}
|
|
202
|
+
</Text>
|
|
203
|
+
{status && (
|
|
204
|
+
<Badge
|
|
205
|
+
size="xs"
|
|
206
|
+
variant="light"
|
|
207
|
+
color={status.color}
|
|
208
|
+
tt="lowercase"
|
|
209
|
+
>
|
|
210
|
+
{status.label}
|
|
211
|
+
</Badge>
|
|
212
|
+
)}
|
|
213
|
+
</Group>
|
|
214
|
+
|
|
215
|
+
{/* Subtitle */}
|
|
216
|
+
{subtitle && (
|
|
217
|
+
<Text size="xs" c="dimmed">
|
|
218
|
+
{subtitle}
|
|
219
|
+
</Text>
|
|
220
|
+
)}
|
|
221
|
+
</Stack>
|
|
222
|
+
</Group>
|
|
223
|
+
|
|
224
|
+
{/* Right: Actions */}
|
|
225
|
+
<Group gap="xs">
|
|
226
|
+
{externalUrl && (
|
|
227
|
+
<Tooltip label="Open in new tab" openDelay={500}>
|
|
228
|
+
<ActionIcon
|
|
229
|
+
variant="subtle"
|
|
230
|
+
color="gray"
|
|
231
|
+
component="a"
|
|
232
|
+
href={externalUrl}
|
|
233
|
+
target="_blank"
|
|
234
|
+
>
|
|
235
|
+
<IconExternalLink size={18} />
|
|
236
|
+
</ActionIcon>
|
|
237
|
+
</Tooltip>
|
|
238
|
+
)}
|
|
239
|
+
|
|
240
|
+
{primaryAction && (
|
|
241
|
+
<ActionButton
|
|
242
|
+
variant={primaryAction.variant ?? "light"}
|
|
243
|
+
color={primaryAction.color}
|
|
244
|
+
onClick={primaryAction.onClick}
|
|
245
|
+
href={primaryAction.href}
|
|
246
|
+
loading={primaryAction.loading}
|
|
247
|
+
disabled={primaryAction.disabled}
|
|
248
|
+
leftSection={
|
|
249
|
+
primaryAction.icon ? (
|
|
250
|
+
<primaryAction.icon size={16} />
|
|
251
|
+
) : undefined
|
|
252
|
+
}
|
|
253
|
+
>
|
|
254
|
+
{primaryAction.label}
|
|
255
|
+
</ActionButton>
|
|
256
|
+
)}
|
|
257
|
+
|
|
258
|
+
{menuActions.length > 0 && (
|
|
259
|
+
<Menu position="bottom-end" shadow="md" width={220}>
|
|
260
|
+
<Menu.Target>
|
|
261
|
+
<Button
|
|
262
|
+
variant="default"
|
|
263
|
+
rightSection={<IconChevronDown size={16} />}
|
|
264
|
+
>
|
|
265
|
+
Actions
|
|
266
|
+
</Button>
|
|
267
|
+
</Menu.Target>
|
|
268
|
+
<Menu.Dropdown>
|
|
269
|
+
{menuActions.map((action, index) => (
|
|
270
|
+
<ActionMenuItem key={index} action={action} />
|
|
271
|
+
))}
|
|
272
|
+
</Menu.Dropdown>
|
|
273
|
+
</Menu>
|
|
274
|
+
)}
|
|
275
|
+
</Group>
|
|
276
|
+
</Group>
|
|
277
|
+
</Stack>
|
|
278
|
+
);
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
export default AdminResourceHeader;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { useActive, useRouter } from "@alepha/react/router";
|
|
2
|
+
import { Tabs } from "@mantine/core";
|
|
3
|
+
import type { ComponentType, ReactNode } from "react";
|
|
4
|
+
|
|
5
|
+
export interface AdminResourceTab {
|
|
6
|
+
/**
|
|
7
|
+
* Tab key/value
|
|
8
|
+
*/
|
|
9
|
+
value: string;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Tab label
|
|
13
|
+
*/
|
|
14
|
+
label: string;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Tab icon
|
|
18
|
+
*/
|
|
19
|
+
icon?: ComponentType<{ size?: number }>;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Navigation href
|
|
23
|
+
*/
|
|
24
|
+
href: string;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Whether tab is disabled
|
|
28
|
+
*/
|
|
29
|
+
disabled?: boolean;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Badge count to show
|
|
33
|
+
*/
|
|
34
|
+
count?: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface AdminResourceTabsProps {
|
|
38
|
+
/**
|
|
39
|
+
* Array of tab configurations
|
|
40
|
+
*/
|
|
41
|
+
tabs: AdminResourceTab[];
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Currently active tab value
|
|
45
|
+
*/
|
|
46
|
+
activeTab?: string;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Content to render below tabs
|
|
50
|
+
*/
|
|
51
|
+
children?: ReactNode;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const TabItem = (props: { tab: AdminResourceTab }) => {
|
|
55
|
+
const { tab } = props;
|
|
56
|
+
const router = useRouter();
|
|
57
|
+
const { isActive, isPending } = useActive({ href: tab.href });
|
|
58
|
+
const anchorProps = router.anchor(tab.href);
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<Tabs.Tab
|
|
62
|
+
value={tab.value}
|
|
63
|
+
component="a"
|
|
64
|
+
leftSection={tab.icon ? <tab.icon size={16} /> : undefined}
|
|
65
|
+
disabled={tab.disabled}
|
|
66
|
+
data-active={isActive || undefined}
|
|
67
|
+
style={{
|
|
68
|
+
opacity: isPending ? 0.6 : 1,
|
|
69
|
+
}}
|
|
70
|
+
{...anchorProps}
|
|
71
|
+
>
|
|
72
|
+
{tab.label}
|
|
73
|
+
{tab.count !== undefined && tab.count > 0 && ` (${tab.count})`}
|
|
74
|
+
</Tabs.Tab>
|
|
75
|
+
);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const AdminResourceTabs = (props: AdminResourceTabsProps) => {
|
|
79
|
+
const { tabs, activeTab, children } = props;
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<Tabs value={activeTab} variant="default">
|
|
83
|
+
<Tabs.List>
|
|
84
|
+
{tabs.map((tab) => (
|
|
85
|
+
<TabItem key={tab.value} tab={tab} />
|
|
86
|
+
))}
|
|
87
|
+
</Tabs.List>
|
|
88
|
+
|
|
89
|
+
{children}
|
|
90
|
+
</Tabs>
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export default AdminResourceTabs;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export {
|
|
2
|
+
type AdminResourceAction,
|
|
3
|
+
type AdminResourceHeaderProps,
|
|
4
|
+
default as AdminResourceHeader,
|
|
5
|
+
} from "./AdminResourceHeader.tsx";
|
|
6
|
+
export {
|
|
7
|
+
type AdminResourceTab,
|
|
8
|
+
type AdminResourceTabsProps,
|
|
9
|
+
default as AdminResourceTabs,
|
|
10
|
+
} from "./AdminResourceTabs.tsx";
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
IconX,
|
|
11
11
|
} from "@tabler/icons-react";
|
|
12
12
|
import { type Page, t } from "alepha";
|
|
13
|
-
import type {
|
|
13
|
+
import type { AdminAuditController, AuditEntity } from "alepha/api/audits";
|
|
14
14
|
|
|
15
15
|
export interface AdminUserAuditsProps {
|
|
16
16
|
userRealmName?: string;
|
|
@@ -40,7 +40,7 @@ const getSeverityIcon = (severity: string) => {
|
|
|
40
40
|
|
|
41
41
|
const AdminUserAudits = (_props: AdminUserAuditsProps) => {
|
|
42
42
|
const state = useRouterState();
|
|
43
|
-
const client = useClient<
|
|
43
|
+
const client = useClient<AdminAuditController>();
|
|
44
44
|
const { l } = useI18n();
|
|
45
45
|
const userId = state.params.userId as string;
|
|
46
46
|
|
|
@@ -4,7 +4,7 @@ import { useRouter } from "@alepha/react/router";
|
|
|
4
4
|
import { ActionButton, Control, Flex } from "@alepha/ui";
|
|
5
5
|
import { Card, Stack, Text } from "@mantine/core";
|
|
6
6
|
import { t } from "alepha";
|
|
7
|
-
import type {
|
|
7
|
+
import type { AdminUserController } from "alepha/api/users";
|
|
8
8
|
import type { AdminRouter } from "../../AdminRouter.ts";
|
|
9
9
|
|
|
10
10
|
export interface AdminUserCreateProps {
|
|
@@ -12,7 +12,7 @@ export interface AdminUserCreateProps {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
const AdminUserCreate = (props: AdminUserCreateProps) => {
|
|
15
|
-
const client = useClient<
|
|
15
|
+
const client = useClient<AdminUserController>();
|
|
16
16
|
const router = useRouter<AdminRouter>();
|
|
17
17
|
|
|
18
18
|
const form = useForm({
|