Skip to content

Commit

Permalink
Merge pull request #236 from zilliztech/user_roles
Browse files Browse the repository at this point in the history
Support create/edit user role on user page
  • Loading branch information
shanghaikid authored Jul 31, 2023
2 parents 19c0140 + 70e1e21 commit 011ab44
Show file tree
Hide file tree
Showing 12 changed files with 444 additions and 141 deletions.
16 changes: 16 additions & 0 deletions client/src/http/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {
UpdateUserParams,
CreateRoleParams,
DeleteRoleParams,
AssignRoleParams,
UnassignRoleParams,
} from '../pages/user/Types';
import BaseModel from './BaseModel';

Expand Down Expand Up @@ -45,6 +47,20 @@ export class UserHttp extends BaseModel {
return super.delete({ path: `${this.USER_URL}/roles/${data.roleName}` });
}

static updateUserRole(data: AssignRoleParams) {
return super.update({
path: `${this.USER_URL}/${data.username}/role/update`,
data,
});
}

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

get _names() {
return this.names;
}
Expand Down
7 changes: 5 additions & 2 deletions client/src/i18n/en/user.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const userTrans = {
createTitle: 'Create User',
updateTitle: 'Update Milvus User',
updateRoleTitle: 'Update User Roles',
user: 'User',
users: 'Users',
deleteWarning: 'You are trying to drop user. This action cannot be undone.',
Expand All @@ -10,11 +11,13 @@ const userTrans = {
update: 'Update password',
isNotSame: 'Not same as new password',
deleteTip:
'Please select at least one item to drop and root can not be dropped.',

'Please select at least one item to drop and the root user can not be dropped.',
deleteEditRoleTip: 'root role is not editable.',
role: 'Role',
editRole: 'Edit Role',
roles: 'Roles',
createRoleTitle: 'Create Role',
updateRoleSuccess: 'User Role',
};

export default userTrans;
61 changes: 58 additions & 3 deletions client/src/pages/user/CreateUser.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { makeStyles, Theme } from '@material-ui/core';
import {
makeStyles,
Theme,
Checkbox,
FormGroup,
FormControlLabel,
Typography,
} from '@material-ui/core';
import { FC, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import DialogTemplate from '@/components/customDialog/DialogTemplate';
Expand All @@ -7,14 +14,25 @@ import { ITextfieldConfig } from '@/components/customInput/Types';
import { useFormValidation } from '@/hooks/Form';
import { formatForm } from '@/utils/Form';
import { CreateUserProps, CreateUserParams } from './Types';
import { Option as RoleOption } from '@/components/customSelector/Types';

const useStyles = makeStyles((theme: Theme) => ({
input: {
margin: theme.spacing(3, 0, 0.5),
margin: theme.spacing(2, 0, 0.5),
},
dialogWrapper: {
maxWidth: theme.spacing(70),
'& .MuiFormControlLabel-root': {
width: theme.spacing(20),
},
},
}));

const CreateUser: FC<CreateUserProps> = ({ handleCreate, handleClose }) => {
const CreateUser: FC<CreateUserProps> = ({
handleCreate,
handleClose,
roleOptions,
}) => {
const { t: commonTrans } = useTranslation();
const { t: userTrans } = useTranslation('user');
const { t: btnTrans } = useTranslation('btn');
Expand All @@ -24,10 +42,14 @@ const CreateUser: FC<CreateUserProps> = ({ handleCreate, handleClose }) => {
const [form, setForm] = useState<CreateUserParams>({
username: '',
password: '',
roles: [],
});

// selected Role
const checkedForm = useMemo(() => {
return formatForm(form);
}, [form]);

const { validation, checkIsValid, disabled } = useFormValidation(checkedForm);

const classes = useStyles();
Expand Down Expand Up @@ -87,6 +109,7 @@ const CreateUser: FC<CreateUserProps> = ({ handleCreate, handleClose }) => {
confirmLabel={btnTrans('create')}
handleConfirm={handleCreateUser}
confirmDisabled={disabled}
dialogClass={classes.dialogWrapper}
>
<>
{createConfigs.map(v => (
Expand All @@ -98,6 +121,38 @@ const CreateUser: FC<CreateUserProps> = ({ handleCreate, handleClose }) => {
key={v.label}
/>
))}

<Typography variant="h5" component="span">
{userTrans('roles')}
</Typography>

<FormGroup row>
{roleOptions.map((r: RoleOption, index: number) => (
<FormControlLabel
control={
<Checkbox
onChange={(
e: React.ChangeEvent<HTMLInputElement>,
checked: boolean
) => {
let newRoles = [...form.roles];

if (!checked) {
newRoles = newRoles.filter((n: string) => n === r.value);
} else {
newRoles.push(String(r.value));
}

setForm(v => ({ ...v, roles: [...newRoles] }));
}}
/>
}
key={index}
label={r.label}
value={r.value}
/>
))}
</FormGroup>
</>
</DialogTemplate>
);
Expand Down
25 changes: 25 additions & 0 deletions client/src/pages/user/Types.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,33 @@
import { Option as RoleOption } from '@/components/customSelector/Types';

export interface UserData {
name: string;
roleName?: string;
roles: string[];
}

export interface CreateUserParams {
username: string;
password: string;
roles: string[];
}

export interface UpdateUserRoleParams {
username: string;
roles: string[];
}

export interface UpdateUserRoleProps {
onUpdate: (data: UpdateUserRoleParams) => void;
handleClose: () => void;
username: string;
roles: string[];
}

export interface CreateUserProps {
handleCreate: (data: CreateUserParams) => void;
handleClose: () => void;
roleOptions: RoleOption[];
}

export interface UpdateUserProps {
Expand Down Expand Up @@ -41,6 +59,13 @@ export interface DeleteRoleParams {
roleName: string;
}

export interface AssignRoleParams {
username: string;
roles: string[];
}

export interface UnassignRoleParams extends AssignRoleParams {}

export interface RoleData {
name: string;
}
Expand Down
103 changes: 103 additions & 0 deletions client/src/pages/user/UpdateUserRole.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import {
makeStyles,
Theme,
Checkbox,
FormGroup,
FormControlLabel,
} from '@material-ui/core';
import { FC, useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import DialogTemplate from '@/components/customDialog/DialogTemplate';
import { UpdateUserRoleProps, UpdateUserRoleParams } from './Types';
import { UserHttp } from '@/http/User';

const useStyles = makeStyles((theme: Theme) => ({
input: {
margin: theme.spacing(2, 0, 0.5),
},
dialogWrapper: {
maxWidth: theme.spacing(70),
'& .MuiFormControlLabel-root': {
width: theme.spacing(20),
},
},
}));

const UpdateUserRole: FC<UpdateUserRoleProps> = ({
onUpdate,
handleClose,
roles,
username,
}) => {
const { t: userTrans } = useTranslation('user');
const { t: btnTrans } = useTranslation('btn');
const [roleOptions, setRoleOptions] = useState([]);

const [form, setForm] = useState<UpdateUserRoleParams>({
username: username,
roles: roles,
});

const classes = useStyles();

const handleUpdate = async () => {
await UserHttp.updateUserRole(form);
onUpdate(form);
};

const fetchAllRoles = async () => {
const roles = await UserHttp.getRoles();

setRoleOptions(roles.results.map((r: any) => r.role.name));
};

useEffect(() => {
fetchAllRoles();
}, []);

return (
<DialogTemplate
title={userTrans('updateRoleTitle')}
handleClose={handleClose}
confirmLabel={btnTrans('update')}
handleConfirm={handleUpdate}
confirmDisabled={false}
dialogClass={classes.dialogWrapper}
>
<>
<FormGroup row>
{roleOptions.map((roleOption: string, index: number) => (
<FormControlLabel
control={
<Checkbox
onChange={(
e: React.ChangeEvent<HTMLInputElement>,
checked: boolean
) => {
let newRoles = [...form.roles];

if (!checked) {
newRoles = newRoles.filter(
(n: string | number) => n !== roleOption
);
} else {
newRoles.push(roleOption);
}

setForm(v => ({ ...v, roles: [...newRoles] }));
}}
/>
}
key={index}
label={roleOption}
value={roleOption}
checked={form.roles.indexOf(roleOption) !== -1}
/>
))}
</FormGroup>
</>
</DialogTemplate>
);
};

export default UpdateUserRole;
Loading

0 comments on commit 011ab44

Please sign in to comment.