diff --git a/webapp/packages/core-administration/src/Administration/Administration.tsx b/webapp/packages/core-administration/src/Administration/Administration.tsx index c839f6f6c5..f370775733 100644 --- a/webapp/packages/core-administration/src/Administration/Administration.tsx +++ b/webapp/packages/core-administration/src/Administration/Administration.tsx @@ -16,8 +16,8 @@ import { composes, useStyles } from '@cloudbeaver/core-theming'; import { AdministrationItemService, filterOnlyActive } from '../AdministrationItem/AdministrationItemService'; import type { IAdministrationItemRoute } from '../AdministrationItem/IAdministrationItemRoute'; +import { ContentSlider } from './ContentSlider'; import { DrawerItem } from './DrawerItem'; -import { ItemContent } from './ItemContent'; const tabsStyles = composes( css` @@ -53,7 +53,7 @@ const administrationStyles = composes( padding-top: 16px; border-right: 2px solid; } - content { + content-container { flex: 1; display: flex; flex-direction: column; @@ -95,13 +95,10 @@ export const Administration = observer(function Administration({ /> ))} - + {children} - - + + ); diff --git a/webapp/packages/core-administration/src/Administration/ContentSlider.tsx b/webapp/packages/core-administration/src/Administration/ContentSlider.tsx new file mode 100644 index 0000000000..98cb10b690 --- /dev/null +++ b/webapp/packages/core-administration/src/Administration/ContentSlider.tsx @@ -0,0 +1,67 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2021 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 { observer } from 'mobx-react-lite'; +import styled, { css } from 'reshadow'; + +import { SlideBox, SlideElement, slideBoxStyles, SlideOverlay, ErrorBoundary } from '@cloudbeaver/core-blocks'; +import { useService } from '@cloudbeaver/core-di'; +import { useStyles } from '@cloudbeaver/core-theming'; +import { OptionsPanelService } from '@cloudbeaver/core-ui'; + +import type { IAdministrationItemRoute } from '../AdministrationItem/IAdministrationItemRoute'; +import { ItemContent } from './ItemContent'; + +const styles = css` + SlideBox { + flex: 1; + } + SlideElement { + flex: 1; + } + content { + height: 100%; + display: flex; + flex-direction: column; + overflow: auto; + white-space: normal; + } +`; + +interface Props { + activeScreen: IAdministrationItemRoute | null; + configurationWizard: boolean; +} + +export const ContentSlider = observer(function ContentSlider({ activeScreen, configurationWizard }) { + const optionsPanelService = useService(OptionsPanelService); + const OptionsPanel = optionsPanelService.getPanelComponent(); + + return styled(useStyles(styles, slideBoxStyles))( + + + + + + + + + + + + + + + optionsPanelService.close()} /> + + + ); +}); diff --git a/webapp/packages/core-localization/src/locales/en.ts b/webapp/packages/core-localization/src/locales/en.ts index 4e7ee22d01..9bfd3c316f 100644 --- a/webapp/packages/core-localization/src/locales/en.ts +++ b/webapp/packages/core-localization/src/locales/en.ts @@ -58,6 +58,7 @@ export const defaultENLocale = [ ['ui_file', 'File'], ['ui_changes_will_be_lost', 'Your changes will be lost'], ['ui_continue', 'Continue'], + ['ui_authentication', 'Authentication'], ['root_permission_denied', "You don't have permissions"], ['root_permission_no_permission', "You don't have permission for this action"], diff --git a/webapp/packages/core-localization/src/locales/ru.ts b/webapp/packages/core-localization/src/locales/ru.ts index f4c2c88c9a..b7e51f9fd6 100644 --- a/webapp/packages/core-localization/src/locales/ru.ts +++ b/webapp/packages/core-localization/src/locales/ru.ts @@ -56,6 +56,7 @@ export const defaultRULocale = [ ['ui_file', 'Файл'], ['ui_changes_will_be_lost', 'Ваши изменения будут потеряны'], ['ui_continue', 'Продолжить'], + ['ui_authentication', 'Аутентификация'], ['root_permission_denied', 'Отказано в доступе'], ['root_permission_no_permission', 'У вас нет разрешения на это действие'], diff --git a/webapp/packages/plugin-authentication-administration/src/PluginBootstrap.ts b/webapp/packages/plugin-authentication-administration/src/PluginBootstrap.ts index 8b15e21dcf..b944805ff2 100644 --- a/webapp/packages/plugin-authentication-administration/src/PluginBootstrap.ts +++ b/webapp/packages/plugin-authentication-administration/src/PluginBootstrap.ts @@ -9,7 +9,7 @@ import { AdministrationTopAppBarService } from '@cloudbeaver/core-administration'; import { injectable, Bootstrap } from '@cloudbeaver/core-di'; import { ServerConfigurationService } from '@cloudbeaver/plugin-administration'; -import { UserInfo } from '@cloudbeaver/plugin-authentication'; +import { UserMenu } from '@cloudbeaver/plugin-authentication'; import { AuthenticationProviders } from './Administration/ServerConfiguration/AuthenticationProviders'; @@ -23,7 +23,7 @@ export class PluginBootstrap extends Bootstrap { } register(): void { - this.administrationTopAppBarService.placeholder.add(UserInfo, 4); + this.administrationTopAppBarService.placeholder.add(UserMenu, 4); this.serverConfigurationService.configurationContainer.add(AuthenticationProviders, 0); } diff --git a/webapp/packages/plugin-authentication/src/PluginBootstrap.ts b/webapp/packages/plugin-authentication/src/PluginBootstrap.ts index da05abce04..8c0a168cc7 100644 --- a/webapp/packages/plugin-authentication/src/PluginBootstrap.ts +++ b/webapp/packages/plugin-authentication/src/PluginBootstrap.ts @@ -7,49 +7,28 @@ */ import { SettingsMenuService, TopNavService } from '@cloudbeaver/core-app'; -import { AuthInfoService, AUTH_PROVIDER_LOCAL_ID } from '@cloudbeaver/core-authentication'; +import { AuthInfoService } from '@cloudbeaver/core-authentication'; import { injectable, Bootstrap } from '@cloudbeaver/core-di'; -import { CommonDialogService } from '@cloudbeaver/core-dialogs'; import { ServerConfigResource } from '@cloudbeaver/core-root'; -import { DATA_CONTEXT_MENU, DATA_CONTEXT_MENU_NESTED, MenuBaseItem, MenuService } from '@cloudbeaver/core-view'; import { AuthenticationService } from './AuthenticationService'; import { AuthDialogService } from './Dialog/AuthDialogService'; -import { UserInfo } from './UserInfo'; -import { ChangeUserPasswordDialog } from './UserMenu/ChangeUserPasswordDialog/ChangeUserPasswordDialog'; -import { MENU_USER_PROFILE } from './UserMenu/MENU_USER_PROFILE'; +import { UserMenu } from './UserMenu/UserMenu'; @injectable() export class PluginBootstrap extends Bootstrap { constructor( - private serverConfigResource: ServerConfigResource, - private authDialogService: AuthDialogService, - private authenticationService: AuthenticationService, - private authInfoService: AuthInfoService, - private settingsMenuService: SettingsMenuService, - private topNavService: TopNavService, - private menuService: MenuService, - private readonly commonDialogService: CommonDialogService, + private readonly serverConfigResource: ServerConfigResource, + private readonly authDialogService: AuthDialogService, + private readonly authenticationService: AuthenticationService, + private readonly authInfoService: AuthInfoService, + private readonly settingsMenuService: SettingsMenuService, + private readonly topNavService: TopNavService, ) { super(); } register(): void { - this.menuService.addCreator({ - isApplicable: - context => { - const profile = context.get(DATA_CONTEXT_MENU) === MENU_USER_PROFILE - && !context.get(DATA_CONTEXT_MENU_NESTED); - - return profile && !!this.authInfoService.userInfo?.linkedAuthProviders.includes(AUTH_PROVIDER_LOCAL_ID); - }, - getItems: (context, items) => [ - ...items, - new MenuBaseItem('change-password', 'authentication_user_password_change_menu_title', undefined, { - onSelect: () => this.commonDialogService.open(ChangeUserPasswordDialog, null), - }), - ], - }); this.settingsMenuService.addMenuItem( SettingsMenuService.settingsMenuToken, { @@ -71,7 +50,7 @@ export class PluginBootstrap extends Bootstrap { onClick: this.authenticationService.logout.bind(this.authenticationService), } ); - this.topNavService.placeholder.add(UserInfo, 4); + this.topNavService.placeholder.add(UserMenu, 4); } load(): void | Promise { } diff --git a/webapp/packages/plugin-authentication/src/UserInfo.tsx b/webapp/packages/plugin-authentication/src/UserInfo.tsx index fc36a9633f..07faf5d199 100644 --- a/webapp/packages/plugin-authentication/src/UserInfo.tsx +++ b/webapp/packages/plugin-authentication/src/UserInfo.tsx @@ -9,16 +9,10 @@ import { observer } from 'mobx-react-lite'; import styled, { css } from 'reshadow'; -import { topMenuStyles } from '@cloudbeaver/core-app'; -import { AuthInfoService, DATA_CONTEXT_USER } from '@cloudbeaver/core-authentication'; import { IconOrImage } from '@cloudbeaver/core-blocks'; -import { useService } from '@cloudbeaver/core-di'; -import { useStyles } from '@cloudbeaver/core-theming'; -import { ContextMenu } from '@cloudbeaver/core-ui'; -import { useMenu } from '@cloudbeaver/core-view'; - -import { MENU_USER_PROFILE } from './UserMenu/MENU_USER_PROFILE'; -import { userMenuStyles } from './UserMenu/userMenuStyles'; +import { useTranslate } from '@cloudbeaver/core-localization'; +import type { UserInfo as IUserInfo } from '@cloudbeaver/core-sdk'; +import { composes, useStyles } from '@cloudbeaver/core-theming'; const styles = css` user { @@ -37,24 +31,37 @@ const styles = css` } `; -export const UserInfo = observer(function UserInfo() { - const authInfoService = useService(AuthInfoService); - const style = useStyles(styles, userMenuStyles); - const menu = useMenu(MENU_USER_PROFILE); - menu.context.set(DATA_CONTEXT_USER, authInfoService.userInfo); +const detachedStyles = composes( + css` + user { + composes: theme-ripple from global; + } + `, + css` + user { + padding: 0 16px; + cursor: pointer; + } + ` +); - if (!authInfoService.userInfo) { - return null; - } +interface Props { + info: IUserInfo; + detached?: boolean; + tooltip?: string; + onClick?: () => void; +} + +export const UserInfo = observer(function UserInfo({ info, detached, tooltip, onClick }) { + const translate = useTranslate(); + const style = useStyles(styles, detached && detachedStyles); return styled(style)( - - - - - - {authInfoService.userInfo.displayName || authInfoService.userInfo.userId} - - + + + + + {info.displayName || info.userId} + ); }); diff --git a/webapp/packages/plugin-authentication/src/UserMenu/ChangeUserPasswordDialog/ChangeUserPasswordDialog.tsx b/webapp/packages/plugin-authentication/src/UserMenu/ChangeUserPasswordDialog/ChangeUserPasswordDialog.tsx deleted file mode 100644 index 4858f2b075..0000000000 --- a/webapp/packages/plugin-authentication/src/UserMenu/ChangeUserPasswordDialog/ChangeUserPasswordDialog.tsx +++ /dev/null @@ -1,157 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2021 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 { observable } from 'mobx'; -import { observer } from 'mobx-react-lite'; -import { useState, useCallback, useEffect } from 'react'; -import styled, { css } from 'reshadow'; - -import { UsersResource } from '@cloudbeaver/core-authentication'; -import { BASE_CONTAINERS_STYLES, Container, ErrorMessage, Group, InputField, SubmittingForm, useErrorDetails, useFocus } from '@cloudbeaver/core-blocks'; -import { useService } from '@cloudbeaver/core-di'; -import { CommonDialogWrapper, DialogComponent, DialogComponentProps } from '@cloudbeaver/core-dialogs'; -import { NotificationService } from '@cloudbeaver/core-events'; -import { useTranslate } from '@cloudbeaver/core-localization'; -import { GQLErrorCatcher } from '@cloudbeaver/core-sdk'; -import { composes, useStyles } from '@cloudbeaver/core-theming'; - -import { ChangeUserPasswordDialogFooter } from './ChangeUserPasswordDialogFooter'; - -const styles = composes( - css` - ErrorMessage { - composes: theme-background-secondary theme-text-on-secondary from global; - } -`, - css` - ErrorMessage { - flex: 1; - } -`); - -interface IState { - oldPassword: string; - newPassword: string; - repeatedPassword: string; - error: GQLErrorCatcher; - customError: string; - submitting: boolean; -} - -export const ChangeUserPasswordDialog: DialogComponent = observer( - function ChangeUserPasswordDialog({ - payload, - resolveDialog, - rejectDialog, - }: DialogComponentProps) { - const [focusedRef] = useFocus({}); - const usersResource = useService(UsersResource); - const notificationService = useService(NotificationService); - const style = useStyles(styles, BASE_CONTAINERS_STYLES); - const translate = useTranslate(); - const [state] = useState(() => observable({ - oldPassword: '', newPassword: '', repeatedPassword: '', error: new GQLErrorCatcher(), customError: '', submitting: false, - })); - const { details: errorDetails, open: openErrorDetails } = useErrorDetails(state.error.exception); - const formFilled - = state.newPassword.length > 0 && state.repeatedPassword.length > 0 && state.oldPassword.length > 0; - const disabled = state.submitting; - - const change = useCallback(async () => { - state.error.clear(); - - if (state.newPassword !== state.repeatedPassword) { - state.customError = 'authentication_user_passwords_not_match'; - return; - } - - try { - state.submitting = true; - await usersResource.updateLocalPassword(state.oldPassword, state.newPassword); - notificationService.logSuccess({ title: 'authentication_user_password_change_success' }); - resolveDialog(); - } catch (exeption) { - if (!state.error.catch(exeption)) { - state.customError = exeption.message; - } - } finally { - state.submitting = false; - } - }, [state, resolveDialog, notificationService, usersResource]); - - useEffect(() => { - focusedRef.current?.focus(); - }, []); - - return styled(style)( - - {(state.error.responseMessage || state.customError) && ( - - )} - - )} - onReject={rejectDialog} - > - - - - value.trim())} - small - required - > - {translate('authentication_user_current_password')} - - value.trim())} - small - required - > - {translate('authentication_user_new_password')} - - value.trim())} - small - required - > - {translate('authentication_user_password_repeat')} - - - - - - ); - } -); diff --git a/webapp/packages/plugin-authentication/src/UserMenu/ChangeUserPasswordDialog/ChangeUserPasswordDialogFooter.tsx b/webapp/packages/plugin-authentication/src/UserMenu/ChangeUserPasswordDialog/ChangeUserPasswordDialogFooter.tsx deleted file mode 100644 index 497d4b5209..0000000000 --- a/webapp/packages/plugin-authentication/src/UserMenu/ChangeUserPasswordDialog/ChangeUserPasswordDialogFooter.tsx +++ /dev/null @@ -1,60 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2021 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 { observer } from 'mobx-react-lite'; -import styled, { css } from 'reshadow'; - -import { Button } from '@cloudbeaver/core-blocks'; -import { useTranslate } from '@cloudbeaver/core-localization'; - -const styles = css` - footer-container { - display: flex; - width: min-content; - flex: 1; - align-items: center; - justify-content: flex-end; - } - footer-container > :not(:first-child) { - margin-left: 16px; - } - Button { - flex: 0 0 auto; - } -`; - -export interface Props { - onChange: () => void; - onCancel: () => void; - formFilled: boolean; - submitting: boolean; - className?: string; -} - -export const ChangeUserPasswordDialogFooter = observer(function ChangeUserPasswordDialogFooter({ - onChange, - onCancel, - formFilled, - submitting, - className, - children, -}) { - const translate = useTranslate(); - - return styled(styles)( - - {children} - - - - ); -}); diff --git a/webapp/packages/plugin-authentication/src/UserMenu/UserMenu.tsx b/webapp/packages/plugin-authentication/src/UserMenu/UserMenu.tsx new file mode 100644 index 0000000000..b5dcb5953b --- /dev/null +++ b/webapp/packages/plugin-authentication/src/UserMenu/UserMenu.tsx @@ -0,0 +1,49 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2021 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 { observer } from 'mobx-react-lite'; +import styled from 'reshadow'; + +import { topMenuStyles } from '@cloudbeaver/core-app'; +import { AuthInfoService, DATA_CONTEXT_USER } from '@cloudbeaver/core-authentication'; +import { useService } from '@cloudbeaver/core-di'; +import { useStyles } from '@cloudbeaver/core-theming'; +import { ContextMenu } from '@cloudbeaver/core-ui'; +import { MenuBaseItem, useMenu } from '@cloudbeaver/core-view'; + +import { UserInfo } from '../UserInfo'; +import { MENU_USER_PROFILE } from './MENU_USER_PROFILE'; +import { userMenuStyles } from './userMenuStyles'; + +export const UserMenu = observer(function UserMenu() { + const style = useStyles(userMenuStyles); + const authInfoService = useService(AuthInfoService); + const menu = useMenu(MENU_USER_PROFILE); + + if (!authInfoService.userInfo) { + return null; + } + + const items = menu.getItems().filter(item => !item.hidden); + + if (items.length === 1 && items[0] instanceof MenuBaseItem) { + const item = items[0]; + + if (item.events?.onSelect) { + return ; + } + } + + menu.context.set(DATA_CONTEXT_USER, authInfoService.userInfo); + + return styled(style)( + + + + ); +}); diff --git a/webapp/packages/plugin-authentication/src/index.ts b/webapp/packages/plugin-authentication/src/index.ts index 6021388650..df66f5b51e 100644 --- a/webapp/packages/plugin-authentication/src/index.ts +++ b/webapp/packages/plugin-authentication/src/index.ts @@ -5,4 +5,4 @@ export default manifest; export * from './AuthenticationService'; export * from './Dialog/AuthDialogService'; export * from './UserMenu/MENU_USER_PROFILE'; -export * from './UserInfo'; +export * from './UserMenu/UserMenu'; diff --git a/webapp/packages/plugin-authentication/src/locales/en.ts b/webapp/packages/plugin-authentication/src/locales/en.ts index 6c57594053..aa6ace1219 100644 --- a/webapp/packages/plugin-authentication/src/locales/en.ts +++ b/webapp/packages/plugin-authentication/src/locales/en.ts @@ -14,8 +14,6 @@ export default [ ['authentication_user_name', 'User name'], ['authentication_user_credentials', 'Credentials'], ['authentication_user_meta_parameters', 'Parameters'], - ['authentication_user_current_password', 'Current password'], - ['authentication_user_new_password', 'New password'], ['authentication_user_password', 'User password'], ['authentication_user_password_repeat', 'Repeat password'], ['authentication_user_role', 'User role'], @@ -25,10 +23,6 @@ export default [ ['authentication_user_passwords_not_match', "Passwords don't match"], ['authentication_user_login_already_exists', 'The user with this name already exists'], ['authentication_user_login_cant_be_used', 'Sorry, that name cannot be used'], - ['authentication_user_password_change_dialog_title', 'Change password (local)'], - ['authentication_user_password_change_menu_title', 'Change local password'], - ['authentication_user_password_change_success', 'Password was changed successfully'], - ['authentication_user_password_change_dialog_submit', 'Change'], ['authentication_user_icon_tooltip', 'User'], ['authentication_role_icon_tooltip', 'Role'], ]; diff --git a/webapp/packages/plugin-authentication/src/locales/it.ts b/webapp/packages/plugin-authentication/src/locales/it.ts index d71ad04365..afba380f87 100644 --- a/webapp/packages/plugin-authentication/src/locales/it.ts +++ b/webapp/packages/plugin-authentication/src/locales/it.ts @@ -8,8 +8,6 @@ export default [ ['authentication_user_name', 'Nome utente'], ['authentication_user_credentials', 'Credenziali'], ['authentication_user_meta_parameters', 'Parameters'], - ['authentication_user_current_password', 'Password corrente'], - ['authentication_user_new_password', 'Nuova password'], ['authentication_user_password', 'Password utente'], ['authentication_user_password_repeat', 'Ripeti password'], ['authentication_user_role', 'Ruolo utente'], @@ -19,10 +17,6 @@ export default [ ['authentication_user_passwords_not_match', 'Le Passwords non coincidono'], ['authentication_user_login_already_exists', 'Esiste già un utente con quel nome'], ['authentication_user_login_cant_be_used', 'Quel nome non può essere usato'], - ['authentication_user_password_change_dialog_title', 'Modifica la password (locale)'], - ['authentication_user_password_change_menu_title', 'Modifica la password locale'], - ['authentication_user_password_change_success', 'Password modificata con successo'], - ['authentication_user_password_change_dialog_submit', 'Modifica'], ['authentication_user_icon_tooltip', 'Utente'], ['authentication_role_icon_tooltip', 'Ruolo'], ]; diff --git a/webapp/packages/plugin-authentication/src/locales/ru.ts b/webapp/packages/plugin-authentication/src/locales/ru.ts index 497a054823..739eeef0b5 100644 --- a/webapp/packages/plugin-authentication/src/locales/ru.ts +++ b/webapp/packages/plugin-authentication/src/locales/ru.ts @@ -14,8 +14,6 @@ export default [ ['authentication_user_name', 'Имя'], ['authentication_user_credentials', 'Учетные данные'], ['authentication_user_meta_parameters', 'Параметры'], - ['authentication_user_current_password', 'Текущий пароль'], - ['authentication_user_new_password', 'Новый пароль'], ['authentication_user_password', 'Пароль'], ['authentication_user_password_repeat', 'Повторите пароль'], ['authentication_user_role', 'Роль'], @@ -25,10 +23,6 @@ export default [ ['authentication_user_passwords_not_match', 'Пароли не совпадают'], ['authentication_user_login_already_exists', 'Пользователь с таким именем уже существует'], ['authentication_user_login_cant_be_used', 'Данное имя нельзя использовать'], - ['authentication_user_password_change_dialog_title', 'Изменение пароля (локального)'], - ['authentication_user_password_change_menu_title', 'Изменить локальный пароль'], - ['authentication_user_password_change_success', 'Пароль успешно изменен'], - ['authentication_user_password_change_dialog_submit', 'Изменить'], ['authentication_user_icon_tooltip', 'Пользователь'], ['authentication_role_icon_tooltip', 'Роль'], ]; diff --git a/webapp/packages/plugin-user-profile/src/UserProfileForm/Authentication/AuthenticationPanel.tsx b/webapp/packages/plugin-user-profile/src/UserProfileForm/Authentication/AuthenticationPanel.tsx new file mode 100644 index 0000000000..48d873063e --- /dev/null +++ b/webapp/packages/plugin-user-profile/src/UserProfileForm/Authentication/AuthenticationPanel.tsx @@ -0,0 +1,34 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2021 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 { observer } from 'mobx-react-lite'; +import styled from 'reshadow'; + +import { BASE_CONTAINERS_STYLES, ColoredContainer, Container, TabPanel } from '@cloudbeaver/core-blocks'; +import { ComponentStyle, useStyles } from '@cloudbeaver/core-theming'; + +import { ChangePassword } from './ChangePassword/ChangePassword'; + +interface Props { + className?: string; + style?: ComponentStyle; +} + +export const AuthenticationPanel = observer(function AuthenticationPanel({ className, style }) { + const styles = useStyles(style, BASE_CONTAINERS_STYLES); + + return styled(styles)( + + + + + + + + ); +}); diff --git a/webapp/packages/plugin-user-profile/src/UserProfileForm/Authentication/AuthenticationTab.tsx b/webapp/packages/plugin-user-profile/src/UserProfileForm/Authentication/AuthenticationTab.tsx new file mode 100644 index 0000000000..ab63b0bca0 --- /dev/null +++ b/webapp/packages/plugin-user-profile/src/UserProfileForm/Authentication/AuthenticationTab.tsx @@ -0,0 +1,28 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2021 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 { observer } from 'mobx-react-lite'; +import styled from 'reshadow'; + +import { Tab } from '@cloudbeaver/core-blocks'; +import { useTranslate } from '@cloudbeaver/core-localization'; +import { ComponentStyle, useStyles } from '@cloudbeaver/core-theming'; + +interface Props { + className?: string; + style?: ComponentStyle; +} + +export const AuthenticationTab = observer(function AuthenticationTab({ className, style }) { + const translate = useTranslate(); + const styles = useStyles(style); + + return styled(styles)( + {translate('ui_authentication')} + ); +}); diff --git a/webapp/packages/plugin-user-profile/src/UserProfileForm/Authentication/ChangePassword/ChangePassword.tsx b/webapp/packages/plugin-user-profile/src/UserProfileForm/Authentication/ChangePassword/ChangePassword.tsx new file mode 100644 index 0000000000..a7481cbff2 --- /dev/null +++ b/webapp/packages/plugin-user-profile/src/UserProfileForm/Authentication/ChangePassword/ChangePassword.tsx @@ -0,0 +1,74 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2021 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 { observer } from 'mobx-react-lite'; +import styled from 'reshadow'; + +import { BASE_CONTAINERS_STYLES, Button, Group, GroupItem, GroupTitle, InputField } from '@cloudbeaver/core-blocks'; +import { useTranslate } from '@cloudbeaver/core-localization'; +import { useStyles } from '@cloudbeaver/core-theming'; + +import { useChangePassword } from './useChangePassword'; + +export const ChangePassword = observer(function ChangePassword() { + const translate = useTranslate(); + const state = useChangePassword(); + + const disabled = state.submitting; + + return styled(useStyles(BASE_CONTAINERS_STYLES))( + + {translate('plugin_user_profile_authentication_change_password')} + value.trim())} + small + required + > + {translate('plugin_user_profile_authentication_change_password_current_password')} + + value.trim())} + small + required + > + {translate('plugin_user_profile_authentication_change_password_new_password')} + + value.trim())} + small + required + > + {translate('plugin_user_profile_authentication_change_password_repeat_password')} + + + + + + ); +}); diff --git a/webapp/packages/plugin-user-profile/src/UserProfileForm/Authentication/ChangePassword/useChangePassword.ts b/webapp/packages/plugin-user-profile/src/UserProfileForm/Authentication/ChangePassword/useChangePassword.ts new file mode 100644 index 0000000000..713d839891 --- /dev/null +++ b/webapp/packages/plugin-user-profile/src/UserProfileForm/Authentication/ChangePassword/useChangePassword.ts @@ -0,0 +1,76 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2021 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 { action, computed, observable } from 'mobx'; + +import { UsersResource } from '@cloudbeaver/core-authentication'; +import { useObservableRef } from '@cloudbeaver/core-blocks'; +import { useService } from '@cloudbeaver/core-di'; +import { NotificationService } from '@cloudbeaver/core-events'; + +interface IConfig { + oldPassword: string; + password: string; + repeatedPassword: string; +} + +interface IState { + config: IConfig; + submitting: boolean; + readonly formFilled: boolean; + changePassword: () => Promise; + resetConfig: () => void; +} + +export function useChangePassword(): IState { + const usersResource = useService(UsersResource); + const notificationService = useService(NotificationService); + + return useObservableRef(() => ({ + config: { + oldPassword: '', + password: '', + repeatedPassword: '', + }, + submitting: false, + get formFilled() { + return this.config.password.length > 0 && this.config.oldPassword.length > 0 + && this.config.repeatedPassword.length > 0; + }, + async changePassword() { + if (this.config.password !== this.config.repeatedPassword) { + this.notificationService.logError({ title: 'plugin_user_profile_authentication_change_password_passwords_not_match' }); + return; + } + + try { + this.submitting = true; + await usersResource.updateLocalPassword(this.config.oldPassword, this.config.password); + notificationService.logSuccess({ title: 'plugin_user_profile_authentication_change_password_success' }); + this.resetConfig(); + } catch (exception) { + notificationService.logException(exception); + } finally { + this.submitting = false; + } + }, + resetConfig() { + this.config.oldPassword = ''; + this.config.password = ''; + this.config.repeatedPassword = ''; + }, + }), + { + config: observable, + submitting: observable.ref, + formFilled: computed, + changePassword: action.bound, + resetConfig: action, + }, + { usersResource, notificationService }); +} diff --git a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserForm.tsx b/webapp/packages/plugin-user-profile/src/UserProfileForm/UserForm.tsx index ac92b1addd..961d1b1a94 100644 --- a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserForm.tsx +++ b/webapp/packages/plugin-user-profile/src/UserProfileForm/UserForm.tsx @@ -9,11 +9,14 @@ import { observer } from 'mobx-react-lite'; import styled, { css } from 'reshadow'; +import { AUTH_PROVIDER_LOCAL_ID } from '@cloudbeaver/core-authentication'; import { BASE_CONTAINERS_STYLES, Button, IconOrImage, TabList, TabsState, UNDERLINE_TAB_STYLES } from '@cloudbeaver/core-blocks'; import { useTranslate } from '@cloudbeaver/core-localization'; import type { UserInfo } from '@cloudbeaver/core-sdk'; import { composes, useStyles } from '@cloudbeaver/core-theming'; +import { AuthenticationPanel } from './Authentication/AuthenticationPanel'; +import { AuthenticationTab } from './Authentication/AuthenticationTab'; import type { IUserProfileFormState } from './IUserProfileFormState'; import { UserInfoPanel } from './UserInfo/UserInfoPanel'; import { UserInfoTab } from './UserInfo/UserInfoTab'; @@ -126,6 +129,8 @@ export const UserForm = observer(function UserForm({ const style = [tabsStyles, UNDERLINE_TAB_STYLES]; const styles = useStyles(style, BASE_CONTAINERS_STYLES, topBarStyles, formStyles); + const localProvider = user.linkedAuthProviders.includes(AUTH_PROVIDER_LOCAL_ID); + return styled(styles)( @@ -141,6 +146,7 @@ export const UserForm = observer(function UserForm({ + {localProvider && } {/* */} @@ -158,6 +164,7 @@ export const UserForm = observer(function UserForm({ + {localProvider && } {/* */} diff --git a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserInfo/UserInfoPanel.tsx b/webapp/packages/plugin-user-profile/src/UserProfileForm/UserInfo/UserInfoPanel.tsx index 6ee13b9790..faf97d0b0d 100644 --- a/webapp/packages/plugin-user-profile/src/UserProfileForm/UserInfo/UserInfoPanel.tsx +++ b/webapp/packages/plugin-user-profile/src/UserProfileForm/UserInfo/UserInfoPanel.tsx @@ -45,7 +45,6 @@ export const UserInfoPanel = observer(function UserInfoPanel({ minLength={1} state={user} mod='surface' - disabled readOnly required tiny @@ -59,7 +58,6 @@ export const UserInfoPanel = observer(function UserInfoPanel({ minLength={1} state={user} mod='surface' - disabled readOnly required tiny @@ -68,14 +66,12 @@ export const UserInfoPanel = observer(function UserInfoPanel({ {translate('plugin_user_profile_info_displayName')} - {() => userMetaParameters.data.length > 0 && styled(styles)( (function UserInfoPanel({ - ); diff --git a/webapp/packages/plugin-user-profile/src/locales/en.ts b/webapp/packages/plugin-user-profile/src/locales/en.ts index d424952bac..0317590459 100644 --- a/webapp/packages/plugin-user-profile/src/locales/en.ts +++ b/webapp/packages/plugin-user-profile/src/locales/en.ts @@ -7,4 +7,12 @@ export default [ ['plugin_user_profile_info', 'User Info'], ['plugin_user_profile_auth_tokens', 'Auth Tokens'], ['plugin_user_profile_auth_providers_active', 'Authentication is active'], + + ['plugin_user_profile_authentication_change_password', 'Change password'], + ['plugin_user_profile_authentication_change_password_current_password', 'Current password'], + ['plugin_user_profile_authentication_change_password_new_password', 'New password'], + ['plugin_user_profile_authentication_change_password_repeat_password', 'Repeat password'], + ['plugin_user_profile_authentication_change_password_success', 'Password was changed successfully'], + ['plugin_user_profile_authentication_change_password_submit_label', 'Change'], + ['plugin_user_profile_authentication_change_password_passwords_not_match', "Passwords don't match"], ]; diff --git a/webapp/packages/plugin-user-profile/src/locales/it.ts b/webapp/packages/plugin-user-profile/src/locales/it.ts index d424952bac..b6e2d18a81 100644 --- a/webapp/packages/plugin-user-profile/src/locales/it.ts +++ b/webapp/packages/plugin-user-profile/src/locales/it.ts @@ -7,4 +7,12 @@ export default [ ['plugin_user_profile_info', 'User Info'], ['plugin_user_profile_auth_tokens', 'Auth Tokens'], ['plugin_user_profile_auth_providers_active', 'Authentication is active'], + + ['plugin_user_profile_authentication_change_password', 'Modifica la password'], + ['plugin_user_profile_authentication_change_password_current_password', 'Password corrente'], + ['plugin_user_profile_authentication_change_password_new_password', 'Nuova password'], + ['plugin_user_profile_authentication_change_password_repeat_password', 'Ripeti password'], + ['plugin_user_profile_authentication_change_password_success', 'Password modificata con successo'], + ['plugin_user_profile_authentication_change_password_submit_label', 'Modifica'], + ['plugin_user_profile_authentication_change_password_passwords_not_match', 'Le Passwords non coincidono'], ]; diff --git a/webapp/packages/plugin-user-profile/src/locales/ru.ts b/webapp/packages/plugin-user-profile/src/locales/ru.ts index 085f4b2ef7..e6c3f0dce4 100644 --- a/webapp/packages/plugin-user-profile/src/locales/ru.ts +++ b/webapp/packages/plugin-user-profile/src/locales/ru.ts @@ -7,4 +7,12 @@ export default [ ['plugin_user_profile_info', 'Информация'], ['plugin_user_profile_auth_tokens', 'Токены авторизации'], ['plugin_user_profile_auth_providers_active', 'Авторизация выполнена'], + + ['plugin_user_profile_authentication_change_password', 'Смена пароля'], + ['plugin_user_profile_authentication_change_password_current_password', 'Текущий пароль'], + ['plugin_user_profile_authentication_change_password_new_password', 'Новый пароль'], + ['plugin_user_profile_authentication_change_password_repeat_password', 'Повторите пароль'], + ['plugin_user_profile_authentication_change_password_success', 'Пароль был успешно изменен'], + ['plugin_user_profile_authentication_change_password_submit_label', 'Сменить'], + ['plugin_user_profile_authentication_change_password_passwords_not_match', 'Пароли не совпадают'], ];