PolicyRoles.tsx86 lines · main
| 1 | import { sortBy } from 'lodash' |
| 2 | import { |
| 3 | MultiSelector, |
| 4 | MultiSelectorContent, |
| 5 | MultiSelectorItem, |
| 6 | MultiSelectorList, |
| 7 | MultiSelectorTrigger, |
| 8 | } from 'ui-patterns/multi-select' |
| 9 | import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader' |
| 10 | |
| 11 | import { SYSTEM_ROLES } from '@/components/interfaces/Database/Roles/Roles.constants' |
| 12 | import AlertError from '@/components/ui/AlertError' |
| 13 | import { useDatabaseRolesQuery } from '@/data/database-roles/database-roles-query' |
| 14 | import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' |
| 15 | |
| 16 | interface PolicyRolesProps { |
| 17 | selectedRoles: string[] |
| 18 | onUpdateSelectedRoles: (roles: string[]) => void |
| 19 | } |
| 20 | type SystemRole = (typeof SYSTEM_ROLES)[number] |
| 21 | |
| 22 | export const PolicyRoles = ({ selectedRoles, onUpdateSelectedRoles }: PolicyRolesProps) => { |
| 23 | const { data: project } = useSelectedProjectQuery() |
| 24 | const { |
| 25 | data, |
| 26 | error, |
| 27 | isPending: isLoading, |
| 28 | isError, |
| 29 | isSuccess, |
| 30 | } = useDatabaseRolesQuery({ |
| 31 | projectRef: project?.ref, |
| 32 | connectionString: project?.connectionString, |
| 33 | }) |
| 34 | const roles = sortBy( |
| 35 | (data ?? []).filter((role) => !SYSTEM_ROLES.includes(role.name as SystemRole)), |
| 36 | (r) => r.name.toLocaleLowerCase() |
| 37 | ) |
| 38 | |
| 39 | const formattedRoles = roles.map((role) => { |
| 40 | return { |
| 41 | id: role.id, |
| 42 | name: role.name, |
| 43 | value: role.name, |
| 44 | disabled: false, |
| 45 | } |
| 46 | }) |
| 47 | |
| 48 | return ( |
| 49 | <div className="flex flex-col md:flex-row gap-4 md:gap-12"> |
| 50 | <div className="flex md:w-1/3 flex-col space-y-2"> |
| 51 | <label className="text-foreground-light text-base" htmlFor="policy-name"> |
| 52 | Target roles |
| 53 | </label> |
| 54 | <p className="text-foreground-lighter text-sm">Apply policy to the selected roles</p> |
| 55 | </div> |
| 56 | <div className="relative md:w-2/3"> |
| 57 | {isLoading && <ShimmeringLoader className="py-4" />} |
| 58 | {isError && <AlertError error={error as any} subject="Failed to retrieve database roles" />} |
| 59 | {isSuccess && ( |
| 60 | <MultiSelector values={selectedRoles} onValuesChange={onUpdateSelectedRoles}> |
| 61 | <MultiSelectorTrigger |
| 62 | mode="inline-combobox" |
| 63 | label={ |
| 64 | selectedRoles.length === 0 |
| 65 | ? 'Defaults to all (public) roles if none selected' |
| 66 | : 'Search for a role' |
| 67 | } |
| 68 | deletableBadge |
| 69 | badgeLimit="wrap" |
| 70 | showIcon={false} |
| 71 | /> |
| 72 | <MultiSelectorContent> |
| 73 | <MultiSelectorList> |
| 74 | {formattedRoles.map((role) => ( |
| 75 | <MultiSelectorItem key={role.id} value={role.value} disabled={role.disabled}> |
| 76 | {role.name} |
| 77 | </MultiSelectorItem> |
| 78 | ))} |
| 79 | </MultiSelectorList> |
| 80 | </MultiSelectorContent> |
| 81 | </MultiSelector> |
| 82 | )} |
| 83 | </div> |
| 84 | </div> |
| 85 | ) |
| 86 | } |