Browse Source

support force delete role

Signed-off-by: ruiyi.jiang <ruiyi.jiang@zilliz.com>
ruiyi.jiang 1 year ago
parent
commit
e3b02d7276

+ 26 - 2
client/src/components/customDialog/DeleteDialogTemplate.tsx

@@ -5,6 +5,8 @@ import {
   TextField,
   Theme,
   Typography,
+  Checkbox,
+  FormControlLabel,
 } from '@material-ui/core';
 import { ChangeEvent, FC, useContext, useState } from 'react';
 import { useTranslation } from 'react-i18next';
@@ -36,16 +38,19 @@ const useStyles = makeStyles((theme: Theme) => ({
   cancelBtn: {
     color: theme.palette.attuGrey.dark,
   },
+  checkBox: {},
 }));
 
 const DeleteTemplate: FC<DeleteDialogContentType> = props => {
-  const { title, text, label, handleDelete, handleCancel } = props;
+  const { title, text, label, handleDelete, handleCancel, forceDelLabel } =
+    props;
   const { handleCloseDialog } = useContext(rootContext);
   const classes = useStyles();
   const { t: dialogTrans } = useTranslation('dialog');
   const { t: btnTrans } = useTranslation('btn');
 
   const [value, setValue] = useState<string>('');
+  const [force, setForce] = useState<boolean>(false);
   const [deleteReady, setDeleteReady] = useState<boolean>(false);
 
   const onCancelClick = () => {
@@ -54,7 +59,7 @@ const DeleteTemplate: FC<DeleteDialogContentType> = props => {
   };
 
   const onDeleteClick = (event: React.FormEvent<HTMLFormElement>) => {
-    handleDelete();
+    handleDelete(force);
     event.preventDefault();
   };
 
@@ -100,6 +105,25 @@ const DeleteTemplate: FC<DeleteDialogContentType> = props => {
             variant="filled"
             fullWidth={true}
           />
+          {forceDelLabel ? (
+            <FormControlLabel
+              control={
+                <Checkbox
+                  onChange={(
+                    e: React.ChangeEvent<HTMLInputElement>,
+                    checked: boolean
+                  ) => {
+                    setForce(checked);
+                  }}
+                />
+              }
+              key={'force'}
+              label={forceDelLabel}
+              value={true}
+              checked={force}
+              className={classes.checkBox}
+            />
+          ) : null}
         </DialogContent>
 
         <DialogActions className={classes.btnWrapper}>

+ 2 - 1
client/src/components/customDialog/Types.ts

@@ -18,7 +18,8 @@ export type DeleteDialogContentType = {
   text: string;
   label: string;
   handleCancel?: () => void;
-  handleDelete: () => void;
+  handleDelete: (force?: boolean) => void;
+  forceDelLabel?: string;
 };
 
 export type DialogContainerProps = {

+ 5 - 2
client/src/http/BaseModel.ts

@@ -70,8 +70,11 @@ export default class BaseModel {
   }
 
   static async delete(options: updateParamsType) {
-    const { path } = options;
-    const res = await http.delete(path);
+    const { path, data } = options;
+
+    console.log('xxx', data);
+
+    const res = await http.delete(path, { data: data });
 
     return res.data;
   }

+ 1 - 1
client/src/http/User.ts

@@ -63,7 +63,7 @@ export class UserHttp extends BaseModel {
 
   // delete a role
   static deleteRole(data: DeleteRoleParams) {
-    return super.delete({ path: `${this.ROLES_URL}/${data.roleName}` });
+    return super.delete({ path: `${this.ROLES_URL}/${data.roleName}`, data });
   }
 
   // get all roles

+ 2 - 0
client/src/i18n/en/user.ts

@@ -30,6 +30,8 @@ const userTrans = {
   objectCollection: 'Collection',
   objectGlobal: 'Global',
   objectUser: 'User',
+
+  forceDelLabel: 'Force delete, revoke all privileges.',
 };
 
 export default userTrans;

+ 32 - 5
client/src/pages/user/Roles.tsx

@@ -1,5 +1,5 @@
 import { useContext, useEffect, useState } from 'react';
-import { makeStyles, Theme } from '@material-ui/core';
+import { makeStyles, Theme, Chip } from '@material-ui/core';
 import { useTranslation } from 'react-i18next';
 import { UserHttp } from '@/http/User';
 import AttuGrid from '@/components/grid/Grid';
@@ -15,6 +15,9 @@ const useStyles = makeStyles((theme: Theme) => ({
   wrapper: {
     height: `calc(100vh - 160px)`,
   },
+  chip: {
+    marginRight: theme.spacing(0.5),
+  },
 }));
 
 const Roles = () => {
@@ -37,6 +40,20 @@ const Roles = () => {
     setRoles(
       roles.results.map((v: any) => ({
         name: v.role.name,
+        privilegeContent: (
+          <>
+            {v.entities.map((e: any) => {
+              return (
+                <Chip
+                  className={classes.chip}
+                  size="small"
+                  label={e.grantor.privilege.name}
+                  variant="outlined"
+                />
+              );
+            })}
+          </>
+        ),
         privileges: v.entities.map((e: any) => ({
           roleName: v.role.name,
           object: e.object.name,
@@ -47,16 +64,18 @@ const Roles = () => {
     );
   };
 
-  const onUpdate = async () => {
+  const onUpdate = async (data: { isEditing: boolean }) => {
     fetchRoles();
     openSnackBar(successTrans('create', { name: userTrans('role') }));
     handleCloseDialog();
   };
 
-  const handleDelete = async () => {
+  const handleDelete = async (force?: boolean) => {
+    console.log('for', force);
     for (const role of selectedRole) {
       const param: DeleteRoleParams = {
         roleName: role.name,
+        force,
       };
       await UserHttp.deleteRole(param);
     }
@@ -126,6 +145,7 @@ const Roles = () => {
                 title={dialogTrans('deleteTitle', { type: userTrans('role') })}
                 text={userTrans('deleteWarning')}
                 handleDelete={handleDelete}
+                forceDelLabel={userTrans('forceDelLabel')}
               />
             ),
           },
@@ -134,9 +154,9 @@ const Roles = () => {
       label: '',
       disabled: () =>
         selectedRole.length === 0 ||
-        selectedRole.findIndex(v => v.name === 'root') > -1,
+        selectedRole.findIndex(v => v.name === 'admin') > -1 ||
+        selectedRole.findIndex(v => v.name === 'public') > -1,
       disabledTooltip: userTrans('deleteTip'),
-
       icon: 'delete',
     },
   ];
@@ -148,6 +168,13 @@ const Roles = () => {
       disablePadding: false,
       label: userTrans('role'),
     },
+
+    {
+      id: 'privilegeContent',
+      align: 'left',
+      disablePadding: false,
+      label: userTrans('privileges'),
+    },
   ];
 
   const handleSelectChange = (value: RoleData[]) => {

+ 2 - 3
client/src/pages/user/Types.ts

@@ -64,13 +64,14 @@ export interface RoleData {
 }
 
 export interface CreateRoleProps {
-  onUpdate: (data: CreateRoleParams) => void;
+  onUpdate: (data: { data: CreateRoleParams; isEditing: boolean }) => void;
   handleClose: () => void;
   role?: RoleData;
 }
 
 export interface DeleteRoleParams {
   roleName: string;
+  force?: boolean;
 }
 
 export interface AssignRoleParams {
@@ -80,8 +81,6 @@ export interface AssignRoleParams {
 
 export interface UnassignRoleParams extends AssignRoleParams {}
 
-
-
 export enum TAB_EMUM {
   'schema',
   'partition',

+ 1 - 1
client/src/pages/user/UpdateRoleDialog.tsx

@@ -113,7 +113,7 @@ const UpdateRoleDialog: FC<CreateRoleProps> = ({
     console.log('form', form);
     await UserHttp.updateRolePrivileges(form);
 
-    onUpdate(form);
+    onUpdate({ data: form, isEditing: isEditing });
   };
 
   const onChange = (newSelection: any) => {

+ 6 - 15
server/src/users/users.controller.ts

@@ -135,7 +135,12 @@ export class UserController {
 
   async deleteRole(req: Request, res: Response, next: NextFunction) {
     const { roleName } = req.params;
+    const { force } = req.body;
+
     try {
+      if (force) {
+        await this.userService.revokeAllRolePrivileges({ roleName });
+      }
       const result = await this.userService.deleteRole({ roleName });
       res.send(result);
     } catch (error) {
@@ -225,22 +230,8 @@ export class UserController {
     const results = [];
 
     try {
-      // get existing privileges
-      const existingPrivileges = await this.userService.listGrants({
-        roleName,
-      });
-
       // revoke all
-      for (let i = 0; i < existingPrivileges.entities.length; i++) {
-        const res = existingPrivileges.entities[i];
-        const result = await this.userService.revokeRolePrivilege({
-          object: res.object.name,
-          objectName: res.object_name,
-          privilegeName: res.grantor.privilege.name,
-          roleName: res.role.name,
-        });
-        results.push(result);
-      }
+      this.userService.revokeAllRolePrivileges({ roleName });
 
       // assign new user roles
       for (let i = 0; i < privileges.length; i++) {

+ 18 - 0
server/src/users/users.service.ts

@@ -122,4 +122,22 @@ export class UserService {
     throwErrorFromSDK(res);
     return res;
   }
+
+  async revokeAllRolePrivileges(data: { roleName: string }) {
+    // get existing privileges
+    const existingPrivileges = await this.listGrants({
+      roleName: data.roleName,
+    });
+
+    // revoke all
+    for (let i = 0; i < existingPrivileges.entities.length; i++) {
+      const res = existingPrivileges.entities[i];
+      await this.revokeRolePrivilege({
+        object: res.object.name,
+        objectName: res.object_name,
+        privilegeName: res.grantor.privilege.name,
+        roleName: res.role.name,
+      });
+    }
+  }
 }