chore(authentication): some improvements

CB-109
This commit is contained in:
Wroud
2020-05-30 18:14:32 +03:00
parent 7bfac2b95a
commit abd51ea425
9 changed files with 136 additions and 23 deletions
@@ -4,5 +4,8 @@
"authentication_create_user_dialog_create": "Create",
"authentication_user_name": "User name",
"authentication_user_password": "User password",
"authentication_user_role": "User role"
"authentication_user_password_repeat": "Repeat password",
"authentication_user_role": "User role",
"authentication_user_passwords_not_match": "Passwords don't match",
"authentication_user_user_created": "User was created successfully"
}
@@ -4,5 +4,8 @@
"authentication_create_user_dialog_create": "Создать",
"authentication_user_name": "Имя",
"authentication_user_password": "Пароль",
"authentication_user_role": "Роль"
"authentication_user_password_repeat": "Повторите пароль",
"authentication_user_role": "Роль",
"authentication_user_passwords_not_match": "Пароли не совпадают",
"authentication_user_user_created": "Пользователь создан успешно"
}
@@ -11,7 +11,7 @@ import { useCallback } from 'react';
import styled, { css } from 'reshadow';
import {
SubmittingForm, ErrorMessage, InputField
SubmittingForm, ErrorMessage, InputField, Checkbox
} from '@dbeaver/core/blocks';
import { useController } from '@dbeaver/core/di';
import { DialogComponent, CommonDialogWrapper } from '@dbeaver/core/dialogs';
@@ -37,7 +37,6 @@ const styles = composes(
}
SubmittingForm {
overflow: auto;
margin: auto;
margin-top: 20px;
margin-bottom: 20px;
}
@@ -47,9 +46,13 @@ const styles = composes(
flex-direction: column;
}
create-form {
flex: 2;
flex-direction: column;
padding: 18px 24px;
}
fill {
flex: 1;
}
ErrorMessage {
position: sticky;
bottom: 0;
@@ -70,8 +73,12 @@ export const CreateUserDialog: DialogComponent<null, null> = observer(
(value: string) => controller.credentials.password = value,
[]
);
const handlePasswordRepeatChange = useCallback(
(value: string) => controller.credentials.passwordRepeat = value,
[]
);
const handleRoleChange = useCallback(
(value: string) => controller.credentials.roles = value,
(roleId: string, value: boolean) => controller.credentials.roles.set(roleId, value),
[]
);
@@ -82,6 +89,7 @@ export const CreateUserDialog: DialogComponent<null, null> = observer(
footer={(
<CreateUserDialogFooter
isCreating={controller.isCreating}
onCancel={props.rejectDialog}
onCreate={controller.create}
/>
)}
@@ -115,18 +123,33 @@ export const CreateUserDialog: DialogComponent<null, null> = observer(
</group>
<group as="div">
<InputField
type='role'
name='text'
value={controller.credentials.roles}
placeholder={controller.roles.join(' ')}
onChange={handleRoleChange}
type='password'
name='password_repeat'
value={controller.credentials.passwordRepeat}
onChange={handlePasswordRepeatChange}
disabled={controller.isCreating}
mod='surface'
>
{translate('authentication_user_role')}
{translate('authentication_user_password_repeat')}
</InputField>
</group>
{controller.roles.map((role, i) => (
<group as="div" key={role.roleId}>
<Checkbox
type='checkbox'
name='role'
checkboxLabel={role.roleName || role.roleId}
onChange={checked => handleRoleChange(role.roleId, checked)}
checked={controller.credentials.roles.get(role.roleId)}
disabled={controller.isCreating}
mod='surface'
>
{i === 0 && translate('authentication_user_role')}
</Checkbox>
</group>
))}
</create-form>
<fill as="div"/>
</SubmittingForm>
{controller.error.responseMessage && (
<ErrorMessage
@@ -24,11 +24,12 @@ export class CreateUserDialogController implements IInitializableController, IDe
@observable credentials = {
login: '',
password: '',
roles: '',
passwordRepeat: '',
roles: new Map<string, boolean>(),
};
@computed get roles() {
return this.rolesManagerService.roles.data.map(role => role.roleId);
return this.rolesManagerService.roles.data;
}
readonly error = new GQLErrorCatcher();
private isDistructed = false;
@@ -54,6 +55,10 @@ export class CreateUserDialogController implements IInitializableController, IDe
if (this.isCreating) {
return;
}
if (this.credentials.password !== this.credentials.passwordRepeat) {
this.notificationService.logError({ title: 'authentication_user_passwords_not_match' });
return;
}
this.isCreating = true;
let isUserCreated = false;
@@ -61,17 +66,13 @@ export class CreateUserDialogController implements IInitializableController, IDe
const user = await this.usersManagerService.create(this.credentials.login, false);
isUserCreated = !!user;
await this.usersManagerService.updateCredentials(user.userId, { password: this.credentials.password });
if (this.credentials.roles) {
const roles = this.credentials.roles
.trim()
.replace(/\s+/g, ' ')
.split(' ');
for (const role of roles) {
await this.usersManagerService.grantRole(user.userId, role);
for (const [roleId, checked] of this.credentials.roles) {
if (checked) {
await this.usersManagerService.grantRole(user.userId, roleId);
}
}
await this.usersManagerService.users.refresh(user.userId);
this.notificationService.logInfo({ title: 'authentication_user_user_created' });
this.close();
} catch (exception) {
if (isUserCreated) {
@@ -21,22 +21,37 @@ const styles = css`
margin: auto;
justify-content: flex-end;
}
fill {
flex: 1;
}
`;
export type Props = {
isCreating: boolean;
onCancel(): void;
onCreate(): void;
}
export const CreateUserDialogFooter = observer(
function CreateUserDialogFooter({
isCreating,
onCancel,
onCreate,
}: Props) {
const translate = useTranslate();
return styled(styles)(
<controls as="div">
<Button
type="button"
mod={['outlined']}
onClick={onCancel}
disabled={isCreating}
>
{translate('ui_processing_cancel')}
</Button>
<fill as="div"/>
<Button
type="button"
mod={['unelevated']}
@@ -63,7 +63,7 @@ export function Snackbar({
<notification as="div" {...use({ mounted, closing })} >
<notification-header as="div">
{type && <NotificationMark type={type} />}
<message as="div">{text}</message>
<message as="div">{translate(text)}</message>
{!closeAfter && onClose && (
<IconButton onClick={onClose} name="cross" viewBox="0 0 16 16" />
)}
@@ -0,0 +1,67 @@
/*
* cloudbeaver - Cloud Database Manager
* Copyright (C) 2020 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import styled, { css, use } from 'reshadow';
import { useStyles } from '@dbeaver/core/theming';
const styles = css`
label {
composes: theme-typography--body1 from global;
}
field {
display: flex;
flex: auto;
box-sizing: border-box;
align-items: center;
padding: 12px 0;
& label {
width: 230px;
text-align: right;
padding: 0 12px;
line-height: 16px;
font-weight: 500;
}
& input {
flex: 1;
margin: 0 12px;
height: 26px;
}
& checkbox {
display: flex;
align-items: center;
}
}
`;
type Props = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange'> & {
checkboxLabel: string;
mod?: 'surface';
onChange?(value: boolean): any;
}
export function Checkbox({
checkboxLabel,
children,
className,
mod,
onChange = () => {},
...rest
}: Props) {
return styled(useStyles(styles))(
<field as="div" className={className}>
<label>{children}</label>
<checkbox as='div'>
<input onChange={e => onChange(e.target.checked)} {...rest} {...use({ mod })} />
<checkbox-label as='div'>{checkboxLabel}</checkbox-label>
</checkbox>
</field>
);
}
@@ -22,7 +22,7 @@ const styles = css`
padding: 12px 0;
& label {
width: 150px;
width: 230px;
text-align: right;
padding: 0 12px;
line-height: 16px;
+1
View File
@@ -12,6 +12,7 @@ export * from './Split';
export * from './Table';
export * from './Tabs';
export * from './Button';
export * from './Checkbox';
export * from './ErrorMessage';
export * from './IconButton';
export * from './IconOrImage';