Skip to content

Commit 928de7e

Browse files
committed
feat: wip
1 parent 14f7bfc commit 928de7e

File tree

3 files changed

+110
-45
lines changed

3 files changed

+110
-45
lines changed

site/src/pages/ManagementSettingsPage/CustomRolesPage/CreateEditRolePage.tsx

+25-7
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,47 @@
11
import type { FC } from "react";
22
import { Helmet } from "react-helmet-async";
3-
import { useMutation, useQueryClient } from "react-query";
3+
import { useMutation, useQuery, useQueryClient } from "react-query";
44
import { useNavigate, useParams } from "react-router-dom";
5-
import { patchOrganizationRole } from "api/queries/roles";
5+
import { getErrorMessage } from "api/errors";
6+
import { patchOrganizationRole, organizationRoles } from "api/queries/roles";
7+
import { displayError } from "components/GlobalSnackbar/utils";
68
import { pageTitle } from "utils/page";
79
import CreateEditRolePageView from "./CreateEditRolePageView";
810

911
export const CreateGroupPage: FC = () => {
1012
const queryClient = useQueryClient();
1113
const navigate = useNavigate();
12-
const { organization } = useParams() as { organization: string };
14+
const { organization, roleName } = useParams() as {
15+
organization: string;
16+
roleName: string;
17+
};
1318
const patchOrganizationRoleMutation = useMutation(
1419
patchOrganizationRole(queryClient, organization ?? "default"),
1520
);
21+
const { data } = useQuery(organizationRoles(organization));
22+
const role = data?.find((role) => role.name === roleName);
23+
const pageTitleText =
24+
role !== undefined ? "Edit Custom Role" : "Create Custom Role";
1625

1726
return (
1827
<>
1928
<Helmet>
20-
<title>{pageTitle("Create Custom Role")}</title>
29+
<title>{pageTitle(pageTitleText)}</title>
2130
</Helmet>
2231
<CreateEditRolePageView
32+
role={role}
33+
organization={organization}
2334
onSubmit={async (data) => {
24-
const newRole = await patchOrganizationRoleMutation.mutateAsync(data);
25-
console.log({ newRole });
26-
navigate(`/organizations/${organization}/roles`);
35+
try {
36+
console.log({ data });
37+
await patchOrganizationRoleMutation.mutateAsync(data);
38+
navigate(`/organizations/${organization}/roles`);
39+
} catch (error) {
40+
console.log({ error });
41+
displayError(
42+
getErrorMessage(error, "Failed to update custom role"),
43+
);
44+
}
2745
}}
2846
error={patchOrganizationRoleMutation.error}
2947
isLoading={patchOrganizationRoleMutation.isLoading}

site/src/pages/ManagementSettingsPage/CustomRolesPage/CreateEditRolePageView.tsx

+80-23
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
11
import type { Interpolation, Theme } from "@emotion/react";
2+
import Checkbox from "@mui/material/Checkbox";
23
import Table from "@mui/material/Table";
34
import TableBody from "@mui/material/TableBody";
45
import TableCell from "@mui/material/TableCell";
56
import TableContainer from "@mui/material/TableContainer";
67
import TableRow from "@mui/material/TableRow";
78
import TextField from "@mui/material/TextField";
8-
import { useFormik } from "formik";
9-
import type { FC } from "react";
9+
import { type FormikValues, useFormik } from "formik";
10+
import { type ChangeEvent, useState, type FC, useEffect } from "react";
1011
import { useNavigate } from "react-router-dom";
1112
import * as Yup from "yup";
1213
import { isApiValidationError } from "api/errors";
1314
import { RBACResourceActions } from "api/rbacresources_gen";
14-
import type { Role } from "api/typesGenerated";
15+
import type {
16+
Role,
17+
Permission,
18+
AssignableRoles,
19+
RBACResource,
20+
RBACAction,
21+
} from "api/typesGenerated";
1522
import { ErrorAlert } from "components/Alert/ErrorAlert";
1623
import {
1724
FormFields,
@@ -27,24 +34,29 @@ const validationSchema = Yup.object({
2734
});
2835

2936
export type CreateEditRolePageViewProps = {
37+
role: AssignableRoles | undefined;
38+
organization: string;
3039
onSubmit: (data: Role) => void;
3140
error?: unknown;
3241
isLoading: boolean;
3342
};
3443

3544
export const CreateEditRolePageView: FC<CreateEditRolePageViewProps> = ({
45+
role,
46+
organization,
3647
onSubmit,
3748
error,
3849
isLoading,
3950
}) => {
4051
const navigate = useNavigate();
4152
const form = useFormik<Role>({
4253
initialValues: {
43-
name: "",
44-
display_name: "",
45-
site_permissions: [],
46-
organization_permissions: [],
47-
user_permissions: [],
54+
name: role?.name || "",
55+
organization_id: role?.organization_id || organization,
56+
display_name: role?.display_name || "",
57+
site_permissions: role?.site_permissions || [],
58+
organization_permissions: role?.organization_permissions || [],
59+
user_permissions: role?.user_permissions || [],
4860
},
4961
validationSchema,
5062
onSubmit,
@@ -55,12 +67,14 @@ export const CreateEditRolePageView: FC<CreateEditRolePageViewProps> = ({
5567
return (
5668
<>
5769
<PageHeader css={{ paddingTop: 8 }}>
58-
<PageHeaderTitle>Create custom role</PageHeaderTitle>
70+
<PageHeaderTitle>
71+
{role ? "Edit" : "Create"} custom role
72+
</PageHeaderTitle>
5973
</PageHeader>
6074
<HorizontalForm onSubmit={form.handleSubmit}>
6175
<FormSection
6276
title="Role settings"
63-
description="Set a name for this role."
77+
description="Set a name and permissions for this role."
6478
>
6579
<FormFields>
6680
{Boolean(error) && !isApiValidationError(error) && (
@@ -80,7 +94,10 @@ export const CreateEditRolePageView: FC<CreateEditRolePageViewProps> = ({
8094
fullWidth
8195
label="Display Name"
8296
/>
83-
<ActionCheckboxes permissions={[]}></ActionCheckboxes>
97+
<ActionCheckboxes
98+
permissions={role?.organization_permissions || []}
99+
form={form}
100+
/>
84101
</FormFields>
85102
</FormSection>
86103
<FormFooter onCancel={onCancel} isLoading={isLoading} />
@@ -90,26 +107,69 @@ export const CreateEditRolePageView: FC<CreateEditRolePageViewProps> = ({
90107
};
91108

92109
interface ActionCheckboxesProps {
93-
permissions: Permissions[];
110+
permissions: readonly Permission[] | undefined;
111+
form: ReturnType<typeof useFormik<Role>> & { values: Role };
94112
}
95113

96-
const ActionCheckboxes: FC<ActionCheckboxesProps> = ({ permissions }) => {
114+
const ActionCheckboxes: FC<ActionCheckboxesProps> = ({ permissions, form }) => {
115+
const [checkedActions, setIsChecked] = useState(permissions);
116+
117+
const handleCheckChange = async (
118+
e: ChangeEvent<HTMLInputElement>,
119+
form: ReturnType<typeof useFormik<Role>> & { values: Role },
120+
) => {
121+
const { name, checked } = e.currentTarget;
122+
const [resource_type, action] = name.split(":");
123+
124+
const newPermissions = checked
125+
? [
126+
...(checkedActions ?? []),
127+
{
128+
negate: false,
129+
resource_type: resource_type as RBACResource,
130+
action: action as RBACAction,
131+
},
132+
]
133+
: checkedActions?.filter(
134+
(p) => p.resource_type !== resource_type || p.action !== action,
135+
);
136+
137+
setIsChecked(newPermissions);
138+
await form.setFieldValue("organization_permissions", checkedActions);
139+
};
140+
141+
// useEffect(() => {
142+
// setIsChecked(permissions);
143+
// }, [permissions]);
144+
97145
return (
98146
<TableContainer>
99147
<Table>
100148
<TableBody>
101-
{Object.entries(RBACResourceActions).map(([key, value]) => {
149+
{Object.entries(RBACResourceActions).map(([resourceKey, value]) => {
102150
return (
103-
<TableRow key={key}>
151+
<TableRow key={resourceKey}>
104152
<TableCell>
105-
<li key={key} css={styles.checkBoxes}>
106-
<input type="checkbox" /> {key}
153+
<li key={resourceKey} css={styles.checkBoxes}>
154+
{resourceKey}
107155
<ul css={styles.checkBoxes}>
108-
{Object.entries(value).map(([key, value]) => {
156+
{Object.entries(value).map(([actionKey, value]) => {
109157
return (
110-
<li key={key}>
158+
<li key={actionKey}>
111159
<span css={styles.actionText}>
112-
<input type="checkbox" /> {key}
160+
<Checkbox
161+
name={`${resourceKey}:${actionKey}`}
162+
checked={
163+
checkedActions?.some(
164+
(p) =>
165+
p.resource_type === resourceKey &&
166+
(p.action.toString() === "*" ||
167+
p.action === actionKey),
168+
) || false
169+
}
170+
onChange={(e) => handleCheckChange(e, form)}
171+
/>
172+
{actionKey}
113173
</span>{" "}
114174
-{" "}
115175
<span css={styles.actionDescription}>{value}</span>
@@ -129,9 +189,6 @@ const ActionCheckboxes: FC<ActionCheckboxesProps> = ({ permissions }) => {
129189
};
130190

131191
const styles = {
132-
rolesDropdown: {
133-
marginBottom: 20,
134-
},
135192
checkBoxes: {
136193
margin: 0,
137194
listStyleType: "none",

site/src/pages/ManagementSettingsPage/CustomRolesPage/CustomRolesPageView.tsx

+5-15
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import type { Interpolation, Theme } from "@emotion/react";
22
import AddOutlined from "@mui/icons-material/AddOutlined";
33
import KeyboardArrowRight from "@mui/icons-material/KeyboardArrowRight";
4-
import { LoadingButton } from "@mui/lab";
5-
import { createFilterOptions } from "@mui/material/Autocomplete";
64
import Button from "@mui/material/Button";
75
import Skeleton from "@mui/material/Skeleton";
86
import Table from "@mui/material/Table";
@@ -11,19 +9,17 @@ import TableCell from "@mui/material/TableCell";
119
import TableContainer from "@mui/material/TableContainer";
1210
import TableHead from "@mui/material/TableHead";
1311
import TableRow from "@mui/material/TableRow";
14-
import { useState, type FC } from "react";
12+
import type { FC } from "react";
1513
import { Link as RouterLink, useNavigate } from "react-router-dom";
1614
import type { Role } from "api/typesGenerated";
1715
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne";
1816
import { EmptyState } from "components/EmptyState/EmptyState";
1917
import { Paywall } from "components/Paywall/Paywall";
20-
import { Stack } from "components/Stack/Stack";
2118
import {
2219
TableLoader,
2320
TableLoaderSkeleton,
2421
TableRowSkeleton,
2522
} from "components/TableLoader/TableLoader";
26-
import { permissionsToCheck } from "contexts/auth/permissions";
2723
import { useClickableTableRow } from "hooks";
2824
import { docs } from "utils/docs";
2925

@@ -33,17 +29,14 @@ export type CustomRolesPageViewProps = {
3329
isCustomRolesEnabled: boolean;
3430
};
3531

36-
// const filter = createFilterOptions<Role>();
37-
3832
export const CustomRolesPageView: FC<CustomRolesPageViewProps> = ({
3933
roles,
4034
canAssignOrgRole,
4135
isCustomRolesEnabled,
4236
}) => {
4337
const isLoading = Boolean(roles === undefined);
4438
const isEmpty = Boolean(roles && roles.length === 0);
45-
// const [selectedRole, setSelectedRole] = useState<Role | null>(null);
46-
console.log({ roles });
39+
4740
return (
4841
<>
4942
<ChooseOne>
@@ -59,9 +52,8 @@ export const CustomRolesPageView: FC<CustomRolesPageViewProps> = ({
5952
<Table>
6053
<TableHead>
6154
<TableRow>
62-
<TableCell width="33%">Name</TableCell>
63-
<TableCell width="33%">Display Name</TableCell>
64-
<TableCell width="33%">Permissions</TableCell>
55+
<TableCell width="50%">Name</TableCell>
56+
<TableCell width="49%">Permissions</TableCell>
6557
<TableCell width="1%"></TableCell>
6658
</TableRow>
6759
</TableHead>
@@ -125,9 +117,7 @@ const RoleRow: FC<RoleRowProps> = ({ role }) => {
125117

126118
return (
127119
<TableRow data-testid={`role-${role.name}`} {...rowProps}>
128-
<TableCell>{role.name}</TableCell>
129-
130-
<TableCell css={styles.secondary}>{role.display_name}</TableCell>
120+
<TableCell>{role.display_name || role.name}</TableCell>
131121

132122
<TableCell css={styles.secondary}>
133123
{role.organization_permissions.length}

0 commit comments

Comments
 (0)