diff --git a/webapp/packages/core-administration/src/Administration/Administration.tsx b/webapp/packages/core-administration/src/Administration/Administration.tsx index 196f933e58..41debaffad 100644 --- a/webapp/packages/core-administration/src/Administration/Administration.tsx +++ b/webapp/packages/core-administration/src/Administration/Administration.tsx @@ -79,7 +79,7 @@ export const Administration: React.FC = observer(function Administration( const contentRef = useRef(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 }); diff --git a/webapp/packages/core-administration/src/Administration/DrawerItem.tsx b/webapp/packages/core-administration/src/Administration/DrawerItem.tsx index f837cc753d..7203ee69d6 100644 --- a/webapp/packages/core-administration/src/Administration/DrawerItem.tsx +++ b/webapp/packages/core-administration/src/Administration/DrawerItem.tsx @@ -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 ( { + 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 { - 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; + }; } diff --git a/webapp/packages/core-administration/src/AdministrationItem/IAdministrationItem.ts b/webapp/packages/core-administration/src/AdministrationItem/IAdministrationItem.ts index 6f0ec32ee2..61b370d814 100644 --- a/webapp/packages/core-administration/src/AdministrationItem/IAdministrationItem.ts +++ b/webapp/packages/core-administration/src/AdministrationItem/IAdministrationItem.ts @@ -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; onFinish?: () => Promise | boolean; - onConfigurationFinish?: () => Promise | void; + onConfigurationFinish?: () => Promise | 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; diff --git a/webapp/packages/core-administration/src/AdministrationScreen/ConfigurationWizard/ConfigurationWizardService.ts b/webapp/packages/core-administration/src/AdministrationScreen/ConfigurationWizard/ConfigurationWizardService.ts index 8ce6b5abd1..5d5ddb776b 100644 --- a/webapp/packages/core-administration/src/AdministrationScreen/ConfigurationWizard/ConfigurationWizardService.ts +++ b/webapp/packages/core-administration/src/AdministrationScreen/ConfigurationWizard/ConfigurationWizardService.ts @@ -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); } diff --git a/webapp/packages/core-authentication/src/AuthProvidersResource.ts b/webapp/packages/core-authentication/src/AuthProvidersResource.ts index a44ee0e1a4..a5184606c7 100644 --- a/webapp/packages/core-authentication/src/AuthProvidersResource.ts +++ b/webapp/packages/core-authentication/src/AuthProvidersResource.ts @@ -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; export class AuthProvidersResource extends CachedDataResource { 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 { const { providers } = await this.graphQLService.sdk.getAuthProviders(); return providers; diff --git a/webapp/packages/core-blocks/src/Placeholder/Placeholder.tsx b/webapp/packages/core-blocks/src/Placeholder/Placeholder.tsx index c7b4543653..c1db2d9cef 100644 --- a/webapp/packages/core-blocks/src/Placeholder/Placeholder.tsx +++ b/webapp/packages/core-blocks/src/Placeholder/Placeholder.tsx @@ -10,18 +10,17 @@ import { observer } from 'mobx-react-lite'; import type { PlaceholderContainer } from './PlaceholderContainer'; -type Props = { +type Props> = T & { container: PlaceholderContainer; -} & (T extends undefined ? { - context?: undefined; -} : { - context: T; -}); +}; -export const Placeholder = observer(function Placeholder({ container, context }: Props) { +export const Placeholder = observer(function Placeholder>({ + container, + ...rest +}: Props) { return ( <> - {container.get().map(({ id, component: Component }) => )} + {container.get().map(({ id, component: Component }) => )} ); }); diff --git a/webapp/packages/core-blocks/src/Placeholder/PlaceholderContainer.ts b/webapp/packages/core-blocks/src/Placeholder/PlaceholderContainer.ts index 599b07c965..60b29e1471 100644 --- a/webapp/packages/core-blocks/src/Placeholder/PlaceholderContainer.ts +++ b/webapp/packages/core-blocks/src/Placeholder/PlaceholderContainer.ts @@ -10,19 +10,15 @@ import { observable } from 'mobx'; import { uuid } from '@cloudbeaver/core-utils'; -export interface PlaceholderProps { - context: T; -} +export type PlaceholderComponent = Record> = React.FunctionComponent; -export type PlaceholderComponent = React.FunctionComponent>; - -export interface PlaceholderElement { +export interface PlaceholderElement = Record> { id: string; component: PlaceholderComponent; order?: number; } -export class PlaceholderContainer { +export class PlaceholderContainer = Record> { private placeholders = observable>([], { deep: false }); get(): Array> { diff --git a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsAdministrationService.ts b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsAdministrationService.ts index 4cd9e1f1f8..4389dc6c90 100644 --- a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsAdministrationService.ts +++ b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsAdministrationService.ts @@ -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(); + readonly connectionDetailsPlaceholder = new PlaceholderContainer(); constructor( private administrationItemService: AdministrationItemService, diff --git a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/Connection.tsx b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/Connection.tsx index 54d6c4aaa5..089fb892c6 100644 --- a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/Connection.tsx +++ b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/Connection.tsx @@ -61,7 +61,10 @@ export const Connection = observer(function Connection({ connection }: Props) { {connection.name} {connection.host}{connection.host && connection.port && `:${connection.port}`} - + ); diff --git a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionDetailsInfo/Origin.tsx b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionDetailsInfo/Origin.tsx index 863b9e1054..649b3dc74f 100644 --- a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionDetailsInfo/Origin.tsx +++ b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionDetailsInfo/Origin.tsx @@ -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 = observer(function Origin({ context }) { - const isLocal = context.origin.type === 'local'; - const icon = context.origin.icon; - const title = context.origin.displayName; +export const Origin: PlaceholderComponent = 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; diff --git a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionDetailsInfo/SSH.tsx b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionDetailsInfo/SSH.tsx index e3b1e322d9..702bcffcae 100644 --- a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionDetailsInfo/SSH.tsx +++ b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionDetailsInfo/SSH.tsx @@ -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 = observer(function SSH({ context }) { +export const SSH: PlaceholderComponent = 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; diff --git a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionDetailsInfo/Template.tsx b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionDetailsInfo/Template.tsx index b3d56ae706..d5bb9a11a1 100644 --- a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionDetailsInfo/Template.tsx +++ b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionDetailsInfo/Template.tsx @@ -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 = observer(function Template({ context }) { - if (!context.template) { +export const Template: PlaceholderComponent = observer(function Template({ connection }) { + if (!connection.template) { return null; } diff --git a/webapp/packages/core-connections/src/ConnectionForm/ConnectionForm.tsx b/webapp/packages/core-connections/src/ConnectionForm/ConnectionForm.tsx index bd9ad3173c..186852b28a 100644 --- a/webapp/packages/core-connections/src/ConnectionForm/ConnectionForm.tsx +++ b/webapp/packages/core-connections/src/ConnectionForm/ConnectionForm.tsx @@ -111,7 +111,7 @@ export const ConnectionForm = observer(function ConnectionForm({ - +