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

This commit is contained in:
Serge Rider
2021-10-22 15:14:06 +03:00
16 changed files with 183 additions and 74 deletions
@@ -0,0 +1,31 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { injectable } from '@cloudbeaver/core-di';
import { PluginManagerService } from '@cloudbeaver/core-plugin';
const defaultSettings = {
baseFeatures: [] as string[],
};
export type AdministrationSettings = typeof defaultSettings;
@injectable()
export class AdministrationSettingsService {
readonly settings = this.pluginManagerService.getPluginSettings('core.administration', defaultSettings);
constructor(private pluginManagerService: PluginManagerService) { }
isBase(feature: string): boolean {
return this.getBase().includes(feature);
}
getBase(): string[] {
return this.settings.getValue('baseFeatures');
}
}
@@ -9,6 +9,7 @@ export * from './AdministrationScreen/ConfigurationWizard/ConfigurationWizardSer
export * from './AdministrationScreen/ConfigurationWizard/WizardTopAppBar/WizardTopAppBarService';
export * from './AdministrationToolsPanelStyles';
export * from './AdministrationLocaleService';
export * from './AdministrationSettingsService';
export * from './EAdminPermission';
export * from './useAdministrationSettings';
@@ -95,6 +95,7 @@ interface SwitchType {
export const Switch: SwitchType = observer(function Switch({
name,
id,
value,
defaultValue,
description,
@@ -142,7 +143,7 @@ export const Switch: SwitchType = observer(function Switch({
as='input'
{...rest}
type="checkbox"
id={value || name}
id={id || value || name}
role="switch"
aria-checked={checkboxState.checked}
checked={checkboxState.checked}
@@ -151,7 +152,7 @@ export const Switch: SwitchType = observer(function Switch({
/>
</switch-control-underlay>
</switch-control>
<field-label as="label" htmlFor={value || name}>{children}</field-label>
<field-label as="label" htmlFor={id || value || name}>{children}</field-label>
</switch-body>
<field-description>{description}</field-description>
</field>
@@ -87,5 +87,5 @@ export function useCheckboxState<TKey extends string>(options: CheckboxStateOpti
refresh(count + 1);
},
}), { checked });
}), { checked }, ['change']);
}
@@ -8,6 +8,7 @@
import {
AdministrationLocaleService,
AdministrationSettingsService,
AdministrationTopAppBarService,
AdministrationScreenService,
AdministrationItemService,
@@ -117,6 +118,7 @@ export const coreManifest: PluginManifest = {
RouterService, // important, should be first because the router starts in load phase first after all plugins register phase
NetworkStateService,
AdministrationLocaleService,
AdministrationSettingsService,
AdministrationTopAppBarService,
AdministrationScreenService,
AdministrationScreenServiceBootstrap,
@@ -56,14 +56,20 @@ export class ServerConfigResource extends CachedDataResource<ServerConfig | null
}
get publicDisabled(): boolean {
if (this.data?.configurationMode
|| (this.data?.licenseRequired && !this.data?.licenseValid)) {
if (
this.data?.configurationMode
|| (this.data?.licenseRequired && !this.data?.licenseValid)
) {
return true;
}
return false;
}
get enabledFeatures(): string[] {
return this.update.enabledFeatures || this.data?.enabledFeatures || [];
}
get enabledAuthProviders(): string[] {
return this.update.enabledAuthProviders || this.data?.enabledAuthProviders || [];
}
@@ -76,6 +82,10 @@ export class ServerConfigResource extends CachedDataResource<ServerConfig | null
return this.update.publicCredentialsSaveEnabled ?? this.data?.publicCredentialsSaveEnabled ?? false;
}
isFeatureEnabled(feature: string): boolean {
return this.enabledFeatures.includes(feature);
}
isChanged(): boolean {
if (!this.data || Object.keys(this.update).length === 0) {
return false;
@@ -97,6 +107,7 @@ export class ServerConfigResource extends CachedDataResource<ServerConfig | null
|| this.update.customConnectionsEnabled !== this.data.supportsCustomConnections
|| !isArraysEqual(this.update.enabledAuthProviders || [], this.data.enabledAuthProviders)
|| !isArraysEqual(this.update.enabledFeatures || [], this.data.enabledFeatures)
);
}
@@ -194,5 +205,6 @@ export class ServerConfigResource extends CachedDataResource<ServerConfig | null
this.update.customConnectionsEnabled = serverConfig.supportsCustomConnections;
this.update.enabledAuthProviders = [...serverConfig.enabledAuthProviders];
this.update.enabledFeatures = [...serverConfig.enabledFeatures];
}
}
@@ -6,12 +6,18 @@
* you may not use this file except in compliance with the License.
*/
import { makeObservable, observable } from 'mobx';
import type { ISettingsSource } from './ISettingsSource';
export class SettingsSource implements ISettingsSource {
protected store = new Map<string, any>();
constructor(protected fallback?: ISettingsSource) {}
constructor(protected fallback?: ISettingsSource) {
makeObservable<this, 'store'>(this, {
store: observable,
});
}
has(key: string): boolean {
return this.store.has(key) || !!this.fallback?.has(key);
@@ -12,6 +12,12 @@ export function parseJSONFlat(
scope?: string
) {
if (typeof object === 'object') {
if (Array.isArray(object)) {
if (scope) {
setValue(scope, object);
}
return;
}
for (const [key, value] of Object.entries<any>(object)) {
parseJSONFlat(value, setValue, `${scope ? `${scope}.` : ''}${key}`);
}
@@ -0,0 +1,65 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
import { useContext } from 'react';
import styled from 'reshadow';
import { AdministrationSettingsService } from '@cloudbeaver/core-administration';
import { BASE_CONTAINERS_STYLES, FormContext, GroupTitle, Loader, PlaceholderComponent, Switch, useDataResource } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { useTranslate } from '@cloudbeaver/core-localization';
import { FeaturesResource } from '@cloudbeaver/core-root';
import { CachedMapAllKey } from '@cloudbeaver/core-sdk';
import { useStyles } from '@cloudbeaver/core-theming';
import type { IConfigurationPlaceholderProps } from '@cloudbeaver/plugin-administration';
export const ServerConfigurationFeaturesForm: PlaceholderComponent<IConfigurationPlaceholderProps> = observer(function ServerConfigurationFeaturesForm({
state: { serverConfig },
configurationWizard,
}) {
const administrationSettingsService = useService(AdministrationSettingsService);
const features = useDataResource(ServerConfigurationFeaturesForm, FeaturesResource, CachedMapAllKey);
const translate = useTranslate();
const styles = useStyles(BASE_CONTAINERS_STYLES);
const formContext = useContext(FormContext);
if (formContext === null) {
throw new Error('Form state should be provided');
}
if (features.data.length === 0) {
return null;
}
return styled(styles)(
<>
<GroupTitle>{translate('administration_configuration_wizard_configuration_features_group')}</GroupTitle>
<Loader state={features} inline>
{() => styled(styles)(
<>
{features.data.map(feature => (
<Switch
key={feature.id}
value={feature.id}
name="enabledFeatures"
state={serverConfig}
description={feature.description}
disabled={administrationSettingsService.isBase(feature.id)}
mod={['primary']}
small
>
{feature.label}
</Switch>
))}
</>
)}
</Loader>
</>
);
});
@@ -18,6 +18,7 @@ import { ServerConfigResource } from '@cloudbeaver/core-root';
import { useStyles } from '@cloudbeaver/core-theming';
import { ServerConfigurationConfigurationForm } from './Form/ServerConfigurationConfigurationForm';
import { ServerConfigurationFeaturesForm } from './Form/ServerConfigurationFeaturesForm';
import { ServerConfigurationInfoForm } from './Form/ServerConfigurationInfoForm';
import { ServerConfigurationNavigatorViewForm } from './Form/ServerConfigurationNavigatorViewForm';
import { ServerConfigurationSecurityForm } from './Form/ServerConfigurationSecurityForm';
@@ -123,6 +124,7 @@ export const ServerConfigurationPage: AdministrationItemContentComponent = obser
<GroupTitle>{translate('administration_configuration_wizard_configuration_plugins')}</GroupTitle>
<ServerConfigurationConfigurationForm serverConfig={service.state.serverConfig} />
<ServerConfigurationNavigatorViewForm configs={service.state} />
<ServerConfigurationFeaturesForm state={service.state} configurationWizard={configurationWizard} />
<Placeholder container={service.pluginsContainer} />
</Group>
<ServerConfigurationSecurityForm serverConfig={service.state.serverConfig} />
@@ -35,7 +35,6 @@ export const AuthenticationProviders: PlaceholderComponent<IConfigurationPlaceho
}
const localProvider = providers.resource.get(AUTH_PROVIDER_LOCAL_ID);
const services = providers.resource.values.filter(provider => provider.id !== AUTH_PROVIDER_LOCAL_ID);
const externalAuthentication = providers.data.length === 1 && localProvider === undefined;
const authenticationDisabled = serverConfig.enabledAuthProviders?.length === 0;
@@ -54,10 +53,10 @@ export const AuthenticationProviders: PlaceholderComponent<IConfigurationPlaceho
return styled(styles)(
<Container wrap gap>
{localProvider && (
<>
<Group key='authentication' form gap medium>
<GroupTitle>{translate('administration_configuration_wizard_configuration_authentication_group')}</GroupTitle>
<Group key='authentication' form gap medium>
<GroupTitle>{translate('administration_configuration_wizard_configuration_authentication_group')}</GroupTitle>
{localProvider && (
<>
<Switch
name="anonymousAccessEnabled"
state={serverConfig}
@@ -69,67 +68,46 @@ export const AuthenticationProviders: PlaceholderComponent<IConfigurationPlaceho
>
{translate('administration_configuration_wizard_configuration_anonymous_access')}
</Switch>
<Loader state={providers} inline>
{() => localProvider && styled(styles)(
<Switch
key={localProvider.id}
value={localProvider.id}
name="enabledAuthProviders"
state={serverConfig}
description={localProvider.description}
mod={['primary']}
small
autoHide
>
{localProvider.label}
</Switch>
)}
</Loader>
</Group>
{configurationWizard && (
<ServerConfigurationAdminForm serverConfig={serverConfig} />
)}
</>
)}
{services.length > 0 ? (
<Group key='services' form gap medium>
<GroupTitle>{translate('administration_configuration_wizard_configuration_services')}</GroupTitle>
<Loader state={providers} inline>
{() => styled(styles)(
<>
{services.map(provider => {
const links = authProviderService.getServiceDescriptionLinks(provider);
</>
)}
<Loader state={providers} inline>
{() => styled(styles)(
<>
{providers.resource.values.map(provider => {
const links = authProviderService.getServiceDescriptionLinks(provider);
return (
<Switch
key={provider.id}
value={provider.id}
name="enabledAuthProviders"
state={serverConfig}
description={(
<>
{provider.description}
{links.map(link => {
const Description = link.description();
return <Description key={link.id} configurationWizard={configurationWizard} />;
})}
</>
)}
disabled={providers.resource.isBase(provider.id)}
mod={['primary']}
small
autoHide
>
{provider.label}
</Switch>
);
})}
</>
)}
</Loader>
</Group>
return (
<Switch
key={provider.id}
id={`authProvider_${provider.id}`}
value={provider.id}
name="enabledAuthProviders"
state={serverConfig}
description={(
<>
{provider.description}
{links.map(link => {
const Description = link.description();
return <Description key={link.id} configurationWizard={configurationWizard} />;
})}
</>
)}
disabled={providers.resource.isBase(provider.id)}
mod={['primary']}
small
autoHide
>
{provider.label}
</Switch>
);
})}
</>
)}
</Loader>
</Group>
{configurationWizard && localProvider ? (
<ServerConfigurationAdminForm serverConfig={serverConfig} />
) : <Container medium />}
{!(configurationWizard && localProvider) && <Container medium />}
</Container>
);
});
@@ -56,6 +56,7 @@ export class ServerConfigurationAuthenticationBootstrap extends Bootstrap {
data.state.serverConfig.anonymousAccessEnabled = config.anonymousAccessEnabled;
data.state.serverConfig.enabledAuthProviders = [...config.enabledAuthProviders];
data.state.serverConfig.enabledFeatures = [...config.enabledFeatures];
} catch (exception) {
ExecutorInterrupter.interrupt(contexts);
this.notificationService.logException(exception, 'Can\'t load server configuration');
@@ -85,8 +85,8 @@ export const AuthDialog: DialogComponent<IAuthPayload, null> = observer(function
const controller = useController(AuthDialogController, link, providerId, rejectDialog);
const translate = useTranslate();
if (!providerId) {
controller.setAdminMode(administrationScreenService.activeScreen !== null);
if (administrationScreenService.activeScreen !== null) {
controller.setAdminMode(administrationScreenService.activeScreen !== null, providerId);
}
const additional = userInfo.data !== null
@@ -92,11 +92,13 @@ export class AuthDialogController implements IInitializableController, IDestruct
this.loadProviders();
}
setAdminMode(mode: boolean): void {
setAdminMode(mode: boolean, providerId: string | null): void {
if (this.admin !== mode) {
this.admin = mode;
this.selectFirstAvailable();
if (!providerId) {
this.selectFirstAvailable();
}
}
}
@@ -53,6 +53,7 @@ export default [
['administration_configuration_wizard_configuration_anonymous_access', 'Allow anonymous access'],
['administration_configuration_wizard_configuration_anonymous_access_description', 'Allows to work with CloudBeaver without user authentication'],
['administration_configuration_wizard_configuration_authentication_group', 'Authentication settings'],
['administration_configuration_wizard_configuration_features_group', 'Features'],
['administration_configuration_wizard_configuration_services', 'Services'],
['administration_configuration_wizard_configuration_authentication', 'Enable users authentication'],
['administration_configuration_wizard_configuration_authentication_description', 'Allows users to authenticate. Otherwise only anonymous access is enabled'],
@@ -51,6 +51,7 @@ export default [
['administration_configuration_wizard_configuration_anonymous_access', 'Permetti l\'accesso anonimo'],
['administration_configuration_wizard_configuration_anonymous_access_description', 'Permetti di lavorare con CloudBeaver senza autenticazione'],
['administration_configuration_wizard_configuration_authentication_group', 'Impostazioni di autenticazione'],
['administration_configuration_wizard_configuration_features_group', 'Функции'],
['administration_configuration_wizard_configuration_services', 'Servizi'],
['administration_configuration_wizard_configuration_authentication', 'Abilita l\'autenticazione utente'],
['administration_configuration_wizard_configuration_authentication_description', 'Permetti agli utenti di autenticarsi. In alternativa solo l\'accesso anonimo sarà attivo'],