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

This commit is contained in:
Serge Rider
2021-03-24 14:01:35 +03:00
26 changed files with 231 additions and 221 deletions
@@ -79,7 +79,7 @@ export const Administration: React.FC<Props> = observer(function Administration(
const contentRef = useRef<HTMLDivElement>(null);
const administrationItemService = useService(AdministrationItemService);
const items = administrationItemService.getActiveItems(configurationWizard);
const hasOnlyActive = items.some(filterOnlyActive);
const hasOnlyActive = items.some(filterOnlyActive(configurationWizard));
useLayoutEffect(() => {
contentRef.current?.scrollTo({ top: 0, left: 0 });
@@ -24,7 +24,7 @@ export const DrawerItem = observer(function DrawerItem({
disabled = !configurationWizardService.isStepAvailable(item.name);
}
const onlyActive = filterOnlyActive(item) ? false : disabled;
const onlyActive = filterOnlyActive(configurationWizard)(item) ? false : disabled;
return (
<Component
@@ -28,9 +28,33 @@ export class AdministrationItemService {
});
}
getUniqueItems(configurationWizard: boolean): IAdministrationItem[] {
const items: IAdministrationItem[] = [];
const orderedByPriority = this.items.slice()
.sort((a, b) => {
if (a.replace && b.replace) {
return (b.replace.priority ?? Number.MIN_SAFE_INTEGER) - (a.replace.priority ?? Number.MIN_SAFE_INTEGER);
}
return 0;
})
.filter(item => !item.replace?.condition || item.replace.condition(configurationWizard));
let lastItem: IAdministrationItem | null = null;
for (const item of orderedByPriority) {
if (lastItem?.name === item.name) {
continue;
}
items.push(item);
lastItem = item;
}
return items;
}
getActiveItems(configurationWizard: boolean): IAdministrationItem[] {
return this.items.filter(item =>
filterHiddenAdministrationItem(item)
return this.getUniqueItems(configurationWizard).filter(item =>
filterHiddenAdministrationItem(configurationWizard)(item)
&& filterConfigurationWizard(configurationWizard)(item)
).sort(orderAdministrationItems(configurationWizard));
}
@@ -42,7 +66,7 @@ export class AdministrationItemService {
return null;
}
const onlyActive = items.find(filterOnlyActive);
const onlyActive = items.find(filterOnlyActive(configurationWizard));
if (onlyActive) {
return onlyActive.name;
@@ -60,11 +84,7 @@ export class AdministrationItemService {
}
getItem(name: string, configurationWizard: boolean): IAdministrationItem | null {
const item = this.items.find(item => (
filterHiddenAdministrationItem(item)
&& filterConfigurationWizard(configurationWizard)(item)
&& item.name === name
));
const item = this.getActiveItems(configurationWizard).find(item => item.name === name);
if (!item) {
return null;
@@ -82,7 +102,7 @@ export class AdministrationItemService {
return sub;
}
create(options: IAdministrationItemOptions, replace?: boolean): void {
create(options: IAdministrationItemOptions): void {
const type = options.type ?? AdministrationItemType.Administration;
const existedIndex = this.items.findIndex(item => item.name === options.name && (
@@ -91,7 +111,7 @@ export class AdministrationItemService {
|| type === AdministrationItemType.Default
));
if (!replace && existedIndex !== -1) {
if (!options.replace && existedIndex !== -1) {
throw new Error(`Administration item "${options.name}" already exists in the same visibility scope`);
}
@@ -101,11 +121,7 @@ export class AdministrationItemService {
sub: options.sub ?? [],
order: options.order ?? Number.MAX_SAFE_INTEGER,
};
if (replace && existedIndex !== -1) {
this.items.splice(existedIndex, 1, item);
} else {
this.items.push(item);
}
this.items.push(item);
}
async activate(
@@ -113,12 +129,16 @@ export class AdministrationItemService {
configurationWizard: boolean,
outside: boolean
): Promise<void> {
const item = this.getItem(screen.item, configurationWizard);
if (!item) {
return;
if (configurationWizard) {
const items = this.getActiveItems(configurationWizard);
for (const item of items) {
await item.configurationWizardOptions?.onLoad?.();
}
}
if (!filterHiddenAdministrationItem(item)) {
const item = this.getItem(screen.item, configurationWizard);
if (!item) {
return;
}
@@ -139,10 +159,6 @@ export class AdministrationItemService {
return;
}
if (!filterHiddenAdministrationItem(item)) {
return;
}
await item.onDeActivate?.(configurationWizard, outside);
if (screen.sub) {
@@ -160,10 +176,6 @@ export class AdministrationItemService {
return false;
}
if (!filterHiddenAdministrationItem(item)) {
return false;
}
if (item.canActivate && !(await item.canActivate(configurationWizard, outside))) {
return false;
}
@@ -179,18 +191,22 @@ export class AdministrationItemService {
}
}
export function filterHiddenAdministrationItem(item: IAdministrationItem): boolean {
if (typeof item.isHidden === 'function') {
return !item.isHidden();
}
export function filterHiddenAdministrationItem(configurationWizard: boolean): (item: IAdministrationItem) => boolean {
return function filterHiddenAdministrationItem(item: IAdministrationItem) {
if (typeof item.isHidden === 'function') {
return !item.isHidden(configurationWizard);
}
return !item.isHidden;
return !item.isHidden;
};
}
export function filterOnlyActive(item: IAdministrationItem): boolean {
if (typeof item.isOnlyActive === 'function') {
return item.isOnlyActive();
}
export function filterOnlyActive(configurationWizard: boolean): (item: IAdministrationItem) => boolean {
return function filterOnlyActive(item: IAdministrationItem) {
if (typeof item.isOnlyActive === 'function') {
return item.isOnlyActive(configurationWizard);
}
return item.isOnlyActive === true;
return item.isOnlyActive === true;
};
}
@@ -16,6 +16,11 @@ export enum AdministrationItemType {
ConfigurationWizard
}
export interface IAdministrationItemReplaceOptions {
priority: number;
condition?: (configurationWizard: boolean) => boolean;
}
export interface AdministrationItemDrawerProps {
item: IAdministrationItem;
configurationWizard: boolean;
@@ -69,8 +74,9 @@ export interface IConfigurationWizardItemOptions {
isDisabled?: () => boolean;
isHidden?: () => boolean;
isDone?: () => boolean;
onLoad?: () => Promise<void> | void;
onFinish?: () => Promise<boolean> | boolean;
onConfigurationFinish?: () => Promise<void> | void;
onConfigurationFinish?: () => Promise<any> | any;
}
export interface IAdministrationItemOptions {
@@ -80,8 +86,9 @@ export interface IAdministrationItemOptions {
configurationWizardOptions?: IConfigurationWizardItemOptions;
order?: number;
sub?: IAdministrationItemSubItem[];
isHidden?: (() => boolean) | boolean;
isOnlyActive?: (() => boolean) | boolean;
isHidden?: ((configurationWizard: boolean) => boolean) | boolean;
isOnlyActive?: ((configurationWizard: boolean) => boolean) | boolean;
replace?: IAdministrationItemReplaceOptions;
getDrawerComponent: () => AdministrationItemDrawerComponent;
getContentComponent: () => AdministrationItemContentComponent;
onActivate?: AdministrationItemEvent;
@@ -11,7 +11,7 @@ import { computed, makeObservable } from 'mobx';
import { injectable } from '@cloudbeaver/core-di';
import { ScreenService } from '@cloudbeaver/core-routing';
import { AdministrationItemService } from '../../AdministrationItem/AdministrationItemService';
import { AdministrationItemService, filterHiddenAdministrationItem } from '../../AdministrationItem/AdministrationItemService';
import { filterConfigurationWizard } from '../../AdministrationItem/filterConfigurationWizard';
import type { IAdministrationItem } from '../../AdministrationItem/IAdministrationItem';
import { orderAdministrationItems } from '../../AdministrationItem/orderAdministrationItems';
@@ -20,8 +20,11 @@ import { AdministrationScreenService } from '../AdministrationScreenService';
@injectable()
export class ConfigurationWizardService {
get steps(): IAdministrationItem[] {
return this.administrationItemService.items
.filter(filterConfigurationWizard(true))
return this.administrationItemService.getUniqueItems(true)
.filter(item =>
filterConfigurationWizard(true)(item)
&& filterHiddenAdministrationItem(true)(item)
)
.sort(orderAdministrationItems(true));
}
@@ -163,7 +166,7 @@ export class ConfigurationWizardService {
}
private getStep(name: string) {
return this.administrationItemService.items
return this.administrationItemService.getUniqueItems(true)
.find(step => filterConfigurationWizard(true)(step) && step.name === name);
}
@@ -7,7 +7,7 @@
*/
import { injectable } from '@cloudbeaver/core-di';
import { PermissionsResource } from '@cloudbeaver/core-root';
import { PermissionsResource, ServerConfigResource } from '@cloudbeaver/core-root';
import {
GraphQLService,
CachedDataResource,
@@ -20,13 +20,22 @@ export type AuthProvider = Omit<AuthProviderInfo, 'configurationParameters'>;
export class AuthProvidersResource extends CachedDataResource<AuthProvider[], void> {
constructor(
private graphQLService: GraphQLService,
private permissionsResource: PermissionsResource
private permissionsResource: PermissionsResource,
private serverConfigResource: ServerConfigResource
) {
super([]);
this.permissionsResource.onDataUpdate.addHandler(() => this.markOutdated());
}
getEnabledProviders(): string[] {
return this.serverConfigResource.data?.enabledAuthProviders ?? [];
}
isEnabled(id: string): boolean {
return this.serverConfigResource.data?.enabledAuthProviders.includes(id) ?? false;
}
protected async loader(): Promise<AuthProvider[]> {
const { providers } = await this.graphQLService.sdk.getAuthProviders();
return providers;
@@ -10,18 +10,17 @@ import { observer } from 'mobx-react-lite';
import type { PlaceholderContainer } from './PlaceholderContainer';
type Props<T> = {
type Props<T extends Record<string, any>> = T & {
container: PlaceholderContainer<T>;
} & (T extends undefined ? {
context?: undefined;
} : {
context: T;
});
};
export const Placeholder = observer(function Placeholder<T>({ container, context }: Props<T>) {
export const Placeholder = observer(function Placeholder<T extends Record<string, any>>({
container,
...rest
}: Props<T>) {
return (
<>
{container.get().map(({ id, component: Component }) => <Component key={id} context={context!} />)}
{container.get().map(({ id, component: Component }) => <Component key={id} {...(rest as any as T)} />)}
</>
);
});
@@ -10,19 +10,15 @@ import { observable } from 'mobx';
import { uuid } from '@cloudbeaver/core-utils';
export interface PlaceholderProps<T = undefined> {
context: T;
}
export type PlaceholderComponent<T extends Record<string, any> = Record<string, any>> = React.FunctionComponent<T>;
export type PlaceholderComponent<T = undefined> = React.FunctionComponent<PlaceholderProps<T>>;
export interface PlaceholderElement<T = undefined> {
export interface PlaceholderElement<T extends Record<string, any> = Record<string, any>> {
id: string;
component: PlaceholderComponent<T>;
order?: number;
}
export class PlaceholderContainer<T = undefined> {
export class PlaceholderContainer<T extends Record<string, any> = Record<string, any>> {
private placeholders = observable<PlaceholderElement<T>>([], { deep: false });
get(): Array<PlaceholderElement<T>> {
@@ -22,9 +22,13 @@ import { SSH } from './ConnectionsTable/ConnectionDetailsInfo/SSH';
import { Template } from './ConnectionsTable/ConnectionDetailsInfo/Template';
import { CreateConnectionService } from './CreateConnectionService';
export interface IConnectionDetailsPlaceholderProps {
connection: DatabaseConnection;
}
@injectable()
export class ConnectionsAdministrationService extends Bootstrap {
readonly connectionDetailsPlaceholder = new PlaceholderContainer<DatabaseConnection>();
readonly connectionDetailsPlaceholder = new PlaceholderContainer<IConnectionDetailsPlaceholderProps>();
constructor(
private administrationItemService: AdministrationItemService,
@@ -61,7 +61,10 @@ export const Connection = observer(function Connection({ connection }: Props) {
<TableColumnValue expand>{connection.name}</TableColumnValue>
<TableColumnValue>{connection.host}{connection.host && connection.port && `:${connection.port}`}</TableColumnValue>
<TableColumnValue flex>
<Placeholder container={connectionsAdministrationService.connectionDetailsPlaceholder} context={connection} />
<Placeholder
container={connectionsAdministrationService.connectionDetailsPlaceholder}
connection={connection}
/>
</TableColumnValue>
</TableItem>
);
@@ -9,19 +9,18 @@
import { observer } from 'mobx-react-lite';
import styled from 'reshadow';
import { StaticImage } from '@cloudbeaver/core-blocks';
import { AUTH_PROVIDER_LOCAL_ID } from '@cloudbeaver/core-authentication';
import { PlaceholderComponent, StaticImage } from '@cloudbeaver/core-blocks';
import type { DatabaseConnection } from '../../../ConnectionsResource';
import type { IConnectionDetailsPlaceholderProps } from '../../ConnectionsAdministrationService';
import { CONNECTION_DETAILS_STYLES } from './ConnectionDetailsStyles';
interface Props {
context: DatabaseConnection;
}
export const Origin: React.FC<Props> = observer(function Origin({ context }) {
const isLocal = context.origin.type === 'local';
const icon = context.origin.icon;
const title = context.origin.displayName;
export const Origin: PlaceholderComponent<IConnectionDetailsPlaceholderProps> = observer(function Origin({
connection,
}) {
const isLocal = connection.origin.type === AUTH_PROVIDER_LOCAL_ID;
const icon = connection.origin.icon;
const title = connection.origin.displayName;
if (isLocal) {
return null;
@@ -9,21 +9,17 @@
import { observer } from 'mobx-react-lite';
import styled from 'reshadow';
import { StaticImage, useMapResource } from '@cloudbeaver/core-blocks';
import { PlaceholderComponent, StaticImage, useMapResource } from '@cloudbeaver/core-blocks';
import { useTranslate } from '@cloudbeaver/core-localization';
import { NetworkHandlerResource, SSH_TUNNEL_ID } from '../../../../NetworkHandlerResource';
import type { DatabaseConnection } from '../../../ConnectionsResource';
import type { IConnectionDetailsPlaceholderProps } from '../../ConnectionsAdministrationService';
import { CONNECTION_DETAILS_STYLES } from './ConnectionDetailsStyles';
interface Props {
context: DatabaseConnection;
}
export const SSH: React.FC<Props> = observer(function SSH({ context }) {
export const SSH: PlaceholderComponent<IConnectionDetailsPlaceholderProps> = observer(function SSH({ connection }) {
const translate = useTranslate();
const handler = useMapResource(NetworkHandlerResource, SSH_TUNNEL_ID);
const sshConfig = context.networkHandlersConfig.find(state => state.id === SSH_TUNNEL_ID);
const sshConfig = connection.networkHandlersConfig.find(state => state.id === SSH_TUNNEL_ID);
if (!sshConfig || !sshConfig.enabled) {
return null;
@@ -9,17 +9,13 @@
import { observer } from 'mobx-react-lite';
import styled from 'reshadow';
import { StaticImage } from '@cloudbeaver/core-blocks';
import { PlaceholderComponent, StaticImage } from '@cloudbeaver/core-blocks';
import type { DatabaseConnection } from '../../../ConnectionsResource';
import type { IConnectionDetailsPlaceholderProps } from '../../ConnectionsAdministrationService';
import { CONNECTION_DETAILS_STYLES } from './ConnectionDetailsStyles';
interface Props {
context: DatabaseConnection;
}
export const Template: React.FC<Props> = observer(function Template({ context }) {
if (!context.template) {
export const Template: PlaceholderComponent<IConnectionDetailsPlaceholderProps> = observer(function Template({ connection }) {
if (!connection.template) {
return null;
}
@@ -111,7 +111,7 @@ export const ConnectionForm = observer(function ConnectionForm({
<box as='div'>
<TabList style={style}>
<fill as="div" />
<Placeholder container={service.actionsContainer} context={{ data, form: formState.form, options }} />
<Placeholder container={service.actionsContainer} data={data} form={formState.form} options={options} />
<Button
type="button"
disabled={formState.form.disabled}
@@ -26,12 +26,12 @@ export class Executor<T = void> implements IExecutor<T> {
this.collection = new ExecutorHandlersCollection();
}
before<TNext extends T>(executor: IExecutor<TNext>, map?: (data: T) => TNext): this {
before<TNext>(executor: IExecutor<TNext>, map?: (data: T) => TNext): this {
this.collection.before(executor, map);
return this;
}
next<TNext extends T>(executor: IExecutor<TNext>, map?: (data: T) => TNext): this {
next<TNext>(executor: IExecutor<TNext>, map?: (data: T) => TNext): this {
this.collection.next(executor, map);
return this;
}
@@ -38,7 +38,7 @@ export class ExecutorHandlersCollection<T = unknown> implements IExecutorHandler
return this.links.get(link) || scoped?.getLinkHandlers(link, scoped.getLinkHandlers(link));
}
before<TNext extends T>(executor: IExecutor<TNext>, map?: (data: T) => TNext): this {
before<TNext>(executor: IExecutor<TNext>, map?: (data: T) => TNext): this {
this.chain.push({
executor,
map,
@@ -47,7 +47,7 @@ export class ExecutorHandlersCollection<T = unknown> implements IExecutorHandler
return this;
}
next<TNext extends T>(executor: IExecutor<TNext>, map?: (data: T) => TNext): this {
next<TNext>(executor: IExecutor<TNext>, map?: (data: T) => TNext): this {
this.chain.push({
executor,
map,
@@ -11,8 +11,8 @@ import type { IExecutorHandler } from './IExecutorHandler';
import type { IExecutorHandlersCollection } from './IExecutorHandlersCollection';
export interface IExecutor<T = void> {
before: <TNext extends T>(executor: IExecutor<TNext>, map?: (data: T) => TNext) => this;
next: <TNext extends T>(executor: IExecutor<TNext>, map?: (data: T) => TNext) => this;
before: <TNext>(executor: IExecutor<TNext>, map?: (data: T) => TNext) => this;
next: <TNext>(executor: IExecutor<TNext>, map?: (data: T) => TNext) => this;
execute: (
data: T,
context?: IExecutionContext<T>,
@@ -23,8 +23,8 @@ export interface IExecutorHandlersCollection<T = unknown> {
readonly postHandlers: Array<IExecutorHandler<T>>;
readonly chain: Array<IChainLink<T>>;
before: <TNext extends T>(executor: IExecutor<TNext>, map?: (data: T) => TNext) => this;
next: <TNext extends T>(executor: IExecutor<TNext>, map?: (data: T) => TNext) => this;
before: <TNext>(executor: IExecutor<TNext>, map?: (data: T) => TNext) => this;
next: <TNext>(executor: IExecutor<TNext>, map?: (data: T) => TNext) => this;
execute: (
data: T,
context: IExecutionContext<T>,
@@ -7,16 +7,15 @@
*/
import { observer } from 'mobx-react-lite';
import styled, { use, css } from 'reshadow';
import styled, { css } from 'reshadow';
import { AdministrationTools, ADMINISTRATION_TOOLS_STYLES } from '@cloudbeaver/core-administration';
import { FormBox, FormBoxElement, IconButton, Loader, SubmittingForm, useFocus } from '@cloudbeaver/core-blocks';
import { AdministrationItemContentComponent, AdministrationTools, ADMINISTRATION_TOOLS_STYLES } from '@cloudbeaver/core-administration';
import { BASE_CONTAINERS_STYLES, ColoredContainer, Container, Group, GroupItem, GroupTitle, IconButton, Loader, Placeholder, SubmittingForm, useFocus } from '@cloudbeaver/core-blocks';
import { useController, useService } from '@cloudbeaver/core-di';
import { useFormValidator } from '@cloudbeaver/core-executor';
import { useTranslate } from '@cloudbeaver/core-localization';
import { useStyles, composes } from '@cloudbeaver/core-theming';
import { useStyles } from '@cloudbeaver/core-theming';
import { ServerConfigurationAdminForm } from './Form/ServerConfigurationAdminForm';
import { ServerConfigurationConfigurationForm } from './Form/ServerConfigurationConfigurationForm';
import { ServerConfigurationInfoForm } from './Form/ServerConfigurationInfoForm';
import { ServerConfigurationNavigatorViewForm } from './Form/ServerConfigurationNavigatorViewForm';
@@ -24,106 +23,81 @@ import { ServerConfigurationSecurityForm } from './Form/ServerConfigurationSecur
import { ServerConfigurationPageController } from './ServerConfigurationPageController';
import { ServerConfigurationService } from './ServerConfigurationService';
const styles = composes(
css`
layout-grid-cell {
composes: theme-background-surface theme-text-on-surface theme-border-color-background from global;
const styles = css`
Loader {
height: 400px;
}
layout-grid-cell, message-box {
composes: theme-border-color-background from global;
SubmittingForm {
flex: 1;
display: flex;
overflow: auto;
flex-direction: column;
}
`,
css`
layout-grid {
width: 100%;
flex: 1;
}
layout-grid-inner {
min-height: 100%;
}
FormBox {
flex: 0;
}
layout-grid-cell {
position: relative;
display: flex;
flex-direction: column;
border: solid 1px;
}
p {
white-space: pre-wrap;
line-height: 2;
}
`;
message-box {
border-bottom: solid 1px;
}
message-box, SubmittingForm {
padding: 16px 24px;
}
SubmittingForm {
flex: 1;
display: flex;
overflow: auto;
flex-direction: column;
}
FormBox {
flex: 0;
}
p {
line-height: 2;
}
`
);
export const ServerConfigurationPage = observer(function ServerConfigurationPage() {
export const ServerConfigurationPage: AdministrationItemContentComponent = observer(function ServerConfigurationPage({
configurationWizard,
}) {
const translate = useTranslate();
const style = useStyles(styles, ADMINISTRATION_TOOLS_STYLES);
const style = useStyles(styles, ADMINISTRATION_TOOLS_STYLES, BASE_CONTAINERS_STYLES);
const [focusedRef] = useFocus<HTMLFormElement>({ focusFirstChild: true });
const service = useService(ServerConfigurationService);
const controller = useController(ServerConfigurationPageController);
useFormValidator(service.validationTask, focusedRef);
return styled(style)(
<layout-grid as="div">
<layout-grid-inner as="div">
<layout-grid-cell as='div' {...use({ span: 12 })}>
{!controller.editing ? (
<message-box as='div'>
<SubmittingForm ref={focusedRef} name='server_config' onSubmit={controller.save} onChange={controller.change}>
{controller.editing && (
<AdministrationTools>
<IconButton name="admin-save" viewBox="0 0 28 28" onClick={controller.save} />
<IconButton name="admin-cancel" viewBox="0 0 28 28" onClick={controller.reset} />
</AdministrationTools>
)}
<ColoredContainer wrap gap overflow parent>
{!controller.editing && (
<Group form>
<GroupItem>
<h3>{translate('administration_configuration_wizard_configuration_title')}</h3>
</GroupItem>
<GroupItem>
<p>{translate('administration_configuration_wizard_configuration_message')}</p>
</message-box>
) : (
<AdministrationTools>
<IconButton name="admin-save" viewBox="0 0 28 28" onClick={controller.save} />
<IconButton name="admin-cancel" viewBox="0 0 28 28" onClick={controller.reset} />
</AdministrationTools>
)}
<SubmittingForm ref={focusedRef} name='server_config' onSubmit={controller.save} onChange={controller.change}>
{service.loading ? (
<Loader />
) : (
</GroupItem>
</Group>
)}
<Container wrap gap>
<Loader state={service}>
{() => styled(style)(
<>
<FormBox>
<FormBoxElement>
<ServerConfigurationInfoForm serverConfig={controller.state.serverConfig} />
{!controller.editing && (
<ServerConfigurationAdminForm serverConfig={controller.state.serverConfig} />
)}
</FormBoxElement>
<FormBoxElement>
<ServerConfigurationConfigurationForm serverConfig={controller.state.serverConfig} />
<ServerConfigurationNavigatorViewForm configs={controller.state} />
</FormBoxElement>
<FormBoxElement>
<ServerConfigurationSecurityForm serverConfig={controller.state.serverConfig} />
</FormBoxElement>
</FormBox>
<ServerConfigurationInfoForm serverConfig={controller.state.serverConfig} />
<Group form gap medium>
<GroupTitle>{translate('administration_configuration_wizard_configuration_plugins')}</GroupTitle>
<ServerConfigurationConfigurationForm serverConfig={controller.state.serverConfig} />
<ServerConfigurationNavigatorViewForm configs={controller.state} />
<Placeholder container={service.pluginsContainer} />
</Group>
<ServerConfigurationSecurityForm serverConfig={controller.state.serverConfig} />
<Placeholder
container={service.configurationContainer}
configurationWizard={configurationWizard}
serverConfig={controller.state.serverConfig}
navigatorConfig={controller.state.navigatorConfig}
/>
</>
)}
</SubmittingForm>
</layout-grid-cell>
</layout-grid-inner>
</layout-grid>
</Loader>
</Container>
</ColoredContainer>
</SubmittingForm>
);
});
@@ -36,6 +36,11 @@ export class ServerConfigurationPageController {
};
change = (): void => {
this.serverConfigurationService.changed();
if (this.state.serverConfig.enabledAuthProviders.length === 0) {
this.state.serverConfig.authenticationEnabled = false;
}
if (!this.state.serverConfig.authenticationEnabled) {
this.state.serverConfig.anonymousAccessEnabled = true;
}
@@ -62,7 +67,7 @@ export class ServerConfigurationPageController {
if (result === DialogueStateResult.Rejected) {
return;
}
await this.serverConfigurationService.save();
await this.serverConfigurationService.saveConfiguration(true);
}
private async finishConfiguration(): Promise<void> {
@@ -18,9 +18,13 @@ import { UsersAdministrationNavigationService } from './UsersAdministrationNavig
import { UsersDrawerItem } from './UsersDrawerItem';
import { Origin } from './UsersTable/UserDetailsInfo/Origin';
export interface IUserDetailsInfoProps {
user: AdminUser;
}
@injectable()
export class UsersAdministrationService extends Bootstrap {
readonly userDetailsInfoPlaceholder = new PlaceholderContainer<AdminUser>();
readonly userDetailsInfoPlaceholder = new PlaceholderContainer<IUserDetailsInfoProps>();
constructor(
private administrationItemService: AdministrationItemService,
@@ -46,7 +46,7 @@ export const User: React.FC<Props> = observer(function User({ user, selectable }
<TableColumnValue expand>{user.userId}</TableColumnValue>
<TableColumnValue>{user.grantedRoles.join(', ')}</TableColumnValue>
<TableColumnValue flex>
<Placeholder container={usersAdministrationService.userDetailsInfoPlaceholder} context={user} />
<Placeholder container={usersAdministrationService.userDetailsInfoPlaceholder} user={user} />
</TableColumnValue>
</TableItem>
);
@@ -10,12 +10,10 @@ import { observer } from 'mobx-react-lite';
import styled from 'reshadow';
import { css } from 'reshadow';
import type { AdminUser } from '@cloudbeaver/core-authentication';
import { StaticImage } from '@cloudbeaver/core-blocks';
import { AUTH_PROVIDER_LOCAL_ID } from '@cloudbeaver/core-authentication';
import { PlaceholderComponent, StaticImage } from '@cloudbeaver/core-blocks';
interface Props {
context: AdminUser;
}
import type { IUserDetailsInfoProps } from '../../UsersAdministrationService';
const USER_DETAILS_STYLES = css`
StaticImage {
@@ -24,10 +22,10 @@ const USER_DETAILS_STYLES = css`
}
`;
export const Origin: React.FC<Props> = observer(function Origin({ context }) {
const isLocal = context.origin.type === 'local';
const icon = isLocal ? '/icons/local_connection.svg' : context.origin.icon;
const title = isLocal ? 'Local user' : context.origin.displayName;
export const Origin: PlaceholderComponent<IUserDetailsInfoProps> = observer(function Origin({ user }) {
const isLocal = user.origin.type === AUTH_PROVIDER_LOCAL_ID;
const icon = isLocal ? '/icons/local_connection.svg' : user.origin.icon;
const title = isLocal ? 'Local user' : user.origin.displayName;
return styled(USER_DETAILS_STYLES)(
<StaticImage icon={icon} title={title} />
@@ -35,7 +35,7 @@ export const TableHeader = observer(function TableHeader({
return styled(styles)(
<table-header as="div">
<Placeholder container={service.tableHeaderPlaceholder} context={model} />
<Placeholder container={service.tableHeaderPlaceholder} model={model} />
</table-header>
);
});
@@ -12,9 +12,13 @@ import { injectable, Bootstrap } from '@cloudbeaver/core-di';
import type { DataModelWrapper } from '../DataModelWrapper';
import { TableWhereFilter } from './TableWhereFilter';
export interface ITableHeaderPlaceholderProps {
model: DataModelWrapper;
}
@injectable()
export class TableHeaderService extends Bootstrap {
readonly tableHeaderPlaceholder = new PlaceholderContainer<DataModelWrapper>();
readonly tableHeaderPlaceholder = new PlaceholderContainer<ITableHeaderPlaceholderProps>();
register(): void {
this.tableHeaderPlaceholder.add(TableWhereFilter, 1);
@@ -11,10 +11,11 @@ import { useCallback, useState } from 'react';
import styled, { css } from 'reshadow';
import { InlineEditor } from '@cloudbeaver/core-app';
import type { PlaceholderComponent } from '@cloudbeaver/core-blocks';
import { useTranslate } from '@cloudbeaver/core-localization';
import { composes, useStyles } from '@cloudbeaver/core-theming';
import type { DataModelWrapper } from '../DataModelWrapper';
import type { ITableHeaderPlaceholderProps } from './TableHeaderService';
const styles = composes(
css`
@@ -30,37 +31,33 @@ const styles = composes(
`
);
interface Props {
context: DataModelWrapper;
}
export const TableWhereFilter = observer(function TableWhereFilter({
context,
}: Props) {
export const TableWhereFilter: PlaceholderComponent<ITableHeaderPlaceholderProps> = observer(function TableWhereFilter({
model,
}) {
const translate = useTranslate();
const [filterValue, setValue] = useState(() => context.source.options?.whereFilter || '');
const [filterValue, setValue] = useState(() => model.source.options?.whereFilter || '');
const handleApply = useCallback(() => {
if (context.isLoading()) {
if (model.isLoading()) {
return;
}
context.source.options!.whereFilter = filterValue;
context.refresh();
}, [context, filterValue]);
model.source.options!.whereFilter = filterValue;
model.refresh();
}, [model, filterValue]);
const resetFilter = useCallback(() => {
if (context.isLoading()) {
if (model.isLoading()) {
return;
}
const applyNeeded = context.source.options?.whereFilter === filterValue;
const applyNeeded = model.source.options?.whereFilter === filterValue;
setValue('');
if (applyNeeded) {
context.source.options!.whereFilter = '';
context.refresh();
model.source.options!.whereFilter = '';
model.refresh();
}
}, [context, filterValue]);
}, [model, filterValue]);
return styled(useStyles(styles))(
<InlineEditor
@@ -69,7 +66,7 @@ export const TableWhereFilter = observer(function TableWhereFilter({
placeholder={translate('table_header_sql_expression')}
controlsPosition='inside'
edited={!!filterValue}
disabled={context.isLoading() || context.source.results.length > 1}
disabled={model.isLoading() || model.source.results.length > 1}
simple
onSave={handleApply}
onUndo={resetFilter}