Skip to content

Commit ed17ef8

Browse files
committed
Ask for confirmation before unassigning a role from the user
1 parent 25bee05 commit ed17ef8

1 file changed

Lines changed: 72 additions & 34 deletions

File tree

frontend/src/components/pages/security/users/user-roles-card-new.tsx

Lines changed: 72 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,21 @@ import {
2121
EmptyTitle,
2222
} from 'components/redpanda-ui/components/empty';
2323
import { ExternalLinkIcon, Trash2Icon } from 'lucide-react';
24+
import { useState } from 'react';
2425

2526
import { UpdateRoleMembershipRequestSchema } from '../../../../protogen/redpanda/api/dataplane/v1/security_pb';
2627
import { useListRolesQuery, useUpdateRoleMembershipMutation } from '../../../../react-query/api/security';
2728
import { rolesApi } from '../../../../state/backend-api';
2829
import { Button } from '../../../redpanda-ui/components/button';
2930
import { Combobox } from '../../../redpanda-ui/components/combobox';
31+
import {
32+
Dialog,
33+
DialogContent,
34+
DialogDescription,
35+
DialogFooter,
36+
DialogHeader,
37+
DialogTitle,
38+
} from '../../../redpanda-ui/components/dialog';
3039
import { ListLayout, ListLayoutContent, ListLayoutFilters } from '../../../redpanda-ui/components/list-layout';
3140
import { Skeleton } from '../../../redpanda-ui/components/skeleton';
3241
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../../../redpanda-ui/components/table';
@@ -44,8 +53,9 @@ type UserRolesCardNewProps = {
4453
};
4554

4655
export const UserRolesCardNew = ({ roles, userName, isLoading }: UserRolesCardNewProps) => {
47-
const { mutateAsync: updateRoleMembership } = useUpdateRoleMembershipMutation();
56+
const { mutateAsync: updateRoleMembership, isPending } = useUpdateRoleMembershipMutation();
4857
const { data: rolesData } = useListRolesQuery();
58+
const [pendingRemoveRole, setPendingRemoveRole] = useState<string | null>(null);
4959

5060
const assignedRoleNames = new Set(roles.map((r) => r.principalName));
5161

@@ -59,6 +69,7 @@ export const UserRolesCardNew = ({ roles, userName, isLoading }: UserRolesCardNe
5969
create(UpdateRoleMembershipRequestSchema, { roleName, remove: [{ principal: userName }] })
6070
);
6171
await Promise.all([rolesApi.refreshRoles(), rolesApi.refreshRoleMembers()]);
72+
setPendingRemoveRole(null);
6273
};
6374

6475
const assignRole = async (roleName: string) => {
@@ -115,7 +126,7 @@ export const UserRolesCardNew = ({ roles, userName, isLoading }: UserRolesCardNe
115126
<div className="flex items-center justify-end gap-1">
116127
{Boolean(userName) && (
117128
<Button
118-
onClick={() => removeFromRole(r.principalName)}
129+
onClick={() => setPendingRemoveRole(r.principalName)}
119130
size="icon-sm"
120131
testId={`remove-role-${r.principalName}`}
121132
variant="ghost"
@@ -135,37 +146,64 @@ export const UserRolesCardNew = ({ roles, userName, isLoading }: UserRolesCardNe
135146
};
136147

137148
return (
138-
<ListLayout className="min-h-0 gap-3 py-0">
139-
<ListLayoutFilters
140-
actions={
141-
userName ? (
142-
<Combobox
143-
className="w-56"
144-
clearable={false}
145-
onChange={assignRole}
146-
options={availableRoleOptions}
147-
placeholder="Assign a role..."
148-
testId="assign-role-combobox"
149-
value=""
150-
/>
151-
) : undefined
152-
}
153-
>
154-
<Heading as="h2" level={4}>
155-
Roles
156-
</Heading>
157-
</ListLayoutFilters>
158-
<ListLayoutContent>
159-
<Table>
160-
<TableHeader>
161-
<TableRow>
162-
<TableHead>Name</TableHead>
163-
<TableHead align="right">Actions</TableHead>
164-
</TableRow>
165-
</TableHeader>
166-
<TableBody>{renderBody()}</TableBody>
167-
</Table>
168-
</ListLayoutContent>
169-
</ListLayout>
149+
<>
150+
<ListLayout className="min-h-0 gap-3 py-0">
151+
<ListLayoutFilters
152+
actions={
153+
userName ? (
154+
<Combobox
155+
className="w-56"
156+
clearable={false}
157+
onChange={assignRole}
158+
options={availableRoleOptions}
159+
placeholder="Assign a role..."
160+
testId="assign-role-combobox"
161+
value=""
162+
/>
163+
) : undefined
164+
}
165+
>
166+
<Heading as="h2" level={4}>
167+
Roles
168+
</Heading>
169+
</ListLayoutFilters>
170+
<ListLayoutContent>
171+
<Table>
172+
<TableHeader>
173+
<TableRow>
174+
<TableHead>Name</TableHead>
175+
<TableHead align="right">Actions</TableHead>
176+
</TableRow>
177+
</TableHeader>
178+
<TableBody>{renderBody()}</TableBody>
179+
</Table>
180+
</ListLayoutContent>
181+
</ListLayout>
182+
183+
<Dialog onOpenChange={(open) => !open && setPendingRemoveRole(null)} open={pendingRemoveRole !== null}>
184+
<DialogContent variant="destructive">
185+
<DialogHeader>
186+
<DialogTitle>Remove role</DialogTitle>
187+
</DialogHeader>
188+
<DialogDescription>
189+
Remove role <strong>{pendingRemoveRole}</strong> from user <strong>{userName}</strong>? The user will lose
190+
all permissions granted by this role.
191+
</DialogDescription>
192+
<DialogFooter>
193+
<Button onClick={() => setPendingRemoveRole(null)} variant="outline">
194+
Cancel
195+
</Button>
196+
<Button
197+
disabled={isPending}
198+
onClick={() => pendingRemoveRole && removeFromRole(pendingRemoveRole)}
199+
testId="confirm-remove-role-button"
200+
variant="destructive"
201+
>
202+
Remove
203+
</Button>
204+
</DialogFooter>
205+
</DialogContent>
206+
</Dialog>
207+
</>
170208
);
171209
};

0 commit comments

Comments
 (0)