mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
Merge remote-tracking branch 'origin/devel' into devel
This commit is contained in:
+7
-2
@@ -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"
|
||||
|
||||
+8
-2
@@ -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}
|
||||
|
||||
+12
-2
@@ -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';
|
||||
|
||||
/**
|
||||
|
||||
+2
-2
@@ -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>
|
||||
|
||||
+16
-4
@@ -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');
|
||||
|
||||
+17
-1
@@ -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} />
|
||||
|
||||
+1
-6
@@ -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;
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
|
||||
+3
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
+8
-9
@@ -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
|
||||
>
|
||||
|
||||
+21
-30
@@ -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');
|
||||
|
||||
+11
-9
@@ -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>
|
||||
);
|
||||
});
|
||||
|
||||
+30
-1
@@ -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>
|
||||
);
|
||||
});
|
||||
|
||||
+55
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -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;
|
||||
}
|
||||
|
||||
+2
-2
@@ -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>
|
||||
|
||||
+15
-4
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user