@@ -21,12 +21,21 @@ import {
2121 EmptyTitle ,
2222} from 'components/redpanda-ui/components/empty' ;
2323import { ExternalLinkIcon , Trash2Icon } from 'lucide-react' ;
24+ import { useState } from 'react' ;
2425
2526import { UpdateRoleMembershipRequestSchema } from '../../../../protogen/redpanda/api/dataplane/v1/security_pb' ;
2627import { useListRolesQuery , useUpdateRoleMembershipMutation } from '../../../../react-query/api/security' ;
2728import { rolesApi } from '../../../../state/backend-api' ;
2829import { Button } from '../../../redpanda-ui/components/button' ;
2930import { 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' ;
3039import { ListLayout , ListLayoutContent , ListLayoutFilters } from '../../../redpanda-ui/components/list-layout' ;
3140import { Skeleton } from '../../../redpanda-ui/components/skeleton' ;
3241import { Table , TableBody , TableCell , TableHead , TableHeader , TableRow } from '../../../redpanda-ui/components/table' ;
@@ -44,8 +53,9 @@ type UserRolesCardNewProps = {
4453} ;
4554
4655export 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