mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
Merge pull request #152 from dbeaver/feat-administration-redesign
feat(plugin-authentication): administration redesign
This commit is contained in:
@@ -26,13 +26,13 @@ export type AdministrationItemDrawerComponent = React.FunctionComponent<Administ
|
||||
export type AdministrationItemContentProps = {
|
||||
item: IAdministrationItem;
|
||||
configurationWizard: boolean;
|
||||
sub?: IAdministrationItemSubItem;
|
||||
param?: string | null;
|
||||
}
|
||||
export type AdministrationItemContentComponent = React.FunctionComponent<AdministrationItemContentProps>
|
||||
|
||||
export type AdministrationItemSubContentProps = {
|
||||
item: IAdministrationItem;
|
||||
export type AdministrationItemSubContentProps = AdministrationItemContentProps & {
|
||||
sub: IAdministrationItemSubItem;
|
||||
configurationWizard: boolean;
|
||||
param: string | null;
|
||||
}
|
||||
export type AdministrationItemSubContentComponent = React.FunctionComponent<AdministrationItemSubContentProps>
|
||||
|
||||
+11
-4
@@ -27,7 +27,7 @@ export class AdministrationScreenServiceBootstrap extends Bootstrap {
|
||||
private serverConfigResource: ServerConfigResource
|
||||
) {
|
||||
super();
|
||||
this.permissionsService.onUpdate.subscribe(this.handleActivate.bind(this));
|
||||
this.permissionsService.onUpdate.subscribe(this.checkPermissions.bind(this));
|
||||
}
|
||||
|
||||
register() {
|
||||
@@ -110,8 +110,7 @@ export class AdministrationScreenServiceBootstrap extends Bootstrap {
|
||||
}
|
||||
|
||||
private async handleActivate() {
|
||||
if (!await this.isAccessProvided()) {
|
||||
this.screenService.navigateToRoot();
|
||||
if (!await this.checkPermissions()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -125,8 +124,16 @@ export class AdministrationScreenServiceBootstrap extends Bootstrap {
|
||||
}
|
||||
}
|
||||
|
||||
private async checkPermissions() {
|
||||
if (!await this.isAccessProvided()) {
|
||||
this.screenService.navigateToRoot();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private async isAccessProvided() {
|
||||
if (await !this.permissionsService.hasAsync(EAdminPermission.admin)) {
|
||||
if (!await this.permissionsService.hasAsync(EAdminPermission.admin)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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 } from 'reshadow';
|
||||
|
||||
import { Translate } from '@cloudbeaver/core-localization/src';
|
||||
import { AdminUserInfo } from '@cloudbeaver/core-sdk';
|
||||
import { useStyles, composes } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { UserForm } from './UserForm/UserForm';
|
||||
|
||||
type Props = {
|
||||
user: AdminUserInfo;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
const styles = composes(
|
||||
css`
|
||||
title-bar {
|
||||
composes: theme-border-color-background from global;
|
||||
}
|
||||
`,
|
||||
css`
|
||||
title-bar {
|
||||
composes: theme-typography--headline6 from global;
|
||||
padding: 0 16px;
|
||||
border-top: solid 1px;
|
||||
align-items: center;
|
||||
height: 48px;
|
||||
display: flex;
|
||||
font-weight: 400;
|
||||
flex: 1;
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
export function CreateUser({
|
||||
user,
|
||||
onCancel,
|
||||
}: Props) {
|
||||
|
||||
return styled(useStyles(styles))(
|
||||
<user-create as='div'>
|
||||
<title-bar as='div'><Translate token='authentication_administration_user_connections_user_add'/></title-bar>
|
||||
<UserForm userId={user.userId} onCancel={onCancel} />
|
||||
</user-create>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
/*
|
||||
* 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 { observer } from 'mobx-react';
|
||||
import { useCallback } from 'react';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import {
|
||||
TabsState, TabList, Tab,
|
||||
TabTitle, Loader, SubmittingForm, TabPanel,
|
||||
ErrorMessage, Button,
|
||||
InputField,
|
||||
Checkbox,
|
||||
useFocus,
|
||||
InputGroup
|
||||
} from '@cloudbeaver/core-blocks';
|
||||
import { useController } from '@cloudbeaver/core-di';
|
||||
import { useTranslate } from '@cloudbeaver/core-localization';
|
||||
import { useStyles, composes } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { GrantedConnections } from './GrantedConnections';
|
||||
import { UserFormController } from './UserFormController';
|
||||
|
||||
const styles = composes(
|
||||
css`
|
||||
Tab {
|
||||
composes: theme-ripple theme-background-secondary theme-text-on-secondary from global;
|
||||
}
|
||||
|
||||
TabList {
|
||||
composes: theme-background-surface theme-text-on-surface from global;
|
||||
}
|
||||
|
||||
ErrorMessage {
|
||||
composes: theme-background-secondary from global;
|
||||
}
|
||||
|
||||
box {
|
||||
composes: theme-background-secondary theme-text-on-secondary from global;
|
||||
}
|
||||
|
||||
content-box {
|
||||
composes: theme-background-secondary theme-border-color-background from global;
|
||||
}
|
||||
|
||||
GrantedConnections {
|
||||
composes: theme-background-surface from global;
|
||||
}
|
||||
`,
|
||||
css`
|
||||
box {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
SubmittingForm {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
TabList {
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
padding-left: 24px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
TabPanel {
|
||||
overflow: auto !important;
|
||||
}
|
||||
|
||||
Tab {
|
||||
composes: theme-typography--body2 from global;
|
||||
text-transform: uppercase;
|
||||
font-weight: normal;
|
||||
|
||||
&:global([aria-selected=true]) {
|
||||
font-weight: normal !important;
|
||||
}
|
||||
|
||||
& TabTitle {
|
||||
padding: 0 24px !important;
|
||||
}
|
||||
}
|
||||
|
||||
ErrorMessage {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
padding: 8px 24px;
|
||||
}
|
||||
|
||||
fill {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
SubmittingForm {
|
||||
position: relative;
|
||||
min-height: 320px;
|
||||
max-height: 500px;
|
||||
}
|
||||
|
||||
Button:not(:first-child) {
|
||||
margin-right: 24px;
|
||||
}
|
||||
|
||||
layout-grid {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
flex-box {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
flex-box-element {
|
||||
min-width: 450px;
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
type Props = {
|
||||
userId: string;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
export const UserForm = observer(function UserForm({
|
||||
userId,
|
||||
onCancel,
|
||||
}: Props) {
|
||||
const translate = useTranslate();
|
||||
const controller = useController(UserFormController, userId, onCancel);
|
||||
const [focusedRef] = useFocus({ focusFirstChild: true });
|
||||
|
||||
const handleLoginChange = useCallback(
|
||||
(value: string) => controller.credentials.login = value,
|
||||
[]
|
||||
);
|
||||
const handlePasswordChange = useCallback(
|
||||
(value: string) => controller.credentials.password = value,
|
||||
[]
|
||||
);
|
||||
const handlePasswordRepeatChange = useCallback(
|
||||
(value: string) => controller.credentials.passwordRepeat = value,
|
||||
[]
|
||||
);
|
||||
const handleRoleChange = useCallback(
|
||||
(roleId: string, value: boolean) => controller.credentials.roles.set(roleId, value),
|
||||
[]
|
||||
);
|
||||
|
||||
return styled(useStyles(styles))(
|
||||
<TabsState selectedId='info'>
|
||||
<box as='div'>
|
||||
<TabList>
|
||||
<Tab tabId='info' >
|
||||
<TabTitle>{translate('authentication_administration_user_info')}</TabTitle>
|
||||
</Tab>
|
||||
<Tab tabId='connections_access' onOpen={controller.loadConnectionsAccess} >
|
||||
<TabTitle>{translate('authentication_administration_user_connections_access')}</TabTitle>
|
||||
</Tab>
|
||||
<fill as="div" />
|
||||
<Button
|
||||
type="button"
|
||||
disabled={controller.isSaving}
|
||||
mod={['outlined']}
|
||||
onClick={onCancel}
|
||||
>
|
||||
{translate('ui_processing_cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
disabled={controller.isSaving}
|
||||
mod={['unelevated']}
|
||||
onClick={controller.save}
|
||||
>
|
||||
{translate(controller.isNew ? 'ui_processing_create' : 'ui_processing_save')}
|
||||
</Button>
|
||||
</TabList>
|
||||
<content-box as='div'>
|
||||
<SubmittingForm onSubmit={controller.save} ref={focusedRef as React.RefObject<HTMLFormElement>}>
|
||||
<TabPanel tabId='info'>
|
||||
<flex-box as="div">
|
||||
<flex-box-element as='div'>
|
||||
<group as="div">
|
||||
<InputGroup>{translate('authentication_user_credentials')}</InputGroup>
|
||||
</group>
|
||||
<group as="div">
|
||||
<InputField
|
||||
type='text'
|
||||
name='login'
|
||||
value={controller.credentials.login}
|
||||
onChange={handleLoginChange}
|
||||
disabled={!controller.isNew || controller.isSaving}
|
||||
mod='surface'
|
||||
required
|
||||
>
|
||||
{translate('authentication_user_name')}
|
||||
</InputField>
|
||||
</group>
|
||||
<group as="div">
|
||||
<InputField
|
||||
type='password'
|
||||
name='password'
|
||||
value={controller.credentials.password}
|
||||
onChange={handlePasswordChange}
|
||||
disabled={controller.isSaving}
|
||||
mod='surface'
|
||||
>
|
||||
{translate('authentication_user_password')}
|
||||
</InputField>
|
||||
</group>
|
||||
<group as="div">
|
||||
<InputField
|
||||
type='password'
|
||||
name='password_repeat'
|
||||
value={controller.credentials.passwordRepeat}
|
||||
onChange={handlePasswordRepeatChange}
|
||||
disabled={controller.isSaving}
|
||||
mod='surface'
|
||||
>
|
||||
{translate('authentication_user_password_repeat')}
|
||||
</InputField>
|
||||
</group>
|
||||
</flex-box-element>
|
||||
<flex-box-element as='div'>
|
||||
<group as="div">
|
||||
<InputGroup>{translate('authentication_user_role')}</InputGroup>
|
||||
</group>
|
||||
{controller.roles.map((role, i) => (
|
||||
<group as="div" key={role.roleId}>
|
||||
<Checkbox
|
||||
value={role.roleId}
|
||||
name='role'
|
||||
checkboxLabel={role.roleName || role.roleId}
|
||||
onChange={checked => handleRoleChange(role.roleId, checked)}
|
||||
checked={controller.credentials.roles.get(role.roleId)}
|
||||
disabled={controller.isSaving}
|
||||
mod='surface'
|
||||
/>
|
||||
</group>
|
||||
))}
|
||||
</flex-box-element>
|
||||
</flex-box>
|
||||
</TabPanel>
|
||||
{controller.isLoading && <Loader overlay/>}
|
||||
<TabPanel tabId='connections_access'>
|
||||
<GrantedConnections
|
||||
grantedConnections={controller.grantedConnections}
|
||||
connections={controller.connections}
|
||||
selectedConnection={controller.selectedConnections}
|
||||
disabled={controller.isLoading}
|
||||
onChange={controller.handleConnectionsAccessChange}
|
||||
/>
|
||||
</TabPanel>
|
||||
</SubmittingForm>
|
||||
{controller.error.responseMessage && (
|
||||
<ErrorMessage
|
||||
text={controller.error.responseMessage}
|
||||
hasDetails={controller.error.hasDetails}
|
||||
onShowDetails={controller.showDetails}
|
||||
/>
|
||||
)}
|
||||
</content-box>
|
||||
</box>
|
||||
</TabsState>
|
||||
);
|
||||
});
|
||||
+1
-1
@@ -19,7 +19,7 @@ import {
|
||||
} from '@cloudbeaver/core-sdk';
|
||||
|
||||
@injectable()
|
||||
export class UserEditController implements IInitializableController, IDestructibleController {
|
||||
export class UserFormController implements IInitializableController, IDestructibleController {
|
||||
readonly selectedConnections = observable<string, boolean>(new Map())
|
||||
@observable grantedConnections: AdminConnectionGrantInfo[] = [];
|
||||
@observable isSaving = false;
|
||||
+8
-2
@@ -9,11 +9,12 @@
|
||||
import { observer } from 'mobx-react';
|
||||
import styled, { css, use } from 'reshadow';
|
||||
|
||||
import { AdministrationTools } from '@cloudbeaver/core-administration';
|
||||
import { AdministrationTools, AdministrationItemContentProps } from '@cloudbeaver/core-administration';
|
||||
import { Loader, IconButton } from '@cloudbeaver/core-blocks';
|
||||
import { useController } from '@cloudbeaver/core-di';
|
||||
import { useStyles, composes } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { CreateUser } from './CreateUser';
|
||||
import { UsersAdministrationController } from './UsersAdministrationController';
|
||||
import { UsersTable } from './UsersTable/UsersTable';
|
||||
|
||||
@@ -55,7 +56,9 @@ const styles = composes(
|
||||
`
|
||||
);
|
||||
|
||||
export const UsersAdministration = observer(function UsersAdministration() {
|
||||
export const UsersAdministration = observer(function UsersAdministration({
|
||||
sub,
|
||||
}: AdministrationItemContentProps) {
|
||||
const controller = useController(UsersAdministrationController);
|
||||
|
||||
return styled(useStyles(styles))(
|
||||
@@ -67,6 +70,9 @@ export const UsersAdministration = observer(function UsersAdministration() {
|
||||
<IconButton name="trash" viewBox="0 0 28 28" onClick={controller.delete} />
|
||||
<IconButton name="reload" viewBox="0 0 28 28" onClick={controller.update} />
|
||||
</AdministrationTools>
|
||||
{sub && controller.creatingUser && (
|
||||
<CreateUser user={controller.creatingUser} onCancel={controller.cancelCreate} />
|
||||
)}
|
||||
<UsersTable
|
||||
users={controller.users}
|
||||
selectedItems={controller.selectedItems}
|
||||
|
||||
+23
-4
@@ -6,7 +6,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { observable } from 'mobx';
|
||||
import { observable, computed } from 'mobx';
|
||||
|
||||
import { UsersResource } from '@cloudbeaver/core-authentication';
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
@@ -15,14 +15,18 @@ import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { ErrorDetailsDialog } from '@cloudbeaver/core-notifications';
|
||||
import { GQLErrorCatcher, resourceKeyList } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { UsersAdministrationNavigationService } from './UsersAdministrationNavigationService';
|
||||
|
||||
@injectable()
|
||||
export class UsersAdministrationController {
|
||||
@observable isDeleting = false;
|
||||
readonly selectedItems = observable<string, boolean>(new Map())
|
||||
readonly expandedItems = observable<string, boolean>(new Map())
|
||||
readonly error = new GQLErrorCatcher();
|
||||
get users() {
|
||||
|
||||
@computed get users() {
|
||||
return Array.from(this.usersResource.data.values())
|
||||
.filter(user => !this.usersResource.isNew(user.userId))
|
||||
.sort((a, b) => {
|
||||
if (this.usersResource.isNew(a.userId) === this.usersResource.isNew(b.userId)) {
|
||||
return 0;
|
||||
@@ -34,6 +38,11 @@ export class UsersAdministrationController {
|
||||
});
|
||||
}
|
||||
|
||||
@computed get creatingUser() {
|
||||
return Array.from(this.usersResource.data.values())
|
||||
.find(user => this.usersResource.isNew(user.userId));
|
||||
}
|
||||
|
||||
get isLoading() {
|
||||
return this.usersResource.isLoading() || this.isDeleting;
|
||||
}
|
||||
@@ -42,11 +51,21 @@ export class UsersAdministrationController {
|
||||
private notificationService: NotificationService,
|
||||
private usersResource: UsersResource,
|
||||
private commonDialogService: CommonDialogService,
|
||||
private usersAdministrationNavigationService: UsersAdministrationNavigationService,
|
||||
) { }
|
||||
|
||||
create = () => {
|
||||
const user = this.usersResource.addNew();
|
||||
this.expandedItems.set(user.userId, true);
|
||||
this.usersResource.addNew();
|
||||
this.usersAdministrationNavigationService.navToAdd();
|
||||
}
|
||||
|
||||
cancelCreate = () => {
|
||||
if (!this.creatingUser) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.usersResource.delete(this.creatingUser.userId);
|
||||
this.usersAdministrationNavigationService.navToRoot();
|
||||
}
|
||||
|
||||
update = async () => {
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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 { AdministrationScreenService } from '@cloudbeaver/core-administration';
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
|
||||
@injectable()
|
||||
export class UsersAdministrationNavigationService {
|
||||
static ItemName = 'users'
|
||||
static AddItemName = 'add'
|
||||
|
||||
constructor(
|
||||
private administrationScreenService: AdministrationScreenService,
|
||||
) {}
|
||||
|
||||
navToRoot() {
|
||||
this.administrationScreenService.navigateToItem(UsersAdministrationNavigationService.ItemName);
|
||||
}
|
||||
|
||||
navToAdd() {
|
||||
this.administrationScreenService.navigateToItemSub(
|
||||
UsersAdministrationNavigationService.ItemName,
|
||||
UsersAdministrationNavigationService.AddItemName
|
||||
);
|
||||
}
|
||||
}
|
||||
+10
-10
@@ -6,19 +6,19 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { AdministrationItemService, AdministrationScreenService } from '@cloudbeaver/core-administration';
|
||||
import { AdministrationItemService } from '@cloudbeaver/core-administration';
|
||||
import { UsersResource } from '@cloudbeaver/core-authentication';
|
||||
import { injectable, Bootstrap } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
|
||||
import { UsersAdministration } from './UsersAdministration';
|
||||
import { UsersAdministrationNavigationService } from './UsersAdministrationNavigationService';
|
||||
import { UsersDrawerItem } from './UsersDrawerItem';
|
||||
|
||||
@injectable()
|
||||
export class UsersAdministrationService extends Bootstrap {
|
||||
constructor(
|
||||
private administrationItemService: AdministrationItemService,
|
||||
private administrationScreenService: AdministrationScreenService,
|
||||
private notificationService: NotificationService,
|
||||
private usersResource: UsersResource,
|
||||
) {
|
||||
@@ -27,12 +27,13 @@ export class UsersAdministrationService extends Bootstrap {
|
||||
|
||||
register() {
|
||||
this.administrationItemService.create({
|
||||
name: 'users',
|
||||
name: UsersAdministrationNavigationService.ItemName,
|
||||
order: 1,
|
||||
sub: [
|
||||
{
|
||||
name: 'edit',
|
||||
name: UsersAdministrationNavigationService.AddItemName,
|
||||
getComponent: () => UsersAdministration,
|
||||
onActivate: this.onCreateActivate.bind(this),
|
||||
},
|
||||
],
|
||||
getContentComponent: () => UsersAdministration,
|
||||
@@ -43,12 +44,11 @@ export class UsersAdministrationService extends Bootstrap {
|
||||
|
||||
load(): void | Promise<void> { }
|
||||
|
||||
navToRoot() {
|
||||
this.administrationScreenService.navigateToItem('users');
|
||||
}
|
||||
|
||||
navToEdit(userId: string) {
|
||||
this.administrationScreenService.navigateToItemSub('users', 'edit', userId);
|
||||
private onCreateActivate() {
|
||||
if (!Array.from(this.usersResource.data.values())
|
||||
.some(user => this.usersResource.isNew(user.userId))) {
|
||||
this.usersResource.addNew();
|
||||
}
|
||||
}
|
||||
|
||||
private async loadUsers() {
|
||||
|
||||
+2
-263
@@ -8,125 +8,12 @@
|
||||
|
||||
import { observer } from 'mobx-react';
|
||||
import { useContext, useCallback } from 'react';
|
||||
import styled, { css, use } from 'reshadow';
|
||||
|
||||
import { UsersResource } from '@cloudbeaver/core-authentication';
|
||||
import {
|
||||
TabsState, TabList, Tab,
|
||||
TabTitle, Loader, SubmittingForm, TabPanel,
|
||||
ErrorMessage, Button, TableItemContext,
|
||||
TableContext,
|
||||
InputField,
|
||||
Checkbox,
|
||||
useFocus,
|
||||
InputGroup
|
||||
} from '@cloudbeaver/core-blocks';
|
||||
import { useService, useController } from '@cloudbeaver/core-di';
|
||||
import { useTranslate } from '@cloudbeaver/core-localization';
|
||||
import { useStyles, composes } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { GrantedConnections } from './GrantedConnections';
|
||||
import { UserEditController } from './UserEditController';
|
||||
|
||||
const styles = composes(
|
||||
css`
|
||||
Tab {
|
||||
composes: theme-ripple theme-background-secondary theme-text-on-secondary from global;
|
||||
}
|
||||
|
||||
TabList {
|
||||
composes: theme-background-surface theme-text-on-surface from global;
|
||||
}
|
||||
|
||||
ErrorMessage {
|
||||
composes: theme-background-secondary from global;
|
||||
}
|
||||
|
||||
box {
|
||||
composes: theme-background-secondary theme-text-on-secondary from global;
|
||||
}
|
||||
|
||||
content-box {
|
||||
composes: theme-background-secondary theme-border-color-background from global;
|
||||
}
|
||||
|
||||
GrantedConnections {
|
||||
composes: theme-background-surface from global;
|
||||
}
|
||||
`,
|
||||
css`
|
||||
box {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
SubmittingForm {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
TabList {
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
padding-left: 24px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
TabPanel {
|
||||
overflow: auto !important;
|
||||
}
|
||||
|
||||
Tab {
|
||||
composes: theme-typography--body2 from global;
|
||||
text-transform: uppercase;
|
||||
font-weight: normal;
|
||||
|
||||
&:global([aria-selected=true]) {
|
||||
font-weight: normal !important;
|
||||
}
|
||||
|
||||
& TabTitle {
|
||||
padding: 0 24px !important;
|
||||
}
|
||||
}
|
||||
|
||||
ErrorMessage {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
padding: 8px 24px;
|
||||
}
|
||||
|
||||
fill {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
SubmittingForm, Loader {
|
||||
min-height: 320px;
|
||||
max-height: 500px;
|
||||
}
|
||||
|
||||
Button:not(:first-child) {
|
||||
margin-right: 24px;
|
||||
}
|
||||
|
||||
layout-grid {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
flex-box {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
flex-box-element {
|
||||
min-width: 450px;
|
||||
}
|
||||
`
|
||||
);
|
||||
import { UserForm } from '../UserForm/UserForm';
|
||||
|
||||
type Props = {
|
||||
item: string;
|
||||
@@ -136,155 +23,7 @@ export const UserEdit = observer(function UserEdit({
|
||||
item,
|
||||
}: Props) {
|
||||
const tableContext = useContext(TableContext);
|
||||
const context = useContext(TableItemContext);
|
||||
const collapse = useCallback(() => tableContext?.setItemExpand(item, false), [tableContext]);
|
||||
|
||||
const translate = useTranslate();
|
||||
const controller = useController(UserEditController, item, collapse);
|
||||
const usersResource = useService(UsersResource);
|
||||
const [focusedRef] = useFocus({ focusFirstChild: true });
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
collapse();
|
||||
|
||||
if (controller.isNew) {
|
||||
usersResource.delete(item);
|
||||
}
|
||||
}, [collapse]);
|
||||
|
||||
const handleLoginChange = useCallback(
|
||||
(value: string) => controller.credentials.login = value,
|
||||
[]
|
||||
);
|
||||
const handlePasswordChange = useCallback(
|
||||
(value: string) => controller.credentials.password = value,
|
||||
[]
|
||||
);
|
||||
const handlePasswordRepeatChange = useCallback(
|
||||
(value: string) => controller.credentials.passwordRepeat = value,
|
||||
[]
|
||||
);
|
||||
const handleRoleChange = useCallback(
|
||||
(roleId: string, value: boolean) => controller.credentials.roles.set(roleId, value),
|
||||
[]
|
||||
);
|
||||
|
||||
return styled(useStyles(styles))(
|
||||
<TabsState selectedId='info'>
|
||||
<box as='div'>
|
||||
<TabList>
|
||||
<Tab tabId='info' >
|
||||
<TabTitle>{translate('authentication_administration_user_info')}</TabTitle>
|
||||
</Tab>
|
||||
<Tab tabId='connections_access' onOpen={controller.loadConnectionsAccess} >
|
||||
<TabTitle>{translate('authentication_administration_user_connections_access')}</TabTitle>
|
||||
</Tab>
|
||||
<fill as="div" />
|
||||
<Button
|
||||
type="button"
|
||||
disabled={controller.isSaving}
|
||||
mod={['outlined']}
|
||||
onClick={handleCancel}
|
||||
>
|
||||
{translate('ui_processing_cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
disabled={controller.isSaving}
|
||||
mod={['unelevated']}
|
||||
onClick={controller.save}
|
||||
>
|
||||
{translate(controller.isNew ? 'ui_processing_create' : 'ui_processing_save')}
|
||||
</Button>
|
||||
</TabList>
|
||||
<content-box as='div'>
|
||||
{controller.isLoading
|
||||
? <Loader />
|
||||
: (
|
||||
<SubmittingForm onSubmit={controller.save} ref={focusedRef as React.RefObject<HTMLFormElement>}>
|
||||
<TabPanel tabId='info'>
|
||||
<flex-box as="div">
|
||||
<flex-box-element as='div'>
|
||||
<group as="div">
|
||||
<InputGroup>{translate('authentication_user_credentials')}</InputGroup>
|
||||
</group>
|
||||
<group as="div">
|
||||
<InputField
|
||||
type='text'
|
||||
name='login'
|
||||
value={controller.credentials.login}
|
||||
onChange={handleLoginChange}
|
||||
disabled={!controller.isNew || controller.isSaving}
|
||||
mod='surface'
|
||||
>
|
||||
{translate('authentication_user_name')}
|
||||
</InputField>
|
||||
</group>
|
||||
<group as="div">
|
||||
<InputField
|
||||
type='password'
|
||||
name='password'
|
||||
value={controller.credentials.password}
|
||||
onChange={handlePasswordChange}
|
||||
disabled={controller.isSaving}
|
||||
mod='surface'
|
||||
>
|
||||
{translate('authentication_user_password')}
|
||||
</InputField>
|
||||
</group>
|
||||
<group as="div">
|
||||
<InputField
|
||||
type='password'
|
||||
name='password_repeat'
|
||||
value={controller.credentials.passwordRepeat}
|
||||
onChange={handlePasswordRepeatChange}
|
||||
disabled={controller.isSaving}
|
||||
mod='surface'
|
||||
>
|
||||
{translate('authentication_user_password_repeat')}
|
||||
</InputField>
|
||||
</group>
|
||||
</flex-box-element>
|
||||
<flex-box-element as='div'>
|
||||
<group as="div">
|
||||
<InputGroup>{translate('authentication_user_role')}</InputGroup>
|
||||
</group>
|
||||
{controller.roles.map((role, i) => (
|
||||
<group as="div" key={role.roleId}>
|
||||
<Checkbox
|
||||
value={role.roleId}
|
||||
name='role'
|
||||
checkboxLabel={role.roleName || role.roleId}
|
||||
onChange={checked => handleRoleChange(role.roleId, checked)}
|
||||
checked={controller.credentials.roles.get(role.roleId)}
|
||||
disabled={controller.isSaving}
|
||||
mod='surface'
|
||||
/>
|
||||
</group>
|
||||
))}
|
||||
</flex-box-element>
|
||||
</flex-box>
|
||||
</TabPanel>
|
||||
<TabPanel tabId='connections_access'>
|
||||
<GrantedConnections
|
||||
grantedConnections={controller.grantedConnections}
|
||||
connections={controller.connections}
|
||||
selectedConnection={controller.selectedConnections}
|
||||
disabled={controller.isLoading}
|
||||
onChange={controller.handleConnectionsAccessChange}
|
||||
/>
|
||||
</TabPanel>
|
||||
</SubmittingForm>
|
||||
)}
|
||||
{controller.error.responseMessage && (
|
||||
<ErrorMessage
|
||||
text={controller.error.responseMessage}
|
||||
hasDetails={controller.error.hasDetails}
|
||||
onShowDetails={controller.showDetails}
|
||||
/>
|
||||
)}
|
||||
</content-box>
|
||||
</box>
|
||||
</TabsState>
|
||||
);
|
||||
return <UserForm userId={item} onCancel={collapse} />;
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import { AdministrationTopAppBarService } from '@cloudbeaver/core-administration';
|
||||
import { SettingsMenuService, TopNavService } from '@cloudbeaver/core-app';
|
||||
import { AuthInfoService } from '@cloudbeaver/core-authentication';
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { injectable, Bootstrap } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { ServerService } from '@cloudbeaver/core-root';
|
||||
|
||||
@@ -18,7 +18,7 @@ import { AuthDialogService } from './Dialog/AuthDialogService';
|
||||
import { UserInfo } from './UserInfo';
|
||||
|
||||
@injectable()
|
||||
export class AuthMenuService {
|
||||
export class AuthMenuService extends Bootstrap {
|
||||
constructor(
|
||||
private serverService: ServerService,
|
||||
private authDialogService: AuthDialogService,
|
||||
@@ -28,7 +28,9 @@ export class AuthMenuService {
|
||||
private notificationService: NotificationService,
|
||||
private topNavService: TopNavService,
|
||||
private administrationTopAppBarService: AdministrationTopAppBarService,
|
||||
) { }
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
register() {
|
||||
this.settingsMenuService.addMenuItem(
|
||||
@@ -57,6 +59,8 @@ export class AuthMenuService {
|
||||
this.administrationTopAppBarService.placeholder.add(UserInfo, 4);
|
||||
}
|
||||
|
||||
load(): void | Promise<void> { }
|
||||
|
||||
private async logout() {
|
||||
try {
|
||||
await this.authInfoService.logout();
|
||||
|
||||
@@ -7,18 +7,20 @@
|
||||
*/
|
||||
|
||||
import { AuthInfoService } from '@cloudbeaver/core-authentication';
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { injectable, Bootstrap } from '@cloudbeaver/core-di';
|
||||
import { ServerService } from '@cloudbeaver/core-root';
|
||||
|
||||
import { AuthDialogService } from './Dialog/AuthDialogService';
|
||||
|
||||
@injectable()
|
||||
export class AuthenticationService {
|
||||
export class AuthenticationService extends Bootstrap {
|
||||
constructor(
|
||||
private serverService: ServerService,
|
||||
private authDialogService: AuthDialogService,
|
||||
private authInfoService: AuthInfoService,
|
||||
) { }
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async auth() {
|
||||
const config = await this.serverService.config.load(null);
|
||||
@@ -39,4 +41,11 @@ export class AuthenticationService {
|
||||
await this.authDialogService.showLoginForm(true);
|
||||
}
|
||||
}
|
||||
|
||||
register(): void | Promise<void> { }
|
||||
|
||||
load() {
|
||||
// we do not await it because to unblock app loading
|
||||
this.auth();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* 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 { injectable } from '@cloudbeaver/core-di';
|
||||
|
||||
import { AuthenticationService } from './AuthenticationService';
|
||||
import { AuthMenuService } from './AuthMenuService';
|
||||
|
||||
@injectable()
|
||||
export class Bootstrap {
|
||||
constructor(
|
||||
private authenticationService: AuthenticationService,
|
||||
private authMenuService: AuthMenuService
|
||||
) { }
|
||||
|
||||
async bootstrap() {
|
||||
this.authMenuService.register();
|
||||
await this.authenticationService.auth();
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ export default [
|
||||
['authentication_login_dialog_title', 'Authentication'],
|
||||
['authentication_login', 'Login'],
|
||||
['authentication_logout', 'Logout'],
|
||||
['authentication_administration_user_connections_user_add', 'User Creation'],
|
||||
['authentication_administration_user_connections_user_new', 'New user'],
|
||||
['authentication_administration_user_connections_access_load_fail', 'User\'s granted connections loading failed'],
|
||||
['authentication_administration_user_connections_access_connections_load_fail', 'Connections loading failed'],
|
||||
|
||||
@@ -2,6 +2,7 @@ export default [
|
||||
['authentication_login_dialog_title', 'Авторизация'],
|
||||
['authentication_login', 'Войти'],
|
||||
['authentication_logout', 'Выйти'],
|
||||
['authentication_administration_user_connections_user_add', 'Создание пользователя'],
|
||||
['authentication_administration_user_connections_user_new', 'Новый пользователь'],
|
||||
['authentication_administration_user_connections_access_load_fail', 'Не удалось загрузить доступные пользователю коннекшены'],
|
||||
['authentication_administration_user_connections_access_connections_load_fail', 'Ошибка при загрузке списка коннекшенов'],
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { IServiceInjector, PluginManifest } from '@cloudbeaver/core-di';
|
||||
import { PluginManifest } from '@cloudbeaver/core-di';
|
||||
|
||||
import { UsersAdministrationNavigationService } from './Administration/Users/UsersAdministrationNavigationService';
|
||||
import { UsersAdministrationService } from './Administration/Users/UsersAdministrationService';
|
||||
import { AuthenticationLocaleService } from './AuthenticationLocaleService';
|
||||
import { AuthenticationService } from './AuthenticationService';
|
||||
import { AuthMenuService } from './AuthMenuService';
|
||||
import { Bootstrap } from './Bootstrap';
|
||||
import { AuthDialogService } from './Dialog/AuthDialogService';
|
||||
|
||||
export const manifest: PluginManifest = {
|
||||
@@ -26,11 +26,6 @@ export const manifest: PluginManifest = {
|
||||
AuthMenuService,
|
||||
UsersAdministrationService,
|
||||
AuthenticationLocaleService,
|
||||
UsersAdministrationNavigationService,
|
||||
],
|
||||
|
||||
async initialize(services: IServiceInjector) {
|
||||
services
|
||||
.resolveServiceByClass(Bootstrap)
|
||||
.bootstrap();
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user