mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-10-12 23:37:00 +08:00
Merge remote-tracking branch 'origin/devel' into devel
This commit is contained in:
@@ -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
|
||||
|
||||
+53
-37
@@ -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;
|
||||
|
||||
+7
-4
@@ -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>> {
|
||||
|
||||
+5
-1
@@ -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,
|
||||
|
||||
+4
-1
@@ -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
-10
@@ -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;
|
||||
|
||||
+4
-8
@@ -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;
|
||||
|
||||
+4
-8
@@ -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>,
|
||||
|
||||
+62
-88
@@ -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>
|
||||
);
|
||||
});
|
||||
|
||||
+6
-1
@@ -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> {
|
||||
|
||||
+5
-1
@@ -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>
|
||||
);
|
||||
|
||||
+7
-9
@@ -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>
|
||||
);
|
||||
});
|
||||
|
||||
+5
-1
@@ -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);
|
||||
|
||||
+16
-19
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user