Skip to content

Commit

Permalink
Merge pull request #238 from zilliztech/user_roles
Browse files Browse the repository at this point in the history
Support assign privilege
  • Loading branch information
shanghaikid authored Aug 2, 2023
2 parents 011ab44 + 99f6af4 commit 1ce967e
Show file tree
Hide file tree
Showing 18 changed files with 594 additions and 151 deletions.
28 changes: 26 additions & 2 deletions client/src/components/customDialog/DeleteDialogTemplate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 = () => {
Expand All @@ -54,7 +59,7 @@ const DeleteTemplate: FC<DeleteDialogContentType> = props => {
};

const onDeleteClick = (event: React.FormEvent<HTMLFormElement>) => {
handleDelete();
handleDelete(force);
event.preventDefault();
};

Expand Down Expand Up @@ -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}>
Expand Down
3 changes: 2 additions & 1 deletion client/src/components/customDialog/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ export type DeleteDialogContentType = {
text: string;
label: string;
handleCancel?: () => void;
handleDelete: () => void;
handleDelete: (force?: boolean) => void;
forceDelLabel?: string;
};

export type DialogContainerProps = {
Expand Down
5 changes: 3 additions & 2 deletions client/src/http/BaseModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,9 @@ export default class BaseModel {
}

static async delete(options: updateParamsType) {
const { path } = options;
const res = await http.delete(path);
const { path, data } = options;

const res = await http.delete(path, { data: data });

return res.data;
}
Expand Down
57 changes: 40 additions & 17 deletions client/src/http/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,50 +17,73 @@ export class UserHttp extends BaseModel {
Object.assign(this, props);
}

static USER_URL = `/users`;
static USERS_URL = `/users`;
static ROLES_URL = `/users/roles`;

// get user data
static getUsers() {
return super.search({ path: this.USER_URL, params: {} });
return super.search({ path: this.USERS_URL, params: {} });
}

// create user
static createUser(data: CreateUserParams) {
return super.create({ path: this.USER_URL, data });
return super.create({ path: this.USERS_URL, data });
}

// update user (pass)
static updateUser(data: UpdateUserParams) {
return super.update({ path: this.USER_URL, data });
return super.update({ path: this.USERS_URL, data });
}

// delete user
static deleteUser(data: DeleteUserParams) {
return super.delete({ path: `${this.USER_URL}/${data.username}` });
return super.delete({ path: `${this.USERS_URL}/${data.username}` });
}

static createRole(data: CreateRoleParams) {
return super.create({ path: `${this.USER_URL}/roles`, data });
// update user role
static updateUserRole(data: AssignRoleParams) {
return super.update({
path: `${this.USERS_URL}/${data.username}/role/update`,
data,
});
}

static getRoles() {
return super.search({ path: `${this.USER_URL}/roles`, params: {} });
// unassign user role
static unassignUserRole(data: UnassignRoleParams) {
return super.update({
path: `${this.USERS_URL}/${data.username}/role/unassign`,
data,
});
}

// create a role
static createRole(data: CreateRoleParams) {
return super.create({ path: `${this.ROLES_URL}`, data });
}

// delete a role
static deleteRole(data: DeleteRoleParams) {
return super.delete({ path: `${this.USER_URL}/roles/${data.roleName}` });
return super.delete({ path: `${this.ROLES_URL}/${data.roleName}`, data });
}

static updateUserRole(data: AssignRoleParams) {
return super.update({
path: `${this.USER_URL}/${data.username}/role/update`,
data,
});
// get all roles
static getRoles() {
return super.search({ path: `${this.ROLES_URL}`, params: {} });
}

static unassignUserRole(data: UnassignRoleParams) {
// update role privileges
static updateRolePrivileges(data: CreateRoleParams) {
return super.update({
path: `${this.USER_URL}/${data.username}/role/unassign`,
path: `${this.ROLES_URL}/${data.roleName}/updatePrivileges`,
data,
});
}

// get RBAC info
static getRBAC() {
return super.search({ path: `${this.USERS_URL}/rbac`, params: {} });
}

get _names() {
return this.names;
}
Expand Down
14 changes: 14 additions & 0 deletions client/src/i18n/en/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,26 @@ const userTrans = {
isNotSame: 'Not same as new password',
deleteTip:
'Please select at least one item to drop and the root user can not be dropped.',

// role
deleteEditRoleTip: 'root role is not editable.',
disableEditRolePrivilegeTip: 'admin and public role are not editable.',

role: 'Role',
editRole: 'Edit Role',
roles: 'Roles',
createRoleTitle: 'Create Role',
updateRolePrivilegeTitle: 'Update Role',
updateRoleSuccess: 'User Role',
type: 'Type',

// Privileges
privileges: 'Privileges',
objectCollection: 'Collection',
objectGlobal: 'Global',
objectUser: 'User',

forceDelLabel: 'Force delete, revoke all privileges.',
};

export default userTrans;
86 changes: 0 additions & 86 deletions client/src/pages/user/CreateRole.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion client/src/pages/user/CreateUser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { Option as RoleOption } from '@/components/customSelector/Types';

const useStyles = makeStyles((theme: Theme) => ({
input: {
margin: theme.spacing(2, 0, 0.5),
margin: theme.spacing(1, 0, 0.5),
},
dialogWrapper: {
maxWidth: theme.spacing(70),
Expand Down
79 changes: 79 additions & 0 deletions client/src/pages/user/PrivilegeOptions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import {
makeStyles,
Theme,
Typography,
Checkbox,
FormGroup,
FormControlLabel,
} from '@material-ui/core';
import { FC } from 'react';
import { Privilege, PrivilegeOptionsProps } from './Types';

const useStyles = makeStyles((theme: Theme) => ({
checkBox: {
width: theme.spacing(24),
},
formGrp: {
marginBottom: theme.spacing(2),
},
subTitle: {
marginBottom: theme.spacing(0.5),
},
}));

const PrivilegeOptions: FC<PrivilegeOptionsProps> = ({
options,
selection,
onChange,
title,
roleName,
object,
objectName = '*',
}) => {
const classes = useStyles();

return (
<>
<Typography variant="h6" component="h6" className={classes.subTitle}>
{title}
</Typography>
<FormGroup row className={classes.formGrp}>
{options.map((r: string) => (
<FormControlLabel
control={
<Checkbox
onChange={(
e: React.ChangeEvent<HTMLInputElement>,
checked: boolean
) => {
let newSelection = [...selection];

if (!checked) {
newSelection = newSelection.filter(
(n: Privilege) => n.privilegeName !== r
);
} else {
newSelection.push({
privilegeName: r,
object: object,
objectName: objectName,
roleName: roleName,
});
}
onChange(newSelection);
}}
/>
}
key={r}
label={r}
value={r}
checked={selection.filter((s: Privilege) => s.privilegeName === r).length > 0}
className={classes.checkBox}
/>
))}
</FormGroup>
</>
);
};

export default PrivilegeOptions;
Loading

0 comments on commit 1ce967e

Please sign in to comment.