diff --git a/site/src/pages/ManagementSettingsPage/CustomRolesPage/CustomRolesPageView.stories.tsx b/site/src/pages/ManagementSettingsPage/CustomRolesPage/CustomRolesPageView.stories.tsx index 4a36523c16a17..26565985e1ee6 100644 --- a/site/src/pages/ManagementSettingsPage/CustomRolesPage/CustomRolesPageView.stories.tsx +++ b/site/src/pages/ManagementSettingsPage/CustomRolesPage/CustomRolesPageView.stories.tsx @@ -1,5 +1,8 @@ import type { Meta, StoryObj } from "@storybook/react"; -import { MockRoleWithOrgPermissions } from "testHelpers/entities"; +import { + MockOrganizationAuditorRole, + MockRoleWithOrgPermissions, +} from "testHelpers/entities"; import { CustomRolesPageView } from "./CustomRolesPageView"; const meta: Meta = { @@ -26,6 +29,14 @@ export const Enabled: Story = { }, }; +export const RoleWithoutPermissions: Story = { + args: { + roles: [MockOrganizationAuditorRole], + canAssignOrgRole: true, + isCustomRolesEnabled: true, + }, +}; + export const EmptyDisplayName: Story = { args: { roles: [ @@ -40,7 +51,7 @@ export const EmptyDisplayName: Story = { }, }; -export const EmptyRoleWithoutPermission: Story = { +export const EmptyTableUserWithoutPermission: Story = { args: { roles: [], canAssignOrgRole: false, @@ -48,7 +59,7 @@ export const EmptyRoleWithoutPermission: Story = { }, }; -export const EmptyRoleWithPermission: Story = { +export const EmptyTableUserWithPermission: Story = { args: { roles: [], canAssignOrgRole: true, diff --git a/site/src/pages/ManagementSettingsPage/CustomRolesPage/CustomRolesPageView.tsx b/site/src/pages/ManagementSettingsPage/CustomRolesPage/CustomRolesPageView.tsx index 15d9c3773b3b5..80208aa4b4261 100644 --- a/site/src/pages/ManagementSettingsPage/CustomRolesPage/CustomRolesPageView.tsx +++ b/site/src/pages/ManagementSettingsPage/CustomRolesPage/CustomRolesPageView.tsx @@ -26,6 +26,7 @@ import { import type { FC } from "react"; import { Link as RouterLink, useNavigate } from "react-router-dom"; import { docs } from "utils/docs"; +import { PermissionPillsList } from "./PermissionPillsList"; export type CustomRolesPageViewProps = { roles: Role[] | undefined; @@ -42,7 +43,6 @@ export const CustomRolesPageView: FC = ({ }) => { const isLoading = roles === undefined; const isEmpty = Boolean(roles && roles.length === 0); - return ( <> @@ -58,8 +58,8 @@ export const CustomRolesPageView: FC = ({ - Name - Permissions + Name + Permissions @@ -129,8 +129,8 @@ const RoleRow: FC = ({ role, onDelete, canAssignOrgRole }) => { {role.display_name || role.name} - - {role.organization_permissions.length} + + diff --git a/site/src/pages/ManagementSettingsPage/CustomRolesPage/PermissionPillsList.stories.tsx b/site/src/pages/ManagementSettingsPage/CustomRolesPage/PermissionPillsList.stories.tsx new file mode 100644 index 0000000000000..327d678e4d802 --- /dev/null +++ b/site/src/pages/ManagementSettingsPage/CustomRolesPage/PermissionPillsList.stories.tsx @@ -0,0 +1,52 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { userEvent, within } from "@storybook/test"; +import { MockRoleWithOrgPermissions } from "testHelpers/entities"; +import { PermissionPillsList } from "./PermissionPillsList"; + +const meta: Meta = { + title: "pages/OrganizationCustomRolesPage/PermissionPillsList", + component: PermissionPillsList, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + permissions: MockRoleWithOrgPermissions.organization_permissions, + }, +}; + +export const SinglePermission: Story = { + args: { + permissions: [ + { + negate: false, + resource_type: "organization_member", + action: "create", + }, + ], + }, +}; + +export const NoPermissions: Story = { + args: { + permissions: [], + }, +}; + +export const HoverOverflowPill: Story = { + args: { + permissions: MockRoleWithOrgPermissions.organization_permissions, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await userEvent.hover(canvas.getByTestId("overflow-permissions-pill")); + }, +}; + +export const ShowAllResources: Story = { + args: { + permissions: MockRoleWithOrgPermissions.organization_permissions, + }, +}; diff --git a/site/src/pages/ManagementSettingsPage/CustomRolesPage/PermissionPillsList.tsx b/site/src/pages/ManagementSettingsPage/CustomRolesPage/PermissionPillsList.tsx new file mode 100644 index 0000000000000..e78e8baba15a1 --- /dev/null +++ b/site/src/pages/ManagementSettingsPage/CustomRolesPage/PermissionPillsList.tsx @@ -0,0 +1,135 @@ +import { type Interpolation, type Theme, useTheme } from "@emotion/react"; +import Stack from "@mui/material/Stack"; +import type { Permission } from "api/typesGenerated"; +import { Pill } from "components/Pill/Pill"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "components/Popover/Popover"; +import type { FC } from "react"; + +function getUniqueResourceTypes(jsonObject: readonly Permission[]) { + const resourceTypes = jsonObject.map((item) => item.resource_type); + return [...new Set(resourceTypes)]; +} + +interface PermissionPillsListProps { + permissions: readonly Permission[]; +} + +export const PermissionPillsList: FC = ({ + permissions, +}) => { + const resourceTypes = getUniqueResourceTypes(permissions); + + return ( + + {permissions.length > 0 ? ( + + ) : ( +

None

+ )} + + {resourceTypes.length > 1 && ( + + )} +
+ ); +}; + +interface PermissionPillProps { + resource: string; + permissions: readonly Permission[]; +} + +const PermissionsPill: FC = ({ + resource, + permissions, +}) => { + const actions = permissions.filter( + (p) => resource === p.resource_type && p.action, + ); + + return ( + + {resource}: {actions.map((p) => p.action).join(", ")} + + ); +}; + +type OverflowPermissionPillProps = { + resources: string[]; + permissions: readonly Permission[]; +}; + +const OverflowPermissionPill: FC = ({ + resources, + permissions, +}) => { + const theme = useTheme(); + + return ( + + + + +{resources.length} more + + + + + {resources.map((resource) => ( + + ))} + + + ); +}; + +const styles = { + permissionPill: (theme) => ({ + backgroundColor: theme.experimental.pillDefault.background, + borderColor: theme.experimental.pillDefault.outline, + color: theme.experimental.pillDefault.text, + width: "fit-content", + }), +} satisfies Record>; diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 08c567187366b..8d09460422251 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -388,6 +388,31 @@ export const MockRoleWithOrgPermissions: TypesGen.Role = { resource_type: "audit_log", action: "read", }, + { + negate: false, + resource_type: "group", + action: "create", + }, + { + negate: false, + resource_type: "group", + action: "delete", + }, + { + negate: false, + resource_type: "group", + action: "read", + }, + { + negate: false, + resource_type: "group", + action: "update", + }, + { + negate: false, + resource_type: "provisioner_daemon", + action: "create", + }, ], user_permissions: [], }; diff --git a/site/src/theme/dark/experimental.ts b/site/src/theme/dark/experimental.ts index aedc1d17146d9..25074fb0106e0 100644 --- a/site/src/theme/dark/experimental.ts +++ b/site/src/theme/dark/experimental.ts @@ -43,4 +43,10 @@ export default { }, }, }, + + pillDefault: { + background: colors.zinc[800], + outline: colors.zinc[700], + text: colors.zinc[200], + }, } satisfies NewTheme; diff --git a/site/src/theme/darkBlue/experimental.ts b/site/src/theme/darkBlue/experimental.ts index 8cf9dafaf3fe6..d7e4b816a775e 100644 --- a/site/src/theme/darkBlue/experimental.ts +++ b/site/src/theme/darkBlue/experimental.ts @@ -43,4 +43,10 @@ export default { }, }, }, + + pillDefault: { + background: colors.gray[800], + outline: colors.gray[700], + text: colors.gray[200], + }, } satisfies NewTheme; diff --git a/site/src/theme/experimental.ts b/site/src/theme/experimental.ts index 66dce1b19558e..56ce8759bec7c 100644 --- a/site/src/theme/experimental.ts +++ b/site/src/theme/experimental.ts @@ -3,4 +3,9 @@ import type { InteractiveRole, Role } from "./roles"; export interface NewTheme { l1: Role; // page background, things which sit at the "root level" l2: InteractiveRole; // sidebars, table headers, navigation + pillDefault: { + background: string; + outline: string; + text: string; + }; } diff --git a/site/src/theme/light/experimental.ts b/site/src/theme/light/experimental.ts index e9c415be9ea1b..a816daab83ad5 100644 --- a/site/src/theme/light/experimental.ts +++ b/site/src/theme/light/experimental.ts @@ -43,4 +43,10 @@ export default { }, }, }, + + pillDefault: { + background: colors.zinc[200], + outline: colors.zinc[300], + text: colors.zinc[700], + }, } satisfies NewTheme; 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