Skip to content

Commit c44ad03

Browse files
committed
feat: add resource-action pills to custom roles table
1 parent a359879 commit c44ad03

File tree

9 files changed

+157
-7
lines changed

9 files changed

+157
-7
lines changed

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

Lines changed: 121 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
import type { Interpolation, Theme } from "@emotion/react";
1+
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
22
import AddOutlined from "@mui/icons-material/AddOutlined";
33
import Button from "@mui/material/Button";
44
import Skeleton from "@mui/material/Skeleton";
5+
import Stack from "@mui/material/Stack";
56
import Table from "@mui/material/Table";
67
import TableBody from "@mui/material/TableBody";
78
import TableCell from "@mui/material/TableCell";
89
import TableContainer from "@mui/material/TableContainer";
910
import TableHead from "@mui/material/TableHead";
1011
import TableRow from "@mui/material/TableRow";
11-
import type { Role } from "api/typesGenerated";
12+
import type { Permission, Role } from "api/typesGenerated";
1213
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne";
1314
import { EmptyState } from "components/EmptyState/EmptyState";
1415
import {
@@ -19,6 +20,12 @@ import {
1920
ThreeDotsButton,
2021
} from "components/MoreMenu/MoreMenu";
2122
import { Paywall } from "components/Paywall/Paywall";
23+
import { Pill } from "components/Pill/Pill";
24+
import {
25+
Popover,
26+
PopoverContent,
27+
PopoverTrigger,
28+
} from "components/Popover/Popover";
2229
import {
2330
TableLoaderSkeleton,
2431
TableRowSkeleton,
@@ -42,7 +49,6 @@ export const CustomRolesPageView: FC<CustomRolesPageViewProps> = ({
4249
}) => {
4350
const isLoading = roles === undefined;
4451
const isEmpty = Boolean(roles && roles.length === 0);
45-
4652
return (
4753
<>
4854
<ChooseOne>
@@ -58,8 +64,8 @@ export const CustomRolesPageView: FC<CustomRolesPageViewProps> = ({
5864
<Table>
5965
<TableHead>
6066
<TableRow>
61-
<TableCell width="50%">Name</TableCell>
62-
<TableCell width="49%">Permissions</TableCell>
67+
<TableCell width="40%">Name</TableCell>
68+
<TableCell width="59%">Permissions</TableCell>
6369
<TableCell width="1%" />
6470
</TableRow>
6571
</TableHead>
@@ -116,6 +122,10 @@ export const CustomRolesPageView: FC<CustomRolesPageViewProps> = ({
116122
);
117123
};
118124

125+
function getUniqueResourceTypes(jsonObject: readonly Permission[]) {
126+
const resourceTypes = jsonObject.map((item) => item.resource_type);
127+
return [...new Set(resourceTypes)];
128+
}
119129
interface RoleRowProps {
120130
role: Role;
121131
onDelete: () => void;
@@ -125,12 +135,28 @@ interface RoleRowProps {
125135
const RoleRow: FC<RoleRowProps> = ({ role, onDelete, canAssignOrgRole }) => {
126136
const navigate = useNavigate();
127137

138+
const resourceTypes: string[] = getUniqueResourceTypes(
139+
role.organization_permissions,
140+
);
141+
128142
return (
129143
<TableRow data-testid={`role-${role.name}`}>
130144
<TableCell>{role.display_name || role.name}</TableCell>
131145

132-
<TableCell css={styles.secondary}>
133-
{role.organization_permissions.length}
146+
<TableCell>
147+
<Stack direction="row" spacing={1}>
148+
<PermissionsPill
149+
resource={resourceTypes[0]}
150+
permissions={role.organization_permissions}
151+
/>
152+
153+
{resourceTypes.length > 1 && (
154+
<OverflowPermissionPill
155+
resources={resourceTypes.slice(1)}
156+
permissions={role.organization_permissions.slice(1)}
157+
/>
158+
)}
159+
</Stack>
134160
</TableCell>
135161

136162
<TableCell>
@@ -176,10 +202,98 @@ const TableLoader = () => {
176202
);
177203
};
178204

205+
interface PermissionPillProps {
206+
resource: string;
207+
permissions: readonly Permission[];
208+
}
209+
210+
const PermissionsPill: FC<PermissionPillProps> = ({
211+
resource,
212+
permissions,
213+
}) => {
214+
const actions = permissions.filter((p) => {
215+
if (resource === p.resource_type) {
216+
return p.action;
217+
}
218+
});
219+
220+
return (
221+
<Pill css={styles.permissionPill}>
222+
<b>{resource}</b>: {actions.map((p) => p.action).join(", ")}
223+
</Pill>
224+
);
225+
};
226+
227+
type OverflowPermissionPillProps = {
228+
resources: string[];
229+
permissions: readonly Permission[];
230+
};
231+
232+
const OverflowPermissionPill: FC<OverflowPermissionPillProps> = ({
233+
resources,
234+
permissions,
235+
}) => {
236+
const theme = useTheme();
237+
238+
return (
239+
<Popover mode="hover">
240+
<PopoverTrigger>
241+
<Pill
242+
css={{
243+
backgroundColor: theme.palette.background.paper,
244+
borderColor: theme.palette.divider,
245+
}}
246+
>
247+
+{resources.length} more
248+
</Pill>
249+
</PopoverTrigger>
250+
251+
<PopoverContent
252+
disableRestoreFocus
253+
disableScrollLock
254+
css={{
255+
".MuiPaper-root": {
256+
display: "flex",
257+
flexFlow: "column wrap",
258+
columnGap: 8,
259+
rowGap: 12,
260+
padding: "12px 16px",
261+
alignContent: "space-around",
262+
minWidth: "auto",
263+
backgroundColor: theme.palette.background.default,
264+
},
265+
}}
266+
anchorOrigin={{
267+
vertical: -4,
268+
horizontal: "center",
269+
}}
270+
transformOrigin={{
271+
vertical: "bottom",
272+
horizontal: "center",
273+
}}
274+
>
275+
{resources.map((resource) => (
276+
<PermissionsPill
277+
key={resource}
278+
resource={resource}
279+
permissions={permissions}
280+
/>
281+
))}
282+
</PopoverContent>
283+
</Popover>
284+
);
285+
};
286+
179287
const styles = {
180288
secondary: (theme) => ({
181289
color: theme.palette.text.secondary,
182290
}),
291+
permissionPill: (theme) => ({
292+
backgroundColor: theme.permission.background,
293+
borderColor: theme.permission.outline,
294+
color: theme.permission.text,
295+
width: "fit-content",
296+
}),
183297
} satisfies Record<string, Interpolation<Theme>>;
184298

185299
export default CustomRolesPageView;

site/src/theme/dark/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { forDarkThemes } from "../externalImages";
22
import experimental from "./experimental";
33
import monaco from "./monaco";
44
import muiTheme from "./mui";
5+
import permission from "./permission";
56
import roles from "./roles";
67

78
export default {
@@ -10,4 +11,5 @@ export default {
1011
experimental,
1112
monaco,
1213
roles,
14+
permission,
1315
};

site/src/theme/dark/permission.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import type { Permission } from "../permission";
2+
import colors from "../tailwindColors";
3+
4+
export default {
5+
background: colors.zinc[800],
6+
outline: colors.zinc[700],
7+
text: colors.zinc[200],
8+
} satisfies Permission;

site/src/theme/darkBlue/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { forDarkThemes } from "../externalImages";
22
import experimental from "./experimental";
33
import monaco from "./monaco";
44
import muiTheme from "./mui";
5+
import permission from "./permission";
56
import roles from "./roles";
67

78
export default {
@@ -10,4 +11,5 @@ export default {
1011
experimental,
1112
monaco,
1213
roles,
14+
permission,
1315
};

site/src/theme/darkBlue/permission.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import type { Permission } from "../permission";
2+
import colors from "../tailwindColors";
3+
4+
export default {
5+
background: colors.gray[800],
6+
outline: colors.gray[700],
7+
text: colors.gray[200],
8+
} satisfies Permission;

site/src/theme/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import darkBlue from "./darkBlue";
66
import type { NewTheme } from "./experimental";
77
import type { ExternalImageModeStyles } from "./externalImages";
88
import light from "./light";
9+
import type { Permission } from "./permission";
910
import type { Roles } from "./roles";
1011

1112
export interface Theme extends Omit<MuiTheme, "palette"> {

site/src/theme/light/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { forLightThemes } from "../externalImages";
22
import experimental from "./experimental";
33
import monaco from "./monaco";
44
import muiTheme from "./mui";
5+
import permission from "./permission";
56
import roles from "./roles";
67

78
export default {
@@ -10,4 +11,5 @@ export default {
1011
experimental,
1112
monaco,
1213
roles,
14+
permission,
1315
};

site/src/theme/light/permission.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import type { Permission } from "../permission";
2+
import colors from "../tailwindColors";
3+
4+
export default {
5+
background: colors.zinc[200],
6+
outline: colors.zinc[300],
7+
text: colors.zinc[700],
8+
} satisfies Permission;

site/src/theme/permission.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export interface Permission {
2+
background: string;
3+
outline: string;
4+
text: string;
5+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy