@rebasepro/admin 0.2.4 → 0.2.5
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/{CollectionEditorDialog-D0VqpLPO.js → CollectionEditorDialog-Cn8-tGyL.js} +22 -5
- package/dist/CollectionEditorDialog-Cn8-tGyL.js.map +1 -0
- package/dist/{CollectionsStudioView-Bc3Rxxc2.js → CollectionsStudioView-C-Ts1rZt.js} +4 -4
- package/dist/{CollectionsStudioView-Bc3Rxxc2.js.map → CollectionsStudioView-C-Ts1rZt.js.map} +1 -1
- package/dist/{ExportCollectionAction-Ckc-09BQ.js → ExportCollectionAction-BRdKM3DF.js} +2 -2
- package/dist/{ExportCollectionAction-Ckc-09BQ.js.map → ExportCollectionAction-BRdKM3DF.js.map} +1 -1
- package/dist/{ImportCollectionAction-BqjIrC3Z.js → ImportCollectionAction-U-v7lGxO.js} +2 -2
- package/dist/{ImportCollectionAction-BqjIrC3Z.js.map → ImportCollectionAction-U-v7lGxO.js.map} +1 -1
- package/dist/{PropertyEditView-CvRSV-A2.js → PropertyEditView-BDNYkfNf.js} +2 -2
- package/dist/{PropertyEditView-CvRSV-A2.js.map → PropertyEditView-BDNYkfNf.js.map} +1 -1
- package/dist/collection_editor_ui.js +3 -3
- package/dist/components/RebaseRouteDefs.d.ts +1 -1
- package/dist/components/admin/index.d.ts +1 -3
- package/dist/hooks/navigation/useBuildNavigationStateController.d.ts +1 -1
- package/dist/hooks/navigation/useResolvedViews.d.ts +2 -5
- package/dist/{index-DY2k5TtG.js → index-DHaOV-7A.js} +3 -3
- package/dist/index-DHaOV-7A.js.map +1 -0
- package/dist/{index-UQOMHwt1.js → index-DJSL_SCr.js} +3 -3
- package/dist/index-DJSL_SCr.js.map +1 -0
- package/dist/{index-BCcLwgfe.js → index-XMII4H3d.js} +2 -2
- package/dist/{index-BCcLwgfe.js.map → index-XMII4H3d.js.map} +1 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.js +90 -295
- package/dist/index.js.map +1 -1
- package/dist/{util-ZM9gQuCv.js → util-0GYaJqL_.js} +153 -644
- package/dist/util-0GYaJqL_.js.map +1 -0
- package/package.json +8 -8
- package/src/collection_editor/pgColumnToProperty.ts +19 -2
- package/src/components/DefaultDrawer.tsx +2 -2
- package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +4 -4
- package/src/components/EntityCollectionView/EntityCollectionListView.tsx +7 -0
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +4 -1
- package/src/components/RebaseRouteDefs.tsx +4 -6
- package/src/components/admin/index.ts +1 -3
- package/src/components/index.ts +1 -3
- package/src/hooks/navigation/useBuildNavigationStateController.tsx +2 -3
- package/src/hooks/navigation/useResolvedViews.tsx +6 -48
- package/src/index.ts +2 -3
- package/src/util/previews.ts +9 -1
- package/dist/CollectionEditorDialog-D0VqpLPO.js.map +0 -1
- package/dist/components/admin/RoleChip.d.ts +0 -4
- package/dist/components/admin/RolesFilterSelect.d.ts +0 -2
- package/dist/components/admin/RolesView.d.ts +0 -4
- package/dist/components/admin/UserRolesSelectField.d.ts +0 -2
- package/dist/components/admin/UsersView.d.ts +0 -4
- package/dist/index-DY2k5TtG.js.map +0 -1
- package/dist/index-UQOMHwt1.js.map +0 -1
- package/dist/util-ZM9gQuCv.js.map +0 -1
- package/src/components/admin/RoleChip.tsx +0 -23
- package/src/components/admin/RolesFilterSelect.tsx +0 -45
- package/src/components/admin/RolesView.tsx +0 -470
- package/src/components/admin/UserRolesSelectField.tsx +0 -50
- package/src/components/admin/UsersView.tsx +0 -693
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { Chip, ChipColorScheme } from "@rebasepro/ui";
|
|
3
|
-
import { Role } from "@rebasepro/types";
|
|
4
|
-
import { getColorSchemeForSeed, getColorSchemeForKey } from "@rebasepro/ui";
|
|
5
|
-
|
|
6
|
-
export function RoleChip({ role }: { role: Role }) {
|
|
7
|
-
let colorScheme: ChipColorScheme;
|
|
8
|
-
if (role.isAdmin) {
|
|
9
|
-
colorScheme = getColorSchemeForKey("blue");
|
|
10
|
-
} else if (role.id === "editor") {
|
|
11
|
-
colorScheme = getColorSchemeForKey("yellow");
|
|
12
|
-
} else if (role.id === "viewer") {
|
|
13
|
-
colorScheme = getColorSchemeForKey("gray");
|
|
14
|
-
} else {
|
|
15
|
-
colorScheme = getColorSchemeForSeed(role.id);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return (
|
|
19
|
-
<Chip colorScheme={colorScheme} key={role.id}>
|
|
20
|
-
{role.name}
|
|
21
|
-
</Chip>
|
|
22
|
-
);
|
|
23
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { CollectionActionsProps } from "@rebasepro/types";
|
|
3
|
-
import { Select, SelectItem } from "@rebasepro/ui";
|
|
4
|
-
import { useTranslation, useInternalUserManagementController } from "@rebasepro/core";
|
|
5
|
-
|
|
6
|
-
export function RolesFilterSelect({
|
|
7
|
-
tableController
|
|
8
|
-
}: CollectionActionsProps<any>) {
|
|
9
|
-
const { t } = useTranslation();
|
|
10
|
-
const userManagement = useInternalUserManagementController();
|
|
11
|
-
const roles = userManagement?.roles || [];
|
|
12
|
-
|
|
13
|
-
const currentFilterValue = (tableController.filterValues?.roles?.[1] as string) || "";
|
|
14
|
-
|
|
15
|
-
const handleRoleChange = (newRole: string) => {
|
|
16
|
-
const filterVal = newRole === "" ? undefined : newRole;
|
|
17
|
-
if (filterVal) {
|
|
18
|
-
tableController.setFilterValues?.({
|
|
19
|
-
...tableController.filterValues,
|
|
20
|
-
roles: ["array-contains", filterVal]
|
|
21
|
-
});
|
|
22
|
-
} else {
|
|
23
|
-
const nextFilters = { ...tableController.filterValues };
|
|
24
|
-
delete nextFilters.roles;
|
|
25
|
-
tableController.setFilterValues?.(nextFilters);
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
if (!roles || roles.length === 0) return null;
|
|
30
|
-
|
|
31
|
-
return (
|
|
32
|
-
<Select
|
|
33
|
-
value={currentFilterValue || "__all__"}
|
|
34
|
-
onValueChange={(v) => handleRoleChange(v === "__all__" ? "" : v)}
|
|
35
|
-
placeholder={t("all_roles") || "All Roles"}
|
|
36
|
-
size="small"
|
|
37
|
-
className="w-48"
|
|
38
|
-
>
|
|
39
|
-
<SelectItem value="__all__">{t("all_roles") || "All Roles"}</SelectItem>
|
|
40
|
-
{roles.map(role => (
|
|
41
|
-
<SelectItem key={role.id} value={role.id}>{role.name}</SelectItem>
|
|
42
|
-
))}
|
|
43
|
-
</Select>
|
|
44
|
-
);
|
|
45
|
-
}
|
|
@@ -1,470 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import React, { useState } from "react";
|
|
3
|
-
import { useCollectionRegistryController } from "../../index";
|
|
4
|
-
import { useSnackbarController, useTranslation, useInternalUserManagementController } from "@rebasepro/core";
|
|
5
|
-
import { getDataSourceCapabilities, Role, SecurityRule, UserManagementDelegate } from "@rebasepro/types";
|
|
6
|
-
import { useBreadcrumbsController } from "../../index";
|
|
7
|
-
import {
|
|
8
|
-
Button,
|
|
9
|
-
CenteredView,
|
|
10
|
-
Checkbox,
|
|
11
|
-
Chip,
|
|
12
|
-
CircularProgress,
|
|
13
|
-
Container,
|
|
14
|
-
defaultBorderMixin,
|
|
15
|
-
Dialog,
|
|
16
|
-
DialogActions,
|
|
17
|
-
DialogContent,
|
|
18
|
-
DialogTitle,
|
|
19
|
-
IconButton,
|
|
20
|
-
iconSize,
|
|
21
|
-
Label,
|
|
22
|
-
LoadingButton,
|
|
23
|
-
Paper,
|
|
24
|
-
PlusIcon,
|
|
25
|
-
Table,
|
|
26
|
-
TableBody,
|
|
27
|
-
TableCell,
|
|
28
|
-
TableHeader,
|
|
29
|
-
TableRow,
|
|
30
|
-
TextField,
|
|
31
|
-
Tooltip,
|
|
32
|
-
Trash2Icon,
|
|
33
|
-
Typography
|
|
34
|
-
} from "@rebasepro/ui";
|
|
35
|
-
import { RoleChip } from "./RoleChip";
|
|
36
|
-
import { ConfirmationDialog } from "@rebasepro/core";
|
|
37
|
-
|
|
38
|
-
// ============================================
|
|
39
|
-
// RolesView Component
|
|
40
|
-
// ============================================
|
|
41
|
-
export function RolesView({ userManagement: userManagementProp }: { userManagement?: UserManagementDelegate }) {
|
|
42
|
-
const userManagementContext = useInternalUserManagementController();
|
|
43
|
-
const userManagement = userManagementProp ?? userManagementContext;
|
|
44
|
-
if (!userManagement) {
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
const { roles, saveRole, deleteRole, loading, allowDefaultRolesCreation, rolesError } = userManagement;
|
|
48
|
-
const snackbarController = useSnackbarController();
|
|
49
|
-
const { t } = useTranslation();
|
|
50
|
-
const breadcrumbs = useBreadcrumbsController();
|
|
51
|
-
|
|
52
|
-
React.useEffect(() => {
|
|
53
|
-
breadcrumbs.set({
|
|
54
|
-
breadcrumbs: [{ title: t("roles"),
|
|
55
|
-
url: "/roles" }]
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
}, []);
|
|
59
|
-
|
|
60
|
-
const [dialogOpen, setDialogOpen] = useState(false);
|
|
61
|
-
const [selectedRole, setSelectedRole] = useState<Role | undefined>();
|
|
62
|
-
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false);
|
|
63
|
-
const [roleToDelete, setRoleToDelete] = useState<Role | undefined>();
|
|
64
|
-
const [deleteInProgress, setDeleteInProgress] = useState(false);
|
|
65
|
-
|
|
66
|
-
const handleAddRole = () => {
|
|
67
|
-
setSelectedRole(undefined);
|
|
68
|
-
setDialogOpen(true);
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
const handleEditRole = (role: Role) => {
|
|
72
|
-
setSelectedRole(role);
|
|
73
|
-
setDialogOpen(true);
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
const handleClose = () => {
|
|
77
|
-
setDialogOpen(false);
|
|
78
|
-
setSelectedRole(undefined);
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
const handleDelete = async () => {
|
|
82
|
-
if (!roleToDelete || !deleteRole) return;
|
|
83
|
-
setDeleteInProgress(true);
|
|
84
|
-
try {
|
|
85
|
-
await deleteRole(roleToDelete);
|
|
86
|
-
snackbarController.open({ type: "success",
|
|
87
|
-
message: t("role_deleted_successfully") });
|
|
88
|
-
setDeleteConfirmOpen(false);
|
|
89
|
-
setRoleToDelete(undefined);
|
|
90
|
-
} catch (error: unknown) {
|
|
91
|
-
snackbarController.open({ type: "error",
|
|
92
|
-
message: error instanceof Error ? error.message : t("error_deleting_role") });
|
|
93
|
-
} finally {
|
|
94
|
-
setDeleteInProgress(false);
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
const createDefaultRoles = async () => {
|
|
99
|
-
if (!saveRole) return;
|
|
100
|
-
const defaultRoles: Role[] = [
|
|
101
|
-
{ id: "admin",
|
|
102
|
-
name: "Admin",
|
|
103
|
-
isAdmin: true },
|
|
104
|
-
{ id: "editor",
|
|
105
|
-
name: "Editor",
|
|
106
|
-
isAdmin: false },
|
|
107
|
-
{ id: "viewer",
|
|
108
|
-
name: "Viewer",
|
|
109
|
-
isAdmin: false }
|
|
110
|
-
];
|
|
111
|
-
try {
|
|
112
|
-
for (const role of defaultRoles) {
|
|
113
|
-
await saveRole(role);
|
|
114
|
-
}
|
|
115
|
-
snackbarController.open({
|
|
116
|
-
type: "success",
|
|
117
|
-
message: t("saved_correctly")
|
|
118
|
-
});
|
|
119
|
-
} catch (error: unknown) {
|
|
120
|
-
snackbarController.open({
|
|
121
|
-
type: "error",
|
|
122
|
-
message: error instanceof Error ? error.message : t("error_saving_role")
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
if (loading) {
|
|
128
|
-
return <CenteredView><CircularProgress/></CenteredView>;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return (
|
|
132
|
-
<Container className="w-full flex flex-col py-4 gap-4" maxWidth={"6xl"}>
|
|
133
|
-
<div className="flex items-center mt-12 mb-4 gap-4">
|
|
134
|
-
<Typography gutterBottom variant="h4" className="grow mb-0" component="h4">
|
|
135
|
-
{t("roles")}
|
|
136
|
-
</Typography>
|
|
137
|
-
<Button startIcon={<PlusIcon/>} onClick={handleAddRole} disabled={!saveRole}>
|
|
138
|
-
{t("add_role")}
|
|
139
|
-
</Button>
|
|
140
|
-
</div>
|
|
141
|
-
|
|
142
|
-
<div className="w-full overflow-auto">
|
|
143
|
-
<Table className="w-full">
|
|
144
|
-
<TableHeader>
|
|
145
|
-
<TableCell header>{t("role")}</TableCell>
|
|
146
|
-
<TableCell header className="items-center">{t("is_admin")}</TableCell>
|
|
147
|
-
<TableCell header className="w-24 text-right">{t("actions")}</TableCell>
|
|
148
|
-
</TableHeader>
|
|
149
|
-
<TableBody>
|
|
150
|
-
{roles && roles.map((role: Role) => {
|
|
151
|
-
|
|
152
|
-
return (
|
|
153
|
-
<TableRow key={role.id} onClick={() => saveRole && handleEditRole(role)}>
|
|
154
|
-
<TableCell>
|
|
155
|
-
<RoleChip role={role}/>
|
|
156
|
-
</TableCell>
|
|
157
|
-
<TableCell className="items-center">
|
|
158
|
-
<Checkbox checked={role.isAdmin ?? false} disabled/>
|
|
159
|
-
</TableCell>
|
|
160
|
-
<TableCell className="text-right whitespace-nowrap">
|
|
161
|
-
<div className="flex justify-end items-center gap-1">
|
|
162
|
-
{!role.isAdmin && deleteRole && (
|
|
163
|
-
<Tooltip asChild title={t("delete_this_role")}>
|
|
164
|
-
<IconButton
|
|
165
|
-
size="small"
|
|
166
|
-
onClick={(e) => {
|
|
167
|
-
e.stopPropagation();
|
|
168
|
-
setRoleToDelete(role);
|
|
169
|
-
setDeleteConfirmOpen(true);
|
|
170
|
-
}}>
|
|
171
|
-
<Trash2Icon size={iconSize.small}/>
|
|
172
|
-
</IconButton>
|
|
173
|
-
</Tooltip>
|
|
174
|
-
)}
|
|
175
|
-
</div>
|
|
176
|
-
</TableCell>
|
|
177
|
-
</TableRow>
|
|
178
|
-
);
|
|
179
|
-
})}
|
|
180
|
-
|
|
181
|
-
{(!roles || roles.length === 0) && (
|
|
182
|
-
<TableRow>
|
|
183
|
-
<TableCell colspan={3}>
|
|
184
|
-
<CenteredView className="flex flex-col gap-4 my-8 items-center">
|
|
185
|
-
<Typography variant="label">
|
|
186
|
-
{rolesError
|
|
187
|
-
? t("no_permission_to_view_roles")
|
|
188
|
-
: t("no_roles_yet")}
|
|
189
|
-
</Typography>
|
|
190
|
-
{rolesError && (
|
|
191
|
-
<Typography variant="caption" color="secondary">
|
|
192
|
-
{t("no_permission_description")}
|
|
193
|
-
</Typography>
|
|
194
|
-
)}
|
|
195
|
-
{!rolesError && allowDefaultRolesCreation && saveRole && (
|
|
196
|
-
<Button onClick={createDefaultRoles}>
|
|
197
|
-
{t("create_default_roles")}
|
|
198
|
-
</Button>
|
|
199
|
-
)}
|
|
200
|
-
</CenteredView>
|
|
201
|
-
</TableCell>
|
|
202
|
-
</TableRow>
|
|
203
|
-
)}
|
|
204
|
-
</TableBody>
|
|
205
|
-
</Table>
|
|
206
|
-
</div>
|
|
207
|
-
|
|
208
|
-
{/* Role Edit Dialog */}
|
|
209
|
-
{saveRole && (
|
|
210
|
-
<RoleDetailsForm
|
|
211
|
-
key={selectedRole?.id ?? "new"}
|
|
212
|
-
open={dialogOpen}
|
|
213
|
-
role={selectedRole}
|
|
214
|
-
saveRole={saveRole}
|
|
215
|
-
handleClose={handleClose}
|
|
216
|
-
/>
|
|
217
|
-
)}
|
|
218
|
-
|
|
219
|
-
{/* Delete Confirmation */}
|
|
220
|
-
<ConfirmationDialog
|
|
221
|
-
open={deleteConfirmOpen}
|
|
222
|
-
loading={deleteInProgress}
|
|
223
|
-
onAccept={handleDelete}
|
|
224
|
-
onCancel={() => { setDeleteConfirmOpen(false); setRoleToDelete(undefined); }}
|
|
225
|
-
title={<>{t("delete_confirmation_title")}</>}
|
|
226
|
-
body={<>{t("delete_role_confirmation")}</>}
|
|
227
|
-
/>
|
|
228
|
-
</Container>
|
|
229
|
-
);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// ============================================
|
|
233
|
-
// RoleDetailsForm Component
|
|
234
|
-
// ============================================
|
|
235
|
-
function RoleDetailsForm({
|
|
236
|
-
open,
|
|
237
|
-
role: roleProp,
|
|
238
|
-
saveRole,
|
|
239
|
-
handleClose
|
|
240
|
-
}: {
|
|
241
|
-
open: boolean;
|
|
242
|
-
role?: Role;
|
|
243
|
-
saveRole: (role: Role) => Promise<void>;
|
|
244
|
-
handleClose: () => void;
|
|
245
|
-
}) {
|
|
246
|
-
const snackbarController = useSnackbarController();
|
|
247
|
-
const { t } = useTranslation();
|
|
248
|
-
const isNewRole = !roleProp;
|
|
249
|
-
|
|
250
|
-
const [roleId, setRoleId] = useState(roleProp?.id || "");
|
|
251
|
-
const [roleName, setRoleName] = useState(roleProp?.name || "");
|
|
252
|
-
const [isAdmin, setIsAdmin] = useState(roleProp?.isAdmin ?? false);
|
|
253
|
-
|
|
254
|
-
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
255
|
-
const [errors, setErrors] = useState<{ id?: string; name?: string }>({});
|
|
256
|
-
const [submitCount, setSubmitCount] = useState(0);
|
|
257
|
-
|
|
258
|
-
const validate = () => {
|
|
259
|
-
const newErrors: typeof errors = {};
|
|
260
|
-
if (!roleId) newErrors.id = "Required";
|
|
261
|
-
if (!roleName) newErrors.name = "Required";
|
|
262
|
-
setErrors(newErrors);
|
|
263
|
-
return Object.keys(newErrors).length === 0;
|
|
264
|
-
};
|
|
265
|
-
|
|
266
|
-
const handleSubmit = async (e: React.FormEvent) => {
|
|
267
|
-
e.preventDefault();
|
|
268
|
-
setSubmitCount(c => c + 1);
|
|
269
|
-
|
|
270
|
-
if (!validate()) return;
|
|
271
|
-
|
|
272
|
-
setIsSubmitting(true);
|
|
273
|
-
try {
|
|
274
|
-
await saveRole({
|
|
275
|
-
id: roleId,
|
|
276
|
-
name: roleName,
|
|
277
|
-
isAdmin
|
|
278
|
-
});
|
|
279
|
-
handleClose();
|
|
280
|
-
} catch (error: unknown) {
|
|
281
|
-
snackbarController.open({ type: "error",
|
|
282
|
-
message: error instanceof Error ? error.message : "Failed to save role" });
|
|
283
|
-
} finally {
|
|
284
|
-
setIsSubmitting(false);
|
|
285
|
-
}
|
|
286
|
-
};
|
|
287
|
-
|
|
288
|
-
return (
|
|
289
|
-
<Dialog open={open} onOpenChange={(open) => !open ? handleClose() : undefined} maxWidth="4xl">
|
|
290
|
-
<form onSubmit={handleSubmit} autoComplete="off" noValidate
|
|
291
|
-
style={{ display: "flex",
|
|
292
|
-
flexDirection: "column",
|
|
293
|
-
position: "relative",
|
|
294
|
-
height: "100%" }}>
|
|
295
|
-
|
|
296
|
-
<DialogTitle variant="h4" gutterBottom={false}>
|
|
297
|
-
{t("role")}
|
|
298
|
-
</DialogTitle>
|
|
299
|
-
|
|
300
|
-
<DialogContent className="h-full grow overflow-y-auto">
|
|
301
|
-
<div className="grid grid-cols-12 gap-4">
|
|
302
|
-
<div className="col-span-12 sm:col-span-4">
|
|
303
|
-
<TextField
|
|
304
|
-
name="id"
|
|
305
|
-
required
|
|
306
|
-
error={submitCount > 0 && Boolean(errors.id)}
|
|
307
|
-
value={roleId}
|
|
308
|
-
onChange={(e) => setRoleId(e.target.value)}
|
|
309
|
-
label={t("role_id")}
|
|
310
|
-
disabled={!isNewRole}
|
|
311
|
-
/>
|
|
312
|
-
{submitCount > 0 && errors.id && (
|
|
313
|
-
<Typography variant="caption" color="error">{errors.id}</Typography>
|
|
314
|
-
)}
|
|
315
|
-
</div>
|
|
316
|
-
|
|
317
|
-
<div className="col-span-12 sm:col-span-4">
|
|
318
|
-
<TextField
|
|
319
|
-
name="name"
|
|
320
|
-
required
|
|
321
|
-
error={submitCount > 0 && Boolean(errors.name)}
|
|
322
|
-
value={roleName}
|
|
323
|
-
onChange={(e) => setRoleName(e.target.value)}
|
|
324
|
-
label={t("role_name")}
|
|
325
|
-
/>
|
|
326
|
-
{submitCount > 0 && errors.name && (
|
|
327
|
-
<Typography variant="caption" color="error">{errors.name}</Typography>
|
|
328
|
-
)}
|
|
329
|
-
</div>
|
|
330
|
-
|
|
331
|
-
<div className="col-span-12 sm:col-span-4 flex items-start pt-2">
|
|
332
|
-
<Label className="flex items-center gap-2 cursor-pointer mt-3">
|
|
333
|
-
<Checkbox
|
|
334
|
-
checked={isAdmin}
|
|
335
|
-
onCheckedChange={(checked) => setIsAdmin(Boolean(checked))}
|
|
336
|
-
/>
|
|
337
|
-
<Typography variant="body2" className="font-medium">{t("is_admin")}</Typography>
|
|
338
|
-
</Label>
|
|
339
|
-
</div>
|
|
340
|
-
|
|
341
|
-
<div className="col-span-12">
|
|
342
|
-
<CollectionPermissionsMatrix roleId={roleId} isAdmin={isAdmin}/>
|
|
343
|
-
</div>
|
|
344
|
-
</div>
|
|
345
|
-
</DialogContent>
|
|
346
|
-
|
|
347
|
-
<DialogActions>
|
|
348
|
-
<Button variant="text" onClick={handleClose}>
|
|
349
|
-
{t("cancel")}
|
|
350
|
-
</Button>
|
|
351
|
-
<LoadingButton
|
|
352
|
-
variant="filled"
|
|
353
|
-
type="submit"
|
|
354
|
-
disabled={isSubmitting}
|
|
355
|
-
loading={isSubmitting}
|
|
356
|
-
>
|
|
357
|
-
{isNewRole ? t("create_role") : t("update")}
|
|
358
|
-
</LoadingButton>
|
|
359
|
-
</DialogActions>
|
|
360
|
-
</form>
|
|
361
|
-
</Dialog>
|
|
362
|
-
);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
// ============================================
|
|
366
|
-
// CollectionPermissionsMatrix
|
|
367
|
-
// ============================================
|
|
368
|
-
const CRUD_OPS = [
|
|
369
|
-
{ op: "select" as const,
|
|
370
|
-
label: "read" },
|
|
371
|
-
{ op: "insert" as const,
|
|
372
|
-
label: "create" },
|
|
373
|
-
{ op: "update" as const,
|
|
374
|
-
label: "edit" },
|
|
375
|
-
{ op: "delete" as const,
|
|
376
|
-
label: "delete" }
|
|
377
|
-
];
|
|
378
|
-
|
|
379
|
-
function hasRoleAccess(
|
|
380
|
-
rules: SecurityRule[] | undefined,
|
|
381
|
-
roleId: string,
|
|
382
|
-
op: "select" | "insert" | "update" | "delete"
|
|
383
|
-
): boolean {
|
|
384
|
-
if (!rules || rules.length === 0) return true;
|
|
385
|
-
const applicable = rules.filter(r =>
|
|
386
|
-
r.operation === op || r.operation === "all" ||
|
|
387
|
-
r.operations?.includes(op) || r.operations?.includes("all")
|
|
388
|
-
);
|
|
389
|
-
if (applicable.length === 0) return false;
|
|
390
|
-
const forRole = applicable.filter(r =>
|
|
391
|
-
!r.roles || r.roles.length === 0 || r.roles.includes(roleId) || r.roles.includes("public")
|
|
392
|
-
);
|
|
393
|
-
if (forRole.length === 0) return false;
|
|
394
|
-
for (const r of forRole) {
|
|
395
|
-
if ((r.mode ?? "permissive") === "restrictive") return false;
|
|
396
|
-
}
|
|
397
|
-
return forRole.some(r => (r.mode ?? "permissive") === "permissive");
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
function PermCell({ granted }: { granted: boolean }) {
|
|
401
|
-
return (
|
|
402
|
-
<span className={granted
|
|
403
|
-
? "text-green-500 dark:text-green-400 font-bold"
|
|
404
|
-
: "text-surface-300 dark:text-surface-600"}
|
|
405
|
-
>
|
|
406
|
-
{granted ? "✓" : "✗"}
|
|
407
|
-
</span>
|
|
408
|
-
);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
function CollectionPermissionsMatrix({ roleId, isAdmin }: { roleId: string; isAdmin: boolean }) {
|
|
412
|
-
const { collections } = useCollectionRegistryController();
|
|
413
|
-
const { t } = useTranslation();
|
|
414
|
-
|
|
415
|
-
if (!collections || collections.length === 0) {
|
|
416
|
-
return (
|
|
417
|
-
<div className="mt-4">
|
|
418
|
-
<Typography variant="label" className="text-surface-400">{t("no_collections_configured")}</Typography>
|
|
419
|
-
</div>
|
|
420
|
-
);
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
const topLevel = collections;
|
|
424
|
-
|
|
425
|
-
return (
|
|
426
|
-
<div className="mt-4">
|
|
427
|
-
<Typography variant="label" className="mb-2 block text-surface-500 dark:text-surface-400 uppercase tracking-wide text-xs">
|
|
428
|
-
{t("collection_permissions")}
|
|
429
|
-
</Typography>
|
|
430
|
-
<div className={`rounded-lg overflow-hidden border w-full ${defaultBorderMixin}`}>
|
|
431
|
-
<Table className="w-full">
|
|
432
|
-
<TableHeader>
|
|
433
|
-
<TableCell header>{t("collection")}</TableCell>
|
|
434
|
-
{CRUD_OPS.map(({ op, label }) => (
|
|
435
|
-
<TableCell key={op} header align="center" className="w-20">{t(label)}</TableCell>
|
|
436
|
-
))}
|
|
437
|
-
</TableHeader>
|
|
438
|
-
<TableBody>
|
|
439
|
-
{topLevel.map((collection) => {
|
|
440
|
-
const capabilities = getDataSourceCapabilities(collection.driver);
|
|
441
|
-
const rules = capabilities.supportsRLS && 'securityRules' in collection ? collection.securityRules : undefined;
|
|
442
|
-
const noRules = !rules || rules.length === 0;
|
|
443
|
-
return (
|
|
444
|
-
<TableRow key={collection.slug}>
|
|
445
|
-
<TableCell>
|
|
446
|
-
<div className="flex items-center gap-1.5">
|
|
447
|
-
<span className="font-medium">{collection.name}</span>
|
|
448
|
-
{noRules && !isAdmin && (
|
|
449
|
-
<Tooltip title={t("no_security_rules_defined")}>
|
|
450
|
-
<Chip size="smallest" colorScheme="gray">{t("no_rules")}</Chip>
|
|
451
|
-
</Tooltip>
|
|
452
|
-
)}
|
|
453
|
-
</div>
|
|
454
|
-
<span className="text-xs text-surface-400 font-mono">{collection.slug}</span>
|
|
455
|
-
</TableCell>
|
|
456
|
-
{CRUD_OPS.map(({ op }) => (
|
|
457
|
-
<TableCell key={op} align="center" className="w-20">
|
|
458
|
-
<PermCell granted={isAdmin || hasRoleAccess(rules, roleId, op)}/>
|
|
459
|
-
</TableCell>
|
|
460
|
-
))}
|
|
461
|
-
</TableRow>
|
|
462
|
-
);
|
|
463
|
-
})}
|
|
464
|
-
</TableBody>
|
|
465
|
-
</Table>
|
|
466
|
-
</div>
|
|
467
|
-
</div>
|
|
468
|
-
);
|
|
469
|
-
}
|
|
470
|
-
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { FieldProps } from "../../types/fields";
|
|
3
|
-
import { MultiSelect, MultiSelectItem } from "@rebasepro/ui";
|
|
4
|
-
import { useTranslation, useInternalUserManagementController } from "@rebasepro/core";
|
|
5
|
-
import { RoleChip } from "./RoleChip";
|
|
6
|
-
|
|
7
|
-
export function UserRolesSelectField({
|
|
8
|
-
propertyKey,
|
|
9
|
-
value,
|
|
10
|
-
setValue,
|
|
11
|
-
disabled
|
|
12
|
-
}: FieldProps) {
|
|
13
|
-
const { t } = useTranslation();
|
|
14
|
-
const userManagement = useInternalUserManagementController();
|
|
15
|
-
const roles = userManagement?.roles || [];
|
|
16
|
-
|
|
17
|
-
const selectedRoleIds = (value || []).map((r: any) => {
|
|
18
|
-
if (typeof r === "object" && r !== null) {
|
|
19
|
-
return r.id;
|
|
20
|
-
}
|
|
21
|
-
return r;
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
const handleValueChange = (val: string[]) => {
|
|
25
|
-
const references = val.map(id => ({
|
|
26
|
-
id,
|
|
27
|
-
path: "roles",
|
|
28
|
-
__type: "relation"
|
|
29
|
-
}));
|
|
30
|
-
setValue(references);
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
return (
|
|
34
|
-
<div className="col-span-12">
|
|
35
|
-
<MultiSelect
|
|
36
|
-
className="w-full"
|
|
37
|
-
label={t("roles") || "Roles"}
|
|
38
|
-
value={selectedRoleIds}
|
|
39
|
-
onValueChange={handleValueChange}
|
|
40
|
-
disabled={disabled}
|
|
41
|
-
>
|
|
42
|
-
{roles.map(role => (
|
|
43
|
-
<MultiSelectItem key={role.id} value={role.id}>
|
|
44
|
-
<RoleChip role={role}/>
|
|
45
|
-
</MultiSelectItem>
|
|
46
|
-
))}
|
|
47
|
-
</MultiSelect>
|
|
48
|
-
</div>
|
|
49
|
-
);
|
|
50
|
-
}
|