Merge remote-tracking branch 'origin/devel' into devel

This commit is contained in:
Serge Rider
2020-09-09 19:35:38 +03:00
27 changed files with 243 additions and 125 deletions
@@ -41,6 +41,11 @@ const styles = composes(
border: solid 1px;
padding: 16px 24px
}
p {
line-height: 2;
white-space: pre;
}
`
);
@@ -52,8 +57,8 @@ export const FinishPage = observer(function FinishPage() {
<layout-grid as="div">
<layout-grid-inner as="div">
<layout-grid-cell as='div' {...use({ span: 12 })}>
<h3>All done</h3>
<p>Go to fun</p>
<h3>{translate('administration_configuration_wizard_finish_title')}</h3>
<p>{translate('administration_configuration_wizard_finish_message')}</p>
<Button
type="button"
@@ -10,6 +10,7 @@ import { observer } from 'mobx-react';
import styled, { use, css } from 'reshadow';
import { useController } from '@cloudbeaver/core-di';
import { useTranslate } from '@cloudbeaver/core-localization';
import { useStyles, composes } from '@cloudbeaver/core-theming';
import { ServerConfigurationForm } from './ServerConfigurationForm';
@@ -40,18 +41,23 @@ const styles = composes(
border: solid 1px;
padding: 16px 24px
}
p {
line-height: 2;
}
`
);
export const ServerConfigurationPage = observer(function ServerConfigurationPage() {
const translate = useTranslate();
const controller = useController(ServerConfigurationPageController);
return styled(useStyles(styles))(
<layout-grid as="div">
<layout-grid-inner as="div">
<layout-grid-cell as='div' {...use({ span: 12 })}>
<h3>Welcome to initial server configuration</h3>
<p>Here you can configure something...</p>
<h3>{translate('administration_configuration_wizard_configuration_title')}</h3>
<p>{translate('administration_configuration_wizard_configuration_message')}</p>
<ServerConfigurationForm
serverConfig={controller.state.serverConfig}
onChange={controller.onChange}
@@ -8,6 +8,7 @@
import styled, { use, css } from 'reshadow';
import { Translate } from '@cloudbeaver/core-localization';
import { useStyles, composes } from '@cloudbeaver/core-theming';
const styles = composes(
@@ -35,6 +36,14 @@ const styles = composes(
border: solid 1px;
padding: 16px 24px
}
p {
line-height: 2;
}
note {
composes: theme-typography--body2 from global;
}
`
);
@@ -43,8 +52,9 @@ export function WelcomePage() {
<layout-grid as="div">
<layout-grid-inner as="div">
<layout-grid-cell as='div' {...use({ span: 12 })}>
<h3>Welcome to initial server configuration</h3>
<p>Here you can configure something...</p>
<h3><Translate token='administration_configuration_wizard_welcome_title'/></h3>
<p><Translate token='administration_configuration_wizard_welcome_message'/></p>
<note as='div'><Translate token='administration_configuration_wizard_welcome_note'/></note>
</layout-grid-cell>
</layout-grid-inner>
</layout-grid>
@@ -3,9 +3,14 @@ export default [
['administration_menu_back', 'Back to app'],
['administration_configuration_wizard_title', 'Initial Server Configuration'],
['administration_configuration_wizard_welcome', 'Welcome'],
['administration_configuration_wizard_welcome_step_description', 'Welcome'],
['administration_configuration_wizard_welcome_step_description', 'Welcome to CloudBeaver'],
['administration_configuration_wizard_welcome_title', 'Welcome to CloudBeaver, cloud database management system!'],
['administration_configuration_wizard_welcome_message', 'The easy configuration wizard will guide you through the several simple steps to set up the CloudBeaver server. You will need to set server information and administrator credentials. You can also add first database connections here.'],
['administration_configuration_wizard_welcome_note', 'Note: you will be able to change these configuration parameters later on the administration panel.'],
['administration_configuration_wizard_configuration', 'Server configuration'],
['administration_configuration_wizard_configuration_step_description', 'Server configuration'],
['administration_configuration_wizard_configuration_step_description', 'Main server configuration'],
['administration_configuration_wizard_configuration_title', 'You can configure the main server parameters here.'],
['administration_configuration_wizard_configuration_message', 'Administrator is super user which can configure server, set databases connections, manage other users and much more. Please, remember the entered password. It is not possible to recover administrator password automatically.'],
['administration_configuration_wizard_configuration_plugins', 'Configuration'],
['administration_configuration_wizard_configuration_server_info', 'Server Info'],
['administration_configuration_wizard_configuration_server_name', 'Server Name'],
@@ -28,4 +33,6 @@ export default [
['administration_configuration_wizard_configuration_navigator_show_utility_objects', 'Utility Objects'],
['administration_configuration_wizard_finish', 'Confirmation'],
['administration_configuration_wizard_finish_step_description', 'Confirmation'],
['administration_configuration_wizard_finish_title', 'That is almost it.'],
['administration_configuration_wizard_finish_message', 'Press the Finish button to complete the server configuration. You can return to the previous pages if you want to change or add something.\nWhen the configuration is completed all entered settings will be applied for your CloudBeaver server. You will be redirected to the main page to start working.\nYou can always login to the system as administrator to change the server settings.'],
];
@@ -26,7 +26,6 @@ type AdminUserNew = AdminUserInfo & { [NEW_USER_SYMBOL]: boolean }
type UserCreateOptions = {
userId: string;
newId?: string;
roles: string[];
credentials: Record<string, any>;
grantedConnections: string[];
@@ -59,22 +58,6 @@ export class UsersResource extends CachedMapResource<string, AdminUserInfo> {
return this.data.has(id);
}
addNew() {
const user = {
userId: `new-${uuid()}`,
grantedRoles: [],
grantedConnections: [],
configurationParameters: {},
metaParameters: {},
[NEW_USER_SYMBOL]: true,
} as AdminUserNew;
this.data.set(user.userId, user);
this.markUpdated(user.userId);
return user;
}
async loadConnections(userId: string): Promise<AdminConnectionGrantInfo[]> {
if (this.isNew(userId)) {
return [];
@@ -90,15 +73,10 @@ export class UsersResource extends CachedMapResource<string, AdminUserInfo> {
}
async create({
userId, newId, roles, credentials, grantedConnections,
userId, roles, credentials, grantedConnections,
}: UserCreateOptions): Promise<AdminUserInfo> {
const { user } = await this.graphQLService.gql.createUser({ userId });
if (newId) {
this.data.delete(newId);
}
this.set(userId, user as AdminUserInfo);
try {
await this.updateCredentials(userId, credentials);
for (const roleId of roles) {
@@ -106,6 +84,8 @@ export class UsersResource extends CachedMapResource<string, AdminUserInfo> {
}
await this.setConnections(userId, grantedConnections);
const user = await this.refresh(userId)as AdminUserNew;
user[NEW_USER_SYMBOL] = true;
} catch (exception) {
this.delete(userId);
throw exception;
@@ -13,4 +13,5 @@ export interface IProperty {
defaultValue?: string;
description?: string;
validValues?: string[];
new?: boolean;
}
@@ -105,7 +105,7 @@ export const PropertiesTable = observer(function PropertiesTable({
{translate('block_properties_table_value')}
</properties-header-value>
<properties-header-right as="div">
{onAdd && <Button type='button' mod={['outlined']} onClick={onAdd}>{translate('block_properties_table_add')}</Button> }
{onAdd && <Button type='button' mod={['outlined']} onClick={() => onAdd()}>{translate('block_properties_table_add')}</Button> }
</properties-header-right>
</properties-header>
<properties-list as="div">
@@ -134,10 +134,10 @@ export const PropertyItem = observer(function PropertyItem({
const handleRemove = useCallback(() => onRemove(property.id), [property]);
useLayoutEffect(() => {
if (keyInputRef.current && isKeyEditable) {
if (keyInputRef.current && isKeyEditable && property.new) {
keyInputRef.current.focus();
}
}, []);
}, [property]);
return styled(useStyles(styles))(
<property-item as="div">
@@ -12,12 +12,10 @@ import {
NavigationTreeContextMenuService,
LogViewerMenuService,
ConnectionSchemaManagerService,
AppScreenService,
} from '@cloudbeaver/core-app';
import { injectable } from '@cloudbeaver/core-di';
import { SessionExpireService } from '@cloudbeaver/core-dialogs';
import { ExceptionsCatcherService } from '@cloudbeaver/core-events';
import { RouterService } from '@cloudbeaver/core-routing';
import { ThemeService } from '@cloudbeaver/core-theming';
/**
@@ -39,7 +39,7 @@ export const DriverProperties = observer(function DriverProperties({
state,
loadProperties,
}: DriverPropertiesProps) {
const controller = useController(DriverPropertiesController, driverId);
const controller = useController(DriverPropertiesController, driverId, state);
useMemo(() => {
if (loadProperties) {
@@ -54,7 +54,7 @@ export const DriverProperties = observer(function DriverProperties({
<PropertiesTable
properties={controller.driverProperties}
propertiesState={state}
onAdd={controller.onAddProperty}
onAdd={controller.addProperty}
/>
)}
</properties>
@@ -32,20 +32,24 @@ export class DriverPropertiesController implements IInitializableController {
loaded = false;
private state!: Record<string, string>;
constructor(
private driverPropertiesService: DriverPropertiesService,
private notificationService: NotificationService
) { }
init(driverId: string) {
init(driverId: string, state: Record<string, string>) {
this.driverId = driverId;
this.state = state;
}
onAddProperty = () => {
addProperty = (key?: string, value?: string) => {
this.driverProperties.unshift({
id: uuid(),
key: 'property',
defaultValue: '',
key: key ?? 'property',
defaultValue: value ?? '',
new: !key,
});
}
@@ -64,6 +68,14 @@ export class DriverPropertiesController implements IInitializableController {
description: property.description,
validValues: property.validValues,
})));
for (const key of Object.keys(this.state)) {
if (this.driverProperties.some(property => property.key === key)) {
continue;
}
this.addProperty(key, this.state[key]);
}
this.loaded = true;
} catch (exception) {
this.notificationService.logException(exception, 'Can\'t load driver properties');
@@ -12,6 +12,7 @@ import styled, { css, use } from 'reshadow';
import { AdministrationTools, AdministrationItemContentProps } from '@cloudbeaver/core-administration';
import { Loader, IconButton } from '@cloudbeaver/core-blocks';
import { useController } from '@cloudbeaver/core-di';
import { Translate } from '@cloudbeaver/core-localization';
import { useStyles, composes } from '@cloudbeaver/core-theming';
import { ConnectionsAdministrationController } from './ConnectionsAdministrationController';
@@ -24,11 +25,16 @@ const styles = composes(
composes: theme-background-surface theme-text-on-surface from global;
}
layout-grid-cell {
layout-grid-cell, message-box {
composes: theme-border-color-background from global;
}
`,
css`
message-box {
padding: 16px 24px;
border-bottom: solid 1px;
}
layout-grid {
width: 100%;
flex: 1;
@@ -59,6 +65,11 @@ const styles = composes(
padding: 0 12px;
padding-right: 24px;
}
p {
line-height: 2;
white-space: pre;
}
`
);
@@ -73,6 +84,11 @@ export const ConnectionsAdministration = observer(function ConnectionsAdministra
<layout-grid as="div">
<layout-grid-inner as="div">
<layout-grid-cell as='div' {...use({ span: 12 })}>
{configurationWizard && (
<message-box as='div'>
<p><Translate token='connections_administration_configuration_wizard_message'/></p>
</message-box>
)}
<AdministrationTools>
<IconButton name="add" viewBox="0 0 28 28" onClick={controller.create} />
<IconButton name="trash" viewBox="0 0 28 28" onClick={controller.delete} />
@@ -26,18 +26,13 @@ const styles = composes(
`,
css`
box {
box-sizing: border-box;
padding: 24px;
min-height: 320px;
max-height: 500px;
display: flex;
flex-direction: column;
}
ErrorMessage {
position: sticky;
bottom: 0;
padding: 8px 24px;
}
`
);
@@ -25,6 +25,9 @@ const styles = composes(
}
`,
css`
Table {
width: 100%;
}
TableColumnHeader {
border-top: solid 1px;
}
@@ -5,6 +5,7 @@ export default [
['connections_administration_new_connection', 'New connection'],
['connections_administration_configuration_wizard_step_title', 'Database connections'],
['connections_administration_configuration_wizard_step_description', 'Database connections'],
['connections_administration_configuration_wizard_message', 'Here you can create the database connections manually or using a database server search feature.\nYou can skip this step and configure connections later.'],
['connections_connection_create_search_database', 'Search Database'],
['connections_connection_edit_authentication', 'Authentication'],
['connections_connection_edit_access', 'Access'],
@@ -12,10 +12,15 @@
min-width: 100%;
text-align: left;
border-collapse: collapse;
table-layout: fixed;
&[use|size="big"] {
> thead > tr > th {
height: 54px;
&[use|min] {
width: 46px;
}
}
> tbody > tr > td {
@@ -53,6 +58,7 @@
}
th {
box-sizing: border-box;
white-space: nowrap;
padding: 16px;
height: 36px;
@@ -64,7 +70,7 @@
text-decoration: none !important;
&[use|min] {
width: 1%;
width: 28px;
}
&:last-child {
@@ -74,6 +80,7 @@
td {
position: relative;
box-sizing: border-box;
height: 28px;
padding: 0 16px;
transition: padding ease-in-out 0.24s;
@@ -28,13 +28,13 @@ const styles = composes(
css`
title-bar {
composes: theme-typography--headline6 from global;
padding: 0 16px;
box-sizing: border-box;
padding: 16px 24px;
border-top: solid 1px;
align-items: center;
height: 48px;
display: flex;
font-weight: 400;
flex: 1;
flex: auto 0 0;
}
`
);
@@ -47,7 +47,7 @@ export function CreateUser({
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} />
<UserForm user={user} onCancel={onCancel} />
</user-create>
);
}
@@ -21,6 +21,7 @@ import {
} from '@cloudbeaver/core-blocks';
import { useController } from '@cloudbeaver/core-di';
import { useTranslate } from '@cloudbeaver/core-localization';
import { AdminUserInfo } from '@cloudbeaver/core-sdk';
import { useStyles, composes } from '@cloudbeaver/core-theming';
import { GrantedConnections } from './GrantedConnections';
@@ -53,10 +54,6 @@ const styles = composes(
}
`,
css`
box {
padding: 24px;
}
SubmittingForm {
flex: 1;
display: flex;
@@ -128,16 +125,18 @@ const styles = composes(
);
type Props = {
userId: string;
user: AdminUserInfo;
editing?: boolean;
onCancel: () => void;
}
export const UserForm = observer(function UserForm({
userId,
user,
editing = false,
onCancel,
}: Props) {
const translate = useTranslate();
const controller = useController(UserFormController, userId, onCancel);
const controller = useController(UserFormController, user, editing, onCancel);
const [focusedRef] = useFocus<HTMLFormElement>({ focusFirstChild: true });
const handleLoginChange = useCallback(
@@ -182,7 +181,7 @@ export const UserForm = observer(function UserForm({
mod={['unelevated']}
onClick={controller.save}
>
{translate(controller.isNew ? 'ui_processing_create' : 'ui_processing_save')}
{translate(!editing ? 'ui_processing_create' : 'ui_processing_save')}
</Button>
</TabList>
<content-box as='div'>
@@ -199,7 +198,7 @@ export const UserForm = observer(function UserForm({
name='login'
value={controller.credentials.login}
onChange={handleLoginChange}
disabled={!controller.isNew || controller.isSaving}
disabled={editing || controller.isSaving}
mod='surface'
required
>
@@ -31,10 +31,6 @@ export class UserFormController implements IInitializableController, IDestructib
roles: new Map<string, boolean>(),
};
@computed get isNew() {
return this.usersResource.isNew(this.userId);
}
@computed get connections() {
return Array.from(this.connectionsResource.data.values())
.filter(connection => !this.connectionsResource.isNew(connection.id));
@@ -44,16 +40,14 @@ export class UserFormController implements IInitializableController, IDestructib
return Array.from(this.rolesManagerService.roles.data.values());
}
@computed get user(): AdminUserInfo {
return this.usersResource.get(this.userId)!;
}
user!: AdminUserInfo;
readonly error = new GQLErrorCatcher();
private isDistructed = false;
private userId!: string;
private connectionAccessChanged = false;
private connectionAccessLoaded = false;
private collapse!: () => void;
private editing!: boolean;
constructor(
private notificationService: NotificationService,
@@ -64,8 +58,9 @@ export class UserFormController implements IInitializableController, IDestructib
private dbDriverResource: DBDriverResource
) { }
init(id: string, collapse: () => void) {
this.userId = id;
init(user: AdminUserInfo, editing: boolean, collapse: () => void) {
this.user = user;
this.editing = editing;
this.collapse = collapse;
this.loadRoles();
}
@@ -84,10 +79,9 @@ export class UserFormController implements IInitializableController, IDestructib
this.isSaving = true;
try {
if (this.isNew) {
if (!this.editing) {
await this.usersResource.create({
userId: this.credentials.login,
newId: this.userId,
credentials: { password: this.credentials.password },
roles: this.getGrantedRoles(),
grantedConnections: this.getGrantedConnections(),
@@ -103,12 +97,9 @@ export class UserFormController implements IInitializableController, IDestructib
await this.usersResource.refresh(this.user.userId);
this.notificationService.logInfo({ title: 'authentication_administration_user_updated' });
}
this.connectionAccessLoaded = false;
await this.loadConnectionsAccess();
} catch (exception) {
if (!this.error.catch(exception) || this.isDistructed) {
if (this.isNew) {
if (!this.editing) {
this.notificationService.logException(exception, 'Error creating new user');
} else {
this.notificationService.logException(exception, 'Error saving user');
@@ -134,12 +125,14 @@ export class UserFormController implements IInitializableController, IDestructib
this.isLoading = true;
try {
this.grantedConnections = await this.usersResource.loadConnections(this.userId);
if (this.editing) {
this.grantedConnections = await this.usersResource.loadConnections(this.user.userId);
this.selectedConnections.clear();
for (const connection of this.grantedConnections) {
if (connection.subjectType !== AdminSubjectType.Role) {
this.selectedConnections.set(connection.connectionId, true);
this.selectedConnections.clear();
for (const connection of this.grantedConnections) {
if (connection.subjectType !== AdminSubjectType.Role) {
this.selectedConnections.set(connection.connectionId, true);
}
}
}
this.connectionAccessLoaded = true;
@@ -161,12 +154,12 @@ export class UserFormController implements IInitializableController, IDestructib
return;
}
if (!this.credentials.password && this.isNew) {
if (!this.credentials.password && !this.editing) {
this.notificationService.logError({ title: 'authentication_user_password_not_set' });
return;
}
if (!this.credentials.password && this.isNew) {
if (!this.credentials.password && !this.editing) {
this.notificationService.logError({ title: 'authentication_user_password_not_set' });
return;
}
@@ -190,7 +183,7 @@ export class UserFormController implements IInitializableController, IDestructib
if (!this.user.grantedRoles.includes(roleId)) {
await this.usersResource.grantRole(this.user.userId, roleId, true);
}
} else if (!this.isNew) {
} else {
await this.usersResource.revokeRole(this.user.userId, roleId, true);
}
}
@@ -215,8 +208,10 @@ export class UserFormController implements IInitializableController, IDestructib
if (!this.connectionAccessChanged) {
return;
}
await this.usersResource.setConnections(this.userId, this.getGrantedConnections());
await this.usersResource.setConnections(this.user.userId, this.getGrantedConnections());
this.connectionAccessChanged = false;
this.connectionAccessLoaded = false;
await this.loadConnectionsAccess();
}
private async loadRoles() {
@@ -232,11 +227,7 @@ export class UserFormController implements IInitializableController, IDestructib
private async loadUser() {
try {
await this.usersResource.load(this.userId);
if (!this.isNew) {
this.credentials.login = this.user.userId;
}
this.credentials.login = this.user.userId;
this.credentials.roles = new Map(this.user.grantedRoles.map(roleId => ([roleId, true])));
} catch (exception) {
this.notificationService.logException(exception, 'Can\'t load user');
@@ -13,7 +13,7 @@ import { injectable } from '@cloudbeaver/core-di';
import { CommonDialogService, ConfirmationDialog } from '@cloudbeaver/core-dialogs';
import { NotificationService } from '@cloudbeaver/core-events';
import { ErrorDetailsDialog } from '@cloudbeaver/core-notifications';
import { GQLErrorCatcher, resourceKeyList } from '@cloudbeaver/core-sdk';
import { GQLErrorCatcher, resourceKeyList, AdminUserInfo } from '@cloudbeaver/core-sdk';
import { UsersAdministrationNavigationService } from './UsersAdministrationNavigationService';
@@ -26,10 +26,9 @@ export class UsersAdministrationController {
@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;
return a.userId.localeCompare(b.userId);
}
if (this.usersResource.isNew(a.userId)) {
return -1;
@@ -38,10 +37,7 @@ export class UsersAdministrationController {
});
}
@computed get creatingUser() {
return Array.from(this.usersResource.data.values())
.find(user => this.usersResource.isNew(user.userId));
}
@observable creatingUser: AdminUserInfo | null = null;
get isLoading() {
return this.usersResource.isLoading() || this.isDeleting;
@@ -55,7 +51,13 @@ export class UsersAdministrationController {
) { }
create = () => {
this.usersResource.addNew();
this.creatingUser = {
userId: '',
grantedRoles: [],
grantedConnections: [],
configurationParameters: {},
metaParameters: {},
} as AdminUserInfo;
this.usersAdministrationNavigationService.navToAdd();
}
@@ -64,7 +66,7 @@ export class UsersAdministrationController {
return;
}
this.usersResource.delete(this.creatingUser.userId);
this.creatingUser = null;
this.usersAdministrationNavigationService.navToRoot();
}
@@ -7,14 +7,11 @@
*/
import { observer } from 'mobx-react';
import styled, { css, use } from 'reshadow';
import styled, { css } from 'reshadow';
import { UsersResource } from '@cloudbeaver/core-authentication';
import {
TableItem, TableColumnValue, TableItemSelect, TableItemExpand
} from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { useTranslate } from '@cloudbeaver/core-localization';
import { AdminUserInfo } from '@cloudbeaver/core-sdk';
import { useStyles } from '@cloudbeaver/core-theming';
@@ -31,9 +28,6 @@ type Props = {
}
export const User = observer(function User({ user }: Props) {
const translate = useTranslate();
const usersResource = useService(UsersResource);
const isNew = usersResource.isNew(user.userId);
return styled(useStyles(styles))(
<TableItem item={user.userId} expandElement={UserEdit}>
@@ -43,9 +37,9 @@ export const User = observer(function User({ user }: Props) {
<TableColumnValue centerContent flex expand>
<TableItemExpand />
</TableColumnValue>
<TableColumnValue expand>{isNew ? translate('authentication_administration_user_connections_user_new') : user.userId}</TableColumnValue>
<TableColumnValue expand>{user.userId}</TableColumnValue>
<TableColumnValue>{user.grantedRoles.join(', ')}</TableColumnValue>
<TableColumnValue align='right'>{isNew && <tag as='div' {...use({ mod: 'positive' })}>{translate('ui_tag_new')}</tag>}</TableColumnValue>
<TableColumnValue></TableColumnValue>
</TableItem>
);
});
@@ -8,12 +8,34 @@
import { observer } from 'mobx-react';
import { useContext, useCallback } from 'react';
import styled, { css } from 'reshadow';
import {
TableContext,
} from '@cloudbeaver/core-blocks';
import { useController } from '@cloudbeaver/core-di';
import { composes, useStyles } from '@cloudbeaver/core-theming';
import { UserForm } from '../UserForm/UserForm';
import { UserEditController } from './UserEditController';
const styles = composes(
css`
box {
composes: theme-background-secondary theme-text-on-secondary from global;
}
`,
css`
box {
box-sizing: border-box;
padding: 24px;
min-height: 320px;
max-height: 500px;
display: flex;
flex-direction: column;
}
`
);
type Props = {
item: string;
@@ -22,8 +44,15 @@ type Props = {
export const UserEdit = observer(function UserEdit({
item,
}: Props) {
const controller = useController(UserEditController, item);
const tableContext = useContext(TableContext);
const collapse = useCallback(() => tableContext?.setItemExpand(item, false), [tableContext]);
return <UserForm userId={item} onCancel={collapse} />;
return styled(useStyles(styles))(
<box as='div'>
{controller.user && (
<UserForm user={controller.user} onCancel={collapse} editing/>
)}
</box>
);
});
@@ -0,0 +1,55 @@
/*
* 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 { observable, computed } from 'mobx';
import { UsersResource } from '@cloudbeaver/core-authentication';
import {
injectable, IInitializableController, IDestructibleController
} from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
import { GQLErrorCatcher, AdminUserInfo } from '@cloudbeaver/core-sdk';
@injectable()
export class UserEditController
implements IInitializableController, IDestructibleController {
@observable isLoading = true;
@observable user: AdminUserInfo | null = null;
@computed get isDisabled() {
return this.isLoading;
}
userId!: string;
readonly error = new GQLErrorCatcher();
constructor(
private notificationService: NotificationService,
private usersResource: UsersResource,
) { }
init(id: string) {
this.userId = id;
this.loadUser();
}
destruct(): void { }
private async loadUser() {
this.isLoading = true;
try {
// we create a copy to protect the current value from mutation
this.user = JSON.parse(JSON.stringify(await this.usersResource.load(this.userId)));
} catch (exception) {
this.notificationService.logException(exception, `Can't load user ${this.userId}`);
} finally {
this.isLoading = false;
}
}
}
@@ -19,6 +19,9 @@ import { useStyles } from '@cloudbeaver/core-theming';
import { User } from './User';
const styles = css`
Table {
width: 100%;
}
TableColumnHeader {
border-top: solid 1px;
}
@@ -41,7 +41,7 @@ export const DriverProperties = observer(function DriverProperties({
state,
loadProperties,
}: DriverPropertiesProps) {
const controller = useController(DriverPropertiesController, driver);
const controller = useController(DriverPropertiesController, driver, state);
useEffect(() => {
if (loadProperties) {
@@ -56,7 +56,7 @@ export const DriverProperties = observer(function DriverProperties({
<PropertiesTable
properties={controller.driverProperties}
propertiesState={state}
onAdd={controller.onAddProperty}
onAdd={controller.addProperty}
/>
)}
</properties>
@@ -34,21 +34,24 @@ export class DriverPropertiesController implements IInitializableController {
@observable driverProperties = observable<IProperty>([])
private loaded = false;
private state!: Record<string, string>;
constructor(
private driverPropertiesService: DriverPropertiesService,
private notificationService: NotificationService
) { }
init(driver: DBDriver) {
init(driver: DBDriver, state: Record<string, string>) {
this.driver = driver;
this.state = state;
}
onAddProperty = () => {
addProperty = (key?: string, value?: string) => {
this.driverProperties.unshift({
id: uuid(),
key: 'property',
defaultValue: '',
key: key ?? 'property',
defaultValue: value ?? '',
new: !key,
});
}
@@ -67,6 +70,14 @@ export class DriverPropertiesController implements IInitializableController {
description: property.description,
validValues: property.validValues,
})));
for (const key of Object.keys(this.state)) {
if (this.driverProperties.some(property => property.key === key)) {
continue;
}
this.addProperty(key, this.state[key]);
}
this.loaded = true;
} catch (exception) {
this.notificationService.logException(exception, 'Can\'t load driver properties');
@@ -18,7 +18,6 @@ import {
ColDef,
ValueGetterParams,
GridOptions,
CellEditingStoppedEvent,
CellClassParams,
SortChangedEvent,
RowNode,
@@ -74,7 +73,6 @@ export class AgGridTableController implements IInitializableController, IDestruc
onGridReady: this.handleGridReady.bind(this),
onBodyScroll: this.handleBodyScroll.bind(this),
onCellEditingStopped: this.handleCellEditingStopped.bind(this),
onSortChanged: this.handleSortChanged.bind(this),
};
@@ -163,11 +161,6 @@ export class AgGridTableController implements IInitializableController, IDestruc
this.gridModel.onCellEditingStopped(rowIndex, colId, value, editing);
}
private handleCellEditingStopped(event: CellEditingStoppedEvent) {
// TODO: probably no more needed because editCellValue executes on every change in editor
// this.gridModel.onCellEditingStopped(event.rowIndex, event.column.getColId(), event.value, false);
}
private isCellEdited(rowIndex: number, column: string) {
return this.gridModel.isCellEdited(rowIndex, column);
}