diff --git a/webapp/packages/core-administration/src/Administration/Administration.tsx b/webapp/packages/core-administration/src/Administration/Administration.tsx index c839f6f6c5..7617b610ef 100644 --- a/webapp/packages/core-administration/src/Administration/Administration.tsx +++ b/webapp/packages/core-administration/src/Administration/Administration.tsx @@ -10,9 +10,10 @@ import { observer } from 'mobx-react-lite'; import { useLayoutEffect, useRef } from 'react'; import styled, { css } from 'reshadow'; -import { TabsState, TabList, verticalTabStyles } from '@cloudbeaver/core-blocks'; +import { TabsState, TabList, verticalTabStyles, SlideBox, SlideElement, ErrorBoundary, SlideOverlay, slideBoxStyles } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; import { composes, useStyles } from '@cloudbeaver/core-theming'; +import { OptionsPanelService } from '@cloudbeaver/core-ui'; import { AdministrationItemService, filterOnlyActive } from '../AdministrationItem/AdministrationItemService'; import type { IAdministrationItemRoute } from '../AdministrationItem/IAdministrationItemRoute'; @@ -53,12 +54,21 @@ const administrationStyles = composes( padding-top: 16px; border-right: 2px solid; } - content { + content-container { flex: 1; display: flex; flex-direction: column; overflow: auto; } + SlideBox { + flex: 1; + } + content { + height: 100%; + display: flex; + flex-direction: column; + overflow: auto; + } ` ); @@ -73,6 +83,9 @@ export const Administration = observer(function Administration({ }) { const contentRef = useRef(null); const administrationItemService = useService(AdministrationItemService); + const optionsPanelService = useService(OptionsPanelService); + + const OptionsPanel = optionsPanelService.getPanelComponent(); const items = administrationItemService.getActiveItems(configurationWizard); const hasOnlyActive = items.some(filterOnlyActive(configurationWizard)); @@ -80,7 +93,7 @@ export const Administration = observer(function Administration({ contentRef.current?.scrollTo({ top: 0, left: 0 }); }, [activeScreen?.item]); - return styled(useStyles(verticalTabStyles, administrationStyles, tabsStyles))( + return styled(useStyles(verticalTabStyles, administrationStyles, tabsStyles, slideBoxStyles))( @@ -95,13 +108,26 @@ export const Administration = observer(function Administration({ /> ))} - + {children} - - + + + + + + + + + + + + + + + optionsPanelService.close()} /> + + + ); diff --git a/webapp/packages/core-app/src/AppScreen/RightArea.tsx b/webapp/packages/core-app/src/AppScreen/RightArea.tsx index 11e679dac2..c9ebc2e803 100644 --- a/webapp/packages/core-app/src/AppScreen/RightArea.tsx +++ b/webapp/packages/core-app/src/AppScreen/RightArea.tsx @@ -47,9 +47,6 @@ const styles = composes( SlideBox { flex: 1; } - SlideElement { - display: flex; - } ` ); diff --git a/webapp/packages/core-blocks/src/Slide/SlideBoxStyles.ts b/webapp/packages/core-blocks/src/Slide/SlideBoxStyles.ts index 7084e21600..ded44bb8b6 100644 --- a/webapp/packages/core-blocks/src/Slide/SlideBoxStyles.ts +++ b/webapp/packages/core-blocks/src/Slide/SlideBoxStyles.ts @@ -22,6 +22,7 @@ export const slideBoxStyles = composes( height: 100%; display: inline-block; vertical-align:top; + white-space: normal; transition: transform cubic-bezier(0.4, 0.0, 0.2, 1) 0.6s; transform: translateX(calc(-100% + 120px)); 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..ca535dffbb 100644 --- a/webapp/packages/plugin-authentication-administration/src/PluginBootstrap.ts +++ b/webapp/packages/plugin-authentication-administration/src/PluginBootstrap.ts @@ -6,24 +6,20 @@ * you may not use this file except in compliance with the License. */ -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 { AuthenticationProviders } from './Administration/ServerConfiguration/AuthenticationProviders'; @injectable() export class PluginBootstrap extends Bootstrap { constructor( - private administrationTopAppBarService: AdministrationTopAppBarService, private readonly serverConfigurationService: ServerConfigurationService, ) { super(); } register(): void { - this.administrationTopAppBarService.placeholder.add(UserInfo, 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..b77d099f44 100644 --- a/webapp/packages/plugin-authentication/src/PluginBootstrap.ts +++ b/webapp/packages/plugin-authentication/src/PluginBootstrap.ts @@ -6,50 +6,27 @@ * you may not use this file except in compliance with the License. */ -import { SettingsMenuService, TopNavService } from '@cloudbeaver/core-app'; -import { AuthInfoService, AUTH_PROVIDER_LOCAL_ID } from '@cloudbeaver/core-authentication'; +import { SettingsMenuService } from '@cloudbeaver/core-app'; +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'; @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, ) { 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 +48,6 @@ export class PluginBootstrap extends Bootstrap { onClick: this.authenticationService.logout.bind(this.authenticationService), } ); - this.topNavService.placeholder.add(UserInfo, 4); } load(): void | Promise { } diff --git a/webapp/packages/plugin-authentication/src/UserInfo.tsx b/webapp/packages/plugin-authentication/src/UserInfo.tsx deleted file mode 100644 index fc36a9633f..0000000000 --- a/webapp/packages/plugin-authentication/src/UserInfo.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 { 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'; - -const styles = css` - user { - height: 100%; - display: flex; - align-items: center; - } - IconOrImage { - display: block; - width: 24px; - } - user-name { - display: block; - line-height: initial; - margin-left: 8px; - } -`; - -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); - - if (!authInfoService.userInfo) { - return null; - } - - return styled(style)( - - - - - - {authInfoService.userInfo.displayName || authInfoService.userInfo.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/index.ts b/webapp/packages/plugin-authentication/src/index.ts index 6021388650..7af0d5bcc7 100644 --- a/webapp/packages/plugin-authentication/src/index.ts +++ b/webapp/packages/plugin-authentication/src/index.ts @@ -4,5 +4,3 @@ export default manifest; export * from './AuthenticationService'; export * from './Dialog/AuthDialogService'; -export * from './UserMenu/MENU_USER_PROFILE'; -export * from './UserInfo'; 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-administration/.gitignore b/webapp/packages/plugin-user-profile-administration/.gitignore new file mode 100644 index 0000000000..15bc16c7c3 --- /dev/null +++ b/webapp/packages/plugin-user-profile-administration/.gitignore @@ -0,0 +1,17 @@ +# dependencies +/node_modules + +# testing +/coverage + +# production +/lib + +# misc +.DS_Store +.env* + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/webapp/packages/plugin-user-profile-administration/package.json b/webapp/packages/plugin-user-profile-administration/package.json new file mode 100644 index 0000000000..d889c291c8 --- /dev/null +++ b/webapp/packages/plugin-user-profile-administration/package.json @@ -0,0 +1,46 @@ +{ + "name": "@cloudbeaver/plugin-user-profile-administration", + "sideEffects": false, + "version": "0.1.0", + "description": "", + "license": "Apache-2.0", + "main": "lib/index.js", + "module": "lib/index.js", + "types": "src/index.ts", + "scripts": { + "prebuild": "rimraf lib/*", + "build": "webpack --config ../../configs/webpack.plugin.config.js", + "postbuild": "tsc", + "lint": "eslint ./src/ --ext .ts,.tsx", + "lint-fix": "eslint ./src/ --ext .ts,.tsx --fix" + }, + "peerDependencies": { + "@cloudbeaver/core-administration": "^0.1.0", + "@cloudbeaver/core-app": "^0.1.0", + "@cloudbeaver/core-authentication": "^0.1.0", + "@cloudbeaver/core-blocks": "^0.1.0", + "@cloudbeaver/core-connections": "^0.1.0", + "@cloudbeaver/core-di": "^0.1.0", + "@cloudbeaver/core-dialogs": "^0.1.0", + "@cloudbeaver/core-events": "^0.1.0", + "@cloudbeaver/core-executor": "^0.1.0", + "@cloudbeaver/core-extensions": "^0.1.0", + "@cloudbeaver/core-localization": "^0.1.0", + "@cloudbeaver/core-notifications": "^0.1.0", + "@cloudbeaver/core-plugin": "^0.1.0", + "@cloudbeaver/core-product": "^0.1.0", + "@cloudbeaver/core-root": "^0.1.0", + "@cloudbeaver/core-routing": "^0.1.0", + "@cloudbeaver/core-sdk": "^0.1.0", + "@cloudbeaver/core-settings": "^0.1.0", + "@cloudbeaver/core-theming": "^0.1.0", + "@cloudbeaver/core-ui": "^0.1.0", + "@cloudbeaver/core-utils": "^0.1.0", + "@cloudbeaver/core-view": "^0.1.0", + "@cloudbeaver/plugin-user-profile": "^0.1.0", + "react": "^16.x.x", + "mobx": "^6.x.x", + "mobx-react-lite": "^3.x.x", + "reshadow": "^0.x.x" + } +} diff --git a/webapp/packages/plugin-user-profile-administration/src/PluginBootstrap.ts b/webapp/packages/plugin-user-profile-administration/src/PluginBootstrap.ts new file mode 100644 index 0000000000..1a21c1e563 --- /dev/null +++ b/webapp/packages/plugin-user-profile-administration/src/PluginBootstrap.ts @@ -0,0 +1,26 @@ +/* + * 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 { AdministrationTopAppBarService } from '@cloudbeaver/core-administration'; +import { Bootstrap, injectable } from '@cloudbeaver/core-di'; +import { UserMenu } from '@cloudbeaver/plugin-user-profile'; + +@injectable() +export class PluginBootstrap extends Bootstrap { + constructor( + private readonly administrationTopAppBarService: AdministrationTopAppBarService + ) { + super(); + } + + register(): void { + this.administrationTopAppBarService.placeholder.add(UserMenu, 4); + } + + load(): void { } +} diff --git a/webapp/packages/plugin-user-profile-administration/src/index.ts b/webapp/packages/plugin-user-profile-administration/src/index.ts new file mode 100644 index 0000000000..b9c556470a --- /dev/null +++ b/webapp/packages/plugin-user-profile-administration/src/index.ts @@ -0,0 +1,3 @@ +import { userProfileAdministrationPlugin } from './manifest'; + +export default userProfileAdministrationPlugin; diff --git a/webapp/packages/plugin-user-profile-administration/src/manifest.ts b/webapp/packages/plugin-user-profile-administration/src/manifest.ts new file mode 100644 index 0000000000..01e6b9837e --- /dev/null +++ b/webapp/packages/plugin-user-profile-administration/src/manifest.ts @@ -0,0 +1,21 @@ +/* + * 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 type { PluginManifest } from '@cloudbeaver/core-di'; + +import { PluginBootstrap } from './PluginBootstrap'; + +export const userProfileAdministrationPlugin: PluginManifest = { + info: { + name: 'User profile administration plugin', + }, + + providers: [ + PluginBootstrap, + ], +}; diff --git a/webapp/packages/plugin-user-profile-administration/tsconfig.json b/webapp/packages/plugin-user-profile-administration/tsconfig.json new file mode 100644 index 0000000000..8cd6b539e3 --- /dev/null +++ b/webapp/packages/plugin-user-profile-administration/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "lib" + }, + "include": ["src"], + "exclude": ["**/node_modules", "**/.*/"], +} diff --git a/webapp/packages/plugin-user-profile/src/PluginBootstrap.ts b/webapp/packages/plugin-user-profile/src/PluginBootstrap.ts index 3534f9c154..847d1c8496 100644 --- a/webapp/packages/plugin-user-profile/src/PluginBootstrap.ts +++ b/webapp/packages/plugin-user-profile/src/PluginBootstrap.ts @@ -6,34 +6,22 @@ * you may not use this file except in compliance with the License. */ +import { TopNavService } from '@cloudbeaver/core-app'; import { Bootstrap, injectable } from '@cloudbeaver/core-di'; -import { DATA_CONTEXT_MENU, DATA_CONTEXT_MENU_NESTED, MenuBaseItem, MenuService } from '@cloudbeaver/core-view'; -import { MENU_USER_PROFILE } from '@cloudbeaver/plugin-authentication'; -import { UserProfileService } from './UserProfileService'; +import { UserMenu } from './UserMenu/UserMenu'; @injectable() export class PluginBootstrap extends Bootstrap { constructor( - private menuService: MenuService, - private userProfileService: UserProfileService + private readonly topNavService: TopNavService ) { super(); } register(): void { - this.menuService.addCreator({ - isApplicable: - context => context.get(DATA_CONTEXT_MENU) === MENU_USER_PROFILE - && !context.get(DATA_CONTEXT_MENU_NESTED), - getItems: (context, items) => [ - new MenuBaseItem('profile', 'plugin_user_profile_menu', undefined, { - onSelect: () => this.userProfileService.open(), - }), - ...items, - ], - }); + this.topNavService.placeholder.add(UserMenu, 4); } - load(): void {} + load(): void { } } diff --git a/webapp/packages/plugin-user-profile/src/UserInfo.tsx b/webapp/packages/plugin-user-profile/src/UserInfo.tsx new file mode 100644 index 0000000000..3cde6b8181 --- /dev/null +++ b/webapp/packages/plugin-user-profile/src/UserInfo.tsx @@ -0,0 +1,62 @@ +/* + * 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 { IconOrImage } from '@cloudbeaver/core-blocks'; +import { useService } from '@cloudbeaver/core-di'; +import { useTranslate } from '@cloudbeaver/core-localization'; +import type { UserInfo as IUserInfo } from '@cloudbeaver/core-sdk'; +import { composes, useStyles } from '@cloudbeaver/core-theming'; + +import { UserProfileService } from './UserProfileService'; + +const styles = composes( + css` + user { + composes: theme-ripple from global; + } + `, + css` + user { + height: 100%; + display: flex; + align-items: center; + padding: 0 16px; + cursor: pointer; + } + IconOrImage { + display: block; + width: 24px; + } + user-name { + display: block; + line-height: initial; + margin-left: 8px; + } +`); + +interface Props { + info: IUserInfo; +} + +export const UserInfo = observer(function UserInfo({ info }) { + const translate = useTranslate(); + const style = useStyles(styles); + const userProfileService = useService(UserProfileService); + + return styled(style)( + userProfileService.open()}> + + + + {info.displayName || info.userId} + + ); +}); diff --git a/webapp/packages/plugin-authentication/src/UserMenu/MENU_USER_PROFILE.ts b/webapp/packages/plugin-user-profile/src/UserMenu/MENU_USER_PROFILE.ts similarity index 100% rename from webapp/packages/plugin-authentication/src/UserMenu/MENU_USER_PROFILE.ts rename to webapp/packages/plugin-user-profile/src/UserMenu/MENU_USER_PROFILE.ts diff --git a/webapp/packages/plugin-user-profile/src/UserMenu/UserMenu.tsx b/webapp/packages/plugin-user-profile/src/UserMenu/UserMenu.tsx new file mode 100644 index 0000000000..6bcf9f79dd --- /dev/null +++ b/webapp/packages/plugin-user-profile/src/UserMenu/UserMenu.tsx @@ -0,0 +1,43 @@ +/* + * 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 { Icon } 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 { 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); + + menu.context.set(DATA_CONTEXT_USER, authInfoService.userInfo); + + if (!authInfoService.userInfo) { + return null; + } + + return styled(style)( + <> + + + + + + ); +}); diff --git a/webapp/packages/plugin-authentication/src/UserMenu/userMenuStyles.ts b/webapp/packages/plugin-user-profile/src/UserMenu/userMenuStyles.ts similarity index 75% rename from webapp/packages/plugin-authentication/src/UserMenu/userMenuStyles.ts rename to webapp/packages/plugin-user-profile/src/UserMenu/userMenuStyles.ts index a94658ac4a..2535327964 100644 --- a/webapp/packages/plugin-authentication/src/UserMenu/userMenuStyles.ts +++ b/webapp/packages/plugin-user-profile/src/UserMenu/userMenuStyles.ts @@ -11,11 +11,17 @@ import { css } from 'reshadow'; export const userMenuStyles = css` ContextMenu { opacity: 1; - padding: 0 16px !important; + padding: 0 !important; height: 48px; cursor: pointer; background: none; border: none; outline: none !important; + & Icon { + background: #47a0dd; + width: 16px; + height: 100%; + padding: 0 16px; + } } `; 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/index.ts b/webapp/packages/plugin-user-profile/src/index.ts index 8c3166e301..61f245673e 100644 --- a/webapp/packages/plugin-user-profile/src/index.ts +++ b/webapp/packages/plugin-user-profile/src/index.ts @@ -1,3 +1,6 @@ import { userProfilePlugin } from './manifest'; export default userProfilePlugin; + +export * from './UserMenu/MENU_USER_PROFILE'; +export * from './UserMenu/UserMenu'; 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', 'Пароли не совпадают'], ]; diff --git a/webapp/packages/product-default/src/index.ts b/webapp/packages/product-default/src/index.ts index 667dd60a33..8d1d9408cd 100644 --- a/webapp/packages/product-default/src/index.ts +++ b/webapp/packages/product-default/src/index.ts @@ -20,6 +20,7 @@ import sqlEditor from '@cloudbeaver/plugin-sql-editor'; import sqlEditorTab from '@cloudbeaver/plugin-sql-editor-navigation-tab'; import sqlEditorScreen from '@cloudbeaver/plugin-sql-editor-screen'; import userProfile from '@cloudbeaver/plugin-user-profile'; +import userProfileAdministration from '@cloudbeaver/plugin-user-profile-administration'; import { defaultProductManifest } from './manifest'; @@ -39,6 +40,7 @@ const PLUGINS: PluginManifest[] = [ sqlEditorTab, sqlEditorScreen, userProfile, + userProfileAdministration, defaultProductManifest, connectionPlugin, connectionAdministration,