feat(plugin-administration): CB-340 server configuration page

This commit is contained in:
Wroud
2020-10-09 03:22:47 +03:00
parent 91e23f4ba1
commit 20e1bc48b3
8 changed files with 177 additions and 88 deletions
@@ -115,9 +115,9 @@ export class AdministrationScreenService {
}
getItemState<T>(name: string): T | undefined
getItemState<T>(name: string, defaultState: () => T): T
getItemState<T>(name: string, defaultState?: () => T): T | undefined {
if (!this.itemState.has(name) && defaultState) {
getItemState<T>(name: string, defaultState: () => T, update?: boolean): T
getItemState<T>(name: string, defaultState?: () => T, update?: boolean): T | undefined {
if ((!this.itemState.has(name) || update) && defaultState) {
this.itemState.set(name, defaultState());
}
@@ -31,7 +31,7 @@ export class ConnectionsAdministrationService extends Bootstrap {
this.administrationItemService.create({
name: 'connections',
type: AdministrationItemType.Default,
order: 2,
order: 3,
configurationWizardOptions: {
defaultRoute: { sub: 'create', param: 'search-database' },
description: 'connections_administration_configuration_wizard_step_description',
@@ -40,7 +40,7 @@ export class ConfigurationWizardPagesBootstrapService extends Bootstrap {
});
this.administrationItemService.create({
name: 'configuration',
type: AdministrationItemType.ConfigurationWizard,
type: AdministrationItemType.Default,
configurationWizardOptions: {
description: 'administration_configuration_wizard_configuration_step_description',
isDone: this.serverConfigurationService.isDone.bind(this.serverConfigurationService),
@@ -48,6 +48,7 @@ export class ConfigurationWizardPagesBootstrapService extends Bootstrap {
onConfigurationFinish: this.serverConfigurationService.apply.bind(this.serverConfigurationService),
},
order: 1.1,
onActivate: this.serverConfigurationService.loadConfig.bind(this.serverConfigurationService),
getContentComponent: () => ServerConfigurationPage,
getDrawerComponent: () => ServerConfigurationDrawerItem,
});
@@ -7,19 +7,17 @@
*/
import { observer } from 'mobx-react';
import { useEffect } from 'react';
import { useCallback, useEffect } from 'react';
import styled, { css } from 'reshadow';
import {
SubmittingForm, InputGroup, InputField, useFocus, Switch
SubmittingForm, InputGroup, InputField, useFocus, Switch, Button
} from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { IExecutor } from '@cloudbeaver/core-executor';
import { useTranslate } from '@cloudbeaver/core-localization';
import { ServerConfigInput } from '@cloudbeaver/core-sdk';
import { useStyles } from '@cloudbeaver/core-theming';
import { ServerConfigurationService } from './ServerConfigurationService';
export const formStyles = css`
SubmittingForm {
flex: 1;
@@ -31,6 +29,11 @@ export const formStyles = css`
box-sizing: border-box;
display: flex;
}
Button {
margin: 12px;
margin-left: 24px;
float: right;
}
`;
const boxStyles = css`
@@ -46,31 +49,38 @@ const boxStyles = css`
interface Props {
serverConfig: ServerConfigInput;
validationTask?: IExecutor<boolean>;
editing?: boolean;
onChange: () => void;
onSave: () => void;
onSubmit: () => void;
}
export const ServerConfigurationForm = observer(function ServerConfigurationForm({
serverConfig,
validationTask,
editing,
onChange,
onSave,
onSubmit,
}: Props) {
const service = useService(ServerConfigurationService);
const translate = useTranslate();
const [focusedRef] = useFocus<HTMLFormElement>({ focusFirstChild: true });
useEffect(() => {
const validate = () => {
focusedRef.current?.checkValidity();
focusedRef.current?.reportValidity();
};
service.validationTask.addHandler(validate);
const validate = useCallback(() => {
focusedRef.current?.checkValidity();
focusedRef.current?.reportValidity();
}, []);
return () => service.validationTask.removeHandler(validate);
}, [service]);
useEffect(() => {
if (!validationTask) {
return;
}
validationTask.addHandler(validate);
return () => validationTask.removeHandler(validate);
}, [validationTask]);
return styled(useStyles(formStyles, boxStyles))(
<SubmittingForm ref={focusedRef} name='server_config' onSubmit={onSave}>
<SubmittingForm ref={focusedRef} name='server_config' onSubmit={onSubmit}>
<box as="div">
<box-element as='div'>
<group as="div">
@@ -88,31 +98,37 @@ export const ServerConfigurationForm = observer(function ServerConfigurationForm
{translate('administration_configuration_wizard_configuration_server_name')}
</InputField>
</group>
<group as="div">
<InputGroup long>{translate('administration_configuration_wizard_configuration_admin')}</InputGroup>
</group>
<InputField
type="text"
name="adminName"
state={serverConfig}
mod='surface'
minLength={6}
required
long
>
{translate('administration_configuration_wizard_configuration_admin_name')}
</InputField>
<InputField
type="password"
name="adminPassword"
state={serverConfig}
autoComplete='new-password'
mod='surface'
required
long
>
{translate('administration_configuration_wizard_configuration_admin_password')}
</InputField>
{editing
? <group as="div"><Button mod={['raised']}>Save</Button></group>
: (
<>
<group as="div">
<InputGroup long>{translate('administration_configuration_wizard_configuration_admin')}</InputGroup>
</group>
<InputField
type="text"
name="adminName"
state={serverConfig}
mod='surface'
minLength={6}
required
long
>
{translate('administration_configuration_wizard_configuration_admin_name')}
</InputField>
<InputField
type="password"
name="adminPassword"
state={serverConfig}
autoComplete='new-password'
mod='surface'
required
long
>
{translate('administration_configuration_wizard_configuration_admin_password')}
</InputField>
</>
)}
</box-element>
<box-element as='div'>
<group as="div">
@@ -9,12 +9,13 @@
import { observer } from 'mobx-react';
import styled, { use, css } from 'reshadow';
import { useController } from '@cloudbeaver/core-di';
import { useController, useService } from '@cloudbeaver/core-di';
import { useTranslate } from '@cloudbeaver/core-localization';
import { useStyles, composes } from '@cloudbeaver/core-theming';
import { ServerConfigurationForm } from './ServerConfigurationForm';
import { ServerConfigurationPageController } from './ServerConfigurationPageController';
import { ServerConfigurationService } from './ServerConfigurationService';
const styles = composes(
css`
@@ -50,18 +51,25 @@ const styles = composes(
export const ServerConfigurationPage = observer(function ServerConfigurationPage() {
const translate = useTranslate();
const service = useService(ServerConfigurationService);
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>{translate('administration_configuration_wizard_configuration_title')}</h3>
<p>{translate('administration_configuration_wizard_configuration_message')}</p>
{!controller.editing && (
<>
<h3>{translate('administration_configuration_wizard_configuration_title')}</h3>
<p>{translate('administration_configuration_wizard_configuration_message')}</p>
</>
)}
<ServerConfigurationForm
serverConfig={controller.state.serverConfig}
validationTask={service.validationTask}
editing={controller.editing}
onChange={controller.onChange}
onSave={controller.finish}
onSubmit={controller.finish}
/>
</layout-grid-cell>
</layout-grid-inner>
@@ -6,7 +6,7 @@
* you may not use this file except in compliance with the License.
*/
import { ConfigurationWizardService } from '@cloudbeaver/core-administration';
import { AdministrationScreenService, ConfigurationWizardService } from '@cloudbeaver/core-administration';
import { injectable } from '@cloudbeaver/core-di';
import { IServerConfigurationPageState } from './IServerConfigurationPageState';
@@ -18,9 +18,14 @@ export class ServerConfigurationPageController {
return this.serverConfigurationService.state;
}
get editing(): boolean {
return !this.administrationScreenService.isConfigurationMode;
}
constructor(
private readonly configurationWizardService: ConfigurationWizardService,
private readonly serverConfigurationService: ServerConfigurationService,
private readonly administrationScreenService: AdministrationScreenService,
) {
}
@@ -30,5 +35,13 @@ export class ServerConfigurationPageController {
}
};
finish = async (): Promise<void> => await this.configurationWizardService.next();
finish = async (): Promise<void> => {
if (this.administrationScreenService.isConfigurationMode) {
await this.configurationWizardService.next();
} else {
if (this.serverConfigurationService.validate()) {
await this.serverConfigurationService.apply();
}
}
};
}
@@ -11,7 +11,8 @@ import { UsersResource } from '@cloudbeaver/core-authentication';
import { injectable } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
import { IExecutor, Executor } from '@cloudbeaver/core-executor';
import { GraphQLService } from '@cloudbeaver/core-sdk';
import { ServerConfigResource } from '@cloudbeaver/core-root';
import { GraphQLService, ServerConfig } from '@cloudbeaver/core-sdk';
import { IServerConfigurationPageState } from './IServerConfigurationPageState';
@@ -23,37 +24,35 @@ export interface IValidationStatusContext {
@injectable()
export class ServerConfigurationService {
readonly state: IServerConfigurationPageState;
state: IServerConfigurationPageState;
readonly validationTask: IExecutor<boolean>;
constructor(
private administrationScreenService: AdministrationScreenService,
private graphQLService: GraphQLService,
private notificationService: NotificationService,
private usersResource: UsersResource
private readonly administrationScreenService: AdministrationScreenService,
private readonly serverConfigResource: ServerConfigResource,
private readonly graphQLService: GraphQLService,
private readonly notificationService: NotificationService,
private readonly usersResource: UsersResource
) {
this.state = this.administrationScreenService.getItemState<IServerConfigurationPageState>('welcome', () => ({
serverConfig: {
serverName: 'CloudBeaver',
adminName: 'cbadmin',
adminPassword: '',
anonymousAccessEnabled: true,
authenticationEnabled: false,
customConnectionsEnabled: false,
},
navigatorConfig: {
hideFolders: false,
hideSchemas: false,
hideVirtualModel: false,
mergeEntities: false,
showOnlyEntities: false,
showSystemObjects: false,
showUtilityObjects: false,
},
}));
this.state = this.administrationScreenService
.getItemState(
'server-configuration',
() => this.getConfig(),
);
this.validationTask = new Executor();
}
async loadConfig() {
const config = await this.serverConfigResource.load(null);
this.state = this.administrationScreenService
.getItemState(
'server-configuration',
() => this.getConfig(config),
!config?.configurationMode
);
}
isDone(): boolean {
return this.isFormFilled();
}
@@ -79,15 +78,6 @@ export class ServerConfigurationService {
};
};
private isFormFilled() {
return !!(
this.state?.serverConfig.serverName
&& this.state.serverConfig.adminName
&& this.state.serverConfig.adminName.length > 5
&& this.state.serverConfig.adminPassword
);
}
async apply(): Promise<void> {
if (!this.state) {
throw new Error('No state available');
@@ -96,6 +86,10 @@ export class ServerConfigurationService {
try {
await this.graphQLService.sdk.setDefaultNavigatorSettings({ settings: this.state.navigatorConfig });
await this.graphQLService.sdk.configureServer({ configuration: this.state.serverConfig });
if (!this.serverConfigResource.data?.configurationMode) {
await this.serverConfigResource.refresh(null);
}
this.usersResource.refreshAllLazy();
} catch (exception) {
this.notificationService.logException(exception, 'Can\'t save server configuration');
@@ -104,4 +98,61 @@ export class ServerConfigurationService {
}
}
private isFormFilled() {
if (!this.state?.serverConfig.serverName) {
return false;
}
if (this.administrationScreenService.isConfigurationMode) {
if (!this.state.serverConfig.adminName
|| this.state.serverConfig.adminName.length < 6
|| !this.state.serverConfig.adminPassword
) {
return false;
}
}
return true;
}
private getConfig(config?: ServerConfig | null): IServerConfigurationPageState {
if (!config || config?.configurationMode) {
return {
serverConfig: {
serverName: 'CloudBeaver',
adminName: 'cbadmin',
adminPassword: '',
anonymousAccessEnabled: true,
authenticationEnabled: false,
customConnectionsEnabled: false,
},
navigatorConfig: {
hideFolders: false,
hideSchemas: false,
hideVirtualModel: false,
mergeEntities: false,
showOnlyEntities: false,
showSystemObjects: false,
showUtilityObjects: false,
},
};
}
return {
serverConfig: {
serverName: config.name,
anonymousAccessEnabled: config.anonymousAccessEnabled,
authenticationEnabled: config.authenticationEnabled,
customConnectionsEnabled: config.supportsCustomConnections,
},
navigatorConfig: {
hideFolders: false,
hideSchemas: false,
hideVirtualModel: false,
mergeEntities: false,
showOnlyEntities: false,
showSystemObjects: false,
showUtilityObjects: false,
},
};
}
}
@@ -28,7 +28,7 @@ export class UsersAdministrationService extends Bootstrap {
register() {
this.administrationItemService.create({
name: UsersAdministrationNavigationService.ItemName,
order: 1,
order: 2,
sub: [
{
name: UsersAdministrationNavigationService.AddItemName,