mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
CB-3214 suspense (#1527)
* CB-3214 feat: enable strict mode for development * CB-3214 refactor: loaders
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { splitStyles, Split, ResizerControls, Pane, useSplitUserState, useStyles, Loader } from '@cloudbeaver/core-blocks';
|
||||
import { splitStyles, Split, ResizerControls, Pane, useSplitUserState, useStyles, Loader, getComputed } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { LeftBarPanelService, SideBarPanel, SideBarPanelService } from '@cloudbeaver/core-ui';
|
||||
|
||||
@@ -38,11 +38,8 @@ export const Main = observer(function Main() {
|
||||
const splitMainState = useSplitUserState('main');
|
||||
const splitRightState = useSplitUserState('main-right');
|
||||
|
||||
const activeSideBars = sideBarPanelService.tabsContainer.getDisplayed();
|
||||
const activeLeftBars = leftBarPanelService.tabsContainer.getDisplayed();
|
||||
|
||||
const sideBarDisabled = activeSideBars.length === 0;
|
||||
const leftBarDisabled = activeLeftBars.length === 0;
|
||||
const sideBarDisabled = getComputed(() => sideBarPanelService.tabsContainer.getDisplayed().length === 0);
|
||||
const leftBarDisabled = getComputed(() => leftBarPanelService.tabsContainer.getDisplayed().length === 0);
|
||||
|
||||
return styled(styles)(
|
||||
<Loader suspense>
|
||||
|
||||
@@ -242,7 +242,10 @@ export const Loader = observer<Props>(function Loader({
|
||||
return renderWrappedChildren();
|
||||
}
|
||||
|
||||
if (exception && !loading && !hideException) {
|
||||
if (exception && !loading) {
|
||||
if (hideException) {
|
||||
return null;
|
||||
}
|
||||
return styled(style)(
|
||||
<ExceptionMessage
|
||||
exception={exception}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
import { Loader } from '../Loader/Loader';
|
||||
import type { PlaceholderContainer, PlaceholderElement } from './PlaceholderContainer';
|
||||
|
||||
type Props<T extends Record<string, any>> = T & {
|
||||
@@ -35,8 +34,8 @@ export const Placeholder = observer(function Placeholder<T extends Record<string
|
||||
}
|
||||
|
||||
return (
|
||||
<Loader suspense>
|
||||
<>
|
||||
{elements.map(({ id, component: Component }) => <Component key={id} {...(rest as any as T)} />)}
|
||||
</Loader>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -183,6 +183,20 @@ export const PropertyItem = observer<Props>(function PropertyItem({
|
||||
>
|
||||
{propertyValue}
|
||||
</ShadowInput>
|
||||
{edited && !isDeletable && (
|
||||
<property-remove title={translate('core_blocks_properties_table_item_reset')}>
|
||||
<button type="button" onClick={handleRevert}>
|
||||
<IconOrImage icon="/icons/data_revert_all_sm.svg" viewBox="0 0 16 16" />
|
||||
</button>
|
||||
</property-remove>
|
||||
)}
|
||||
{isDeletable && (
|
||||
<property-remove title={translate('core_blocks_properties_table_item_remove')}>
|
||||
<button type="button" onClick={handleRemove}>
|
||||
<Icon name="reject" viewBox="0 0 11 11" />
|
||||
</button>
|
||||
</property-remove>
|
||||
)}
|
||||
{(!readOnly && property.validValues && property.validValues.length > 0) && (
|
||||
<property-select>
|
||||
<PropertyValueSelector
|
||||
@@ -197,20 +211,6 @@ export const PropertyItem = observer<Props>(function PropertyItem({
|
||||
</property-select>
|
||||
)}
|
||||
</property-value>
|
||||
{edited && !isDeletable && (
|
||||
<property-remove title={translate('core_blocks_properties_table_item_reset')}>
|
||||
<button type="button" onClick={handleRevert}>
|
||||
<IconOrImage icon="/icons/data_revert_all_sm.svg" viewBox="0 0 16 16" />
|
||||
</button>
|
||||
</property-remove>
|
||||
)}
|
||||
{isDeletable && (
|
||||
<property-remove title={translate('core_blocks_properties_table_item_remove')}>
|
||||
<button type="button" onClick={handleRemove}>
|
||||
<Icon name="reject" viewBox="0 0 11 11" />
|
||||
</button>
|
||||
</property-remove>
|
||||
)}
|
||||
</property-item>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { computed, observable, toJS, untracked, when } from 'mobx';
|
||||
import { action, computed, observable, reaction, toJS, untracked, when } from 'mobx';
|
||||
import { useEffect, useContext, useState } from 'react';
|
||||
|
||||
import { IServiceConstructor, useService } from '@cloudbeaver/core-di';
|
||||
@@ -271,6 +271,7 @@ export function useResource<
|
||||
}
|
||||
},
|
||||
}), {
|
||||
load: action,
|
||||
resourceException: computed,
|
||||
exception: observable.ref,
|
||||
loadingPromise: observable.ref,
|
||||
@@ -323,9 +324,9 @@ export function useResource<
|
||||
}
|
||||
|
||||
// React Suspense block
|
||||
if (refObj.loadingPromise) {
|
||||
throw refObj.loadingPromise;
|
||||
}
|
||||
// if (refObj.loadingPromise) {
|
||||
// throw refObj.loadingPromise;
|
||||
// }
|
||||
|
||||
if (this.loading) {
|
||||
throw this.resource.waitLoad();
|
||||
@@ -395,30 +396,24 @@ export function useResource<
|
||||
}, { preloaded });
|
||||
|
||||
useEffect(() => {
|
||||
let prevData: any;
|
||||
const disposeDataUpdate = when(
|
||||
() => {
|
||||
try {
|
||||
if (result.isError()) {
|
||||
return false;
|
||||
}
|
||||
result.data;
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
const disposeDataUpdate = reaction(
|
||||
() => result.data,
|
||||
(data, prev) => {
|
||||
actions?.onData?.(data as any, resource, prev as any);
|
||||
},
|
||||
() => {
|
||||
const newData = result.data;
|
||||
refObj.actions?.onData?.(newData, resource, prevData);
|
||||
prevData = newData;
|
||||
{
|
||||
onError: () => {},
|
||||
fireImmediately: true,
|
||||
}
|
||||
);
|
||||
const disposeErrorUpdate = when(
|
||||
() => result.isError(),
|
||||
() => {
|
||||
const disposeErrorUpdate = reaction(
|
||||
() => result.exception,
|
||||
exception => {
|
||||
if (!result.isError()) {
|
||||
return;
|
||||
}
|
||||
if (propertiesRef.errorContext) {
|
||||
const errors = Array.isArray(result.exception) ? result.exception : [result.exception];
|
||||
const errors = Array.isArray(exception) ? exception : [exception];
|
||||
|
||||
for (const error of errors) {
|
||||
if (error) {
|
||||
@@ -426,7 +421,10 @@ export function useResource<
|
||||
}
|
||||
}
|
||||
}
|
||||
refObj.actions?.onError?.(result.exception);
|
||||
actions?.onError?.(exception);
|
||||
},
|
||||
{
|
||||
fireImmediately: true,
|
||||
}
|
||||
);
|
||||
return () => {
|
||||
|
||||
@@ -8,6 +8,6 @@
|
||||
|
||||
import { computed } from 'mobx';
|
||||
|
||||
export function getComputed<T>(action: () => T): T {
|
||||
return computed(action).get();
|
||||
export function getComputed<T>(action: () => T, equals?: (a: T, b: T) => boolean): T {
|
||||
return computed(action, { equals }).get();
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { StrictMode, Suspense } from 'react';
|
||||
import { Suspense } from 'react';
|
||||
import { createRoot, Root } from 'react-dom/client';
|
||||
import styled from 'reshadow';
|
||||
|
||||
@@ -52,7 +52,6 @@ export function renderLayout(serviceInjector: IServiceInjector): IRender {
|
||||
height: 100vh;
|
||||
}
|
||||
`(
|
||||
// <StrictMode>
|
||||
<AppContext app={serviceInjector}>
|
||||
<ErrorBoundary root>
|
||||
<Suspense fallback={<Loader />}>
|
||||
@@ -60,7 +59,6 @@ export function renderLayout(serviceInjector: IServiceInjector): IRender {
|
||||
</Suspense>
|
||||
</ErrorBoundary>
|
||||
</AppContext>
|
||||
// </StrictMode>
|
||||
));
|
||||
},
|
||||
renderError(exception?: any) {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { createContext } from 'react';
|
||||
import { createContext, StrictMode } from 'react';
|
||||
|
||||
import type { IServiceInjector } from './IApp';
|
||||
import { useAppLoadingScreen } from './useAppLoadingScreen';
|
||||
@@ -19,5 +19,9 @@ interface Props {
|
||||
|
||||
export const AppContext: React.FC<React.PropsWithChildren<Props>> = function AppContext({ app, children }) {
|
||||
useAppLoadingScreen();
|
||||
return <appContext.Provider value={app}>{children}</appContext.Provider>;
|
||||
return (
|
||||
//<StrictMode> // problems with TabState when empty -> displayed state switch
|
||||
<appContext.Provider value={app}>{children}</appContext.Provider>
|
||||
//</StrictMode>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -13,7 +13,7 @@ import { useTabState } from 'reakit/Tab';
|
||||
|
||||
import { useExecutor, useObjectRef, useObservableRef } from '@cloudbeaver/core-blocks';
|
||||
import { Executor, ExecutorInterrupter } from '@cloudbeaver/core-executor';
|
||||
import { MetadataMap, MetadataValueGetter } from '@cloudbeaver/core-utils';
|
||||
import { isNull, isUndefined, MetadataMap, MetadataValueGetter } from '@cloudbeaver/core-utils';
|
||||
|
||||
import type { ITabData, ITabsContainer } from './TabsContainer/ITabsContainer';
|
||||
import { TabsContext, ITabsContext } from './TabsContext';
|
||||
@@ -96,12 +96,21 @@ export const TabsState = observer(function TabsState<T = Record<string, any>>({
|
||||
tabList,
|
||||
});
|
||||
|
||||
if (currentTabId !== undefined && currentTabId !== null) {
|
||||
if (
|
||||
!isNull(currentTabId)
|
||||
&& !isUndefined(currentTabId)
|
||||
) {
|
||||
state.selectedId = currentTabId;
|
||||
dynamic.selectedId = currentTabId;
|
||||
}
|
||||
|
||||
if (displayed.length > 0 && dynamic.selectedId && selectedId) {
|
||||
if (
|
||||
displayed.length > 0
|
||||
&& !isNull(dynamic.selectedId)
|
||||
&& !isUndefined(dynamic.selectedId)
|
||||
&& !isNull(selectedId)
|
||||
&& !isUndefined(selectedId)
|
||||
) {
|
||||
const tabExists = displayed.includes(dynamic.selectedId);
|
||||
|
||||
if (!tabExists) {
|
||||
@@ -134,7 +143,10 @@ export const TabsState = observer(function TabsState<T = Record<string, any>>({
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (currentTabId !== undefined && currentTabId !== null) {
|
||||
if (
|
||||
!isNull(currentTabId)
|
||||
&& !isUndefined(currentTabId)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -145,13 +157,16 @@ export const TabsState = observer(function TabsState<T = Record<string, any>>({
|
||||
}, [currentTabId, state.selectedId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (state.selectedId) {
|
||||
if (
|
||||
!isNull(state.selectedId)
|
||||
&& !isUndefined(state.selectedId)
|
||||
) {
|
||||
openExecutor.execute({
|
||||
tabId: state.selectedId!,
|
||||
tabId: state.selectedId,
|
||||
props,
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
}, [!isNull(state.selectedId) && !isUndefined(state.selectedId)]);
|
||||
|
||||
const value = useObservableRef<ITabsContext<T>>(() => ({
|
||||
getTabInfo(tabId: string) {
|
||||
|
||||
+24
-25
@@ -11,7 +11,7 @@ import { useContext } from 'react';
|
||||
import styled from 'reshadow';
|
||||
|
||||
import { AdministrationSettingsService } from '@cloudbeaver/core-administration';
|
||||
import { BASE_CONTAINERS_STYLES, FormContext, GroupTitle, Loader, PlaceholderComponent, Switch, useResource, useTranslate, useStyles } from '@cloudbeaver/core-blocks';
|
||||
import { BASE_CONTAINERS_STYLES, FormContext, GroupTitle, PlaceholderComponent, Switch, useResource, useTranslate, useStyles } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { FeaturesResource } from '@cloudbeaver/core-root';
|
||||
import type { IConfigurationPlaceholderProps } from '@cloudbeaver/plugin-administration';
|
||||
@@ -20,16 +20,21 @@ export const ServerConfigurationFeaturesForm: PlaceholderComponent<IConfiguratio
|
||||
state: { serverConfig },
|
||||
configurationWizard,
|
||||
}) {
|
||||
const administrationSettingsService = useService(AdministrationSettingsService);
|
||||
const features = useResource(ServerConfigurationFeaturesForm, FeaturesResource, undefined);
|
||||
const translate = useTranslate();
|
||||
const styles = useStyles(BASE_CONTAINERS_STYLES);
|
||||
const formContext = useContext(FormContext);
|
||||
|
||||
if (formContext === null) {
|
||||
throw new Error('Form state should be provided');
|
||||
}
|
||||
|
||||
const administrationSettingsService = useService(AdministrationSettingsService);
|
||||
const features = useResource(
|
||||
ServerConfigurationFeaturesForm,
|
||||
FeaturesResource,
|
||||
configurationWizard ? null : undefined
|
||||
);
|
||||
const translate = useTranslate();
|
||||
const styles = useStyles(BASE_CONTAINERS_STYLES);
|
||||
|
||||
if (features.data.length === 0 || configurationWizard) {
|
||||
return null;
|
||||
}
|
||||
@@ -37,26 +42,20 @@ export const ServerConfigurationFeaturesForm: PlaceholderComponent<IConfiguratio
|
||||
return styled(styles)(
|
||||
<>
|
||||
<GroupTitle>{translate('administration_configuration_wizard_configuration_services_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>
|
||||
{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>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
+23
-17
@@ -36,6 +36,10 @@ const styles = css`
|
||||
white-space: pre-wrap;
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
Loader {
|
||||
height: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
export const ServerConfigurationPage: AdministrationItemContentComponent = observer(function ServerConfigurationPage({
|
||||
@@ -118,27 +122,29 @@ export const ServerConfigurationPage: AdministrationItemContentComponent = obser
|
||||
)}
|
||||
<Loader state={service}>
|
||||
{() => styled(style)(
|
||||
<Container wrap gap grid medium>
|
||||
<ServerConfigurationInfoForm state={service.state} />
|
||||
<Group form gap>
|
||||
<GroupTitle>{translate('administration_configuration_wizard_configuration_plugins')}</GroupTitle>
|
||||
<ServerConfigurationConfigurationForm serverConfig={service.state.serverConfig} />
|
||||
<ServerConfigurationNavigatorViewForm configs={service.state} />
|
||||
<ServerConfigurationFeaturesForm state={service.state} configurationWizard={configurationWizard} />
|
||||
<Loader suspense>
|
||||
<Container wrap gap grid medium>
|
||||
<ServerConfigurationInfoForm state={service.state} />
|
||||
<Group form gap>
|
||||
<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}
|
||||
configurationWizard={configurationWizard}
|
||||
state={service.state}
|
||||
/>
|
||||
</Group>
|
||||
<Placeholder
|
||||
container={service.pluginsContainer}
|
||||
container={service.configurationContainer}
|
||||
configurationWizard={configurationWizard}
|
||||
state={service.state}
|
||||
/>
|
||||
</Group>
|
||||
<Placeholder
|
||||
container={service.configurationContainer}
|
||||
configurationWizard={configurationWizard}
|
||||
state={service.state}
|
||||
/>
|
||||
<ServerConfigurationSecurityForm serverConfig={service.state.serverConfig} />
|
||||
<ServerConfigurationDriversForm serverConfig={service.state.serverConfig} />
|
||||
</Container>
|
||||
<ServerConfigurationSecurityForm serverConfig={service.state.serverConfig} />
|
||||
<ServerConfigurationDriversForm serverConfig={service.state.serverConfig} />
|
||||
</Container>
|
||||
</Loader>
|
||||
)}
|
||||
</Loader>
|
||||
</ColoredContainer>
|
||||
|
||||
+4
-2
@@ -10,7 +10,7 @@ import { observer } from 'mobx-react-lite';
|
||||
import { useEffect } from 'react';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { Placeholder, useObjectRef, useExecutor, BASE_CONTAINERS_STYLES, IconOrImage, useTranslate, useStyles } from '@cloudbeaver/core-blocks';
|
||||
import { Placeholder, useObjectRef, useExecutor, BASE_CONTAINERS_STYLES, IconOrImage, useTranslate, useStyles, Loader } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import type { AdminAuthProviderConfiguration } from '@cloudbeaver/core-sdk';
|
||||
import { TabsState, TabList, UNDERLINE_TAB_STYLES, TabPanelList, BASE_TAB_STYLES } from '@cloudbeaver/core-ui';
|
||||
@@ -149,7 +149,9 @@ export const AuthConfigurationForm = observer<Props>(function AuthConfigurationF
|
||||
<TabList style={style} disabled={false} />
|
||||
</configuration-top-bar-tabs>
|
||||
<configuration-top-bar-actions>
|
||||
<Placeholder container={service.actionsContainer} state={state} onCancel={onCancel} />
|
||||
<Loader suspense inline hideMessage hideException>
|
||||
<Placeholder container={service.actionsContainer} state={state} onCancel={onCancel} />
|
||||
</Loader>
|
||||
</configuration-top-bar-actions>
|
||||
</configuration-top-bar>
|
||||
<content-box>
|
||||
|
||||
+4
-2
@@ -12,7 +12,7 @@ import styled, { css, use } from 'reshadow';
|
||||
import { AuthProvidersResource } from '@cloudbeaver/core-authentication';
|
||||
import {
|
||||
TableItem, TableColumnValue, TableItemSelect, TableItemExpand,
|
||||
Placeholder, StaticImage, useResource, FieldCheckbox
|
||||
Placeholder, StaticImage, useResource, FieldCheckbox, Loader
|
||||
} from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import type { AdminAuthProviderConfiguration } from '@cloudbeaver/core-sdk';
|
||||
@@ -65,7 +65,9 @@ export const AuthConfiguration = observer<Props>(function AuthConfiguration({ co
|
||||
<FieldCheckbox checked={configuration.disabled} disabled />
|
||||
</TableColumnValue>
|
||||
<TableColumnValue flex {...use({ gap: true })}>
|
||||
<Placeholder container={service.configurationDetailsPlaceholder} configuration={configuration} />
|
||||
<Loader suspense small inline hideMessage>
|
||||
<Placeholder container={service.configurationDetailsPlaceholder} configuration={configuration} />
|
||||
</Loader>
|
||||
</TableColumnValue>
|
||||
</TableItem>
|
||||
);
|
||||
|
||||
+42
-47
@@ -10,7 +10,7 @@ import { observer } from 'mobx-react-lite';
|
||||
import React, { useContext } from 'react';
|
||||
import styled from 'reshadow';
|
||||
|
||||
import { AuthProviderService, AuthProvidersResource, AuthSettingsService, AUTH_PROVIDER_LOCAL_ID } from '@cloudbeaver/core-authentication';
|
||||
import { AuthProvider, AuthProviderService, AuthProvidersResource, AuthSettingsService, AUTH_PROVIDER_LOCAL_ID } from '@cloudbeaver/core-authentication';
|
||||
import { BASE_CONTAINERS_STYLES, FormContext, Group, GroupTitle, Loader, PlaceholderComponent, Switch, useExecutor, useResource, useTranslate, useStyles } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { CachedMapAllKey } from '@cloudbeaver/core-sdk';
|
||||
@@ -33,8 +33,8 @@ export const AuthenticationProviders: PlaceholderComponent<IConfigurationPlaceho
|
||||
throw new Error('Form state should be provided');
|
||||
}
|
||||
|
||||
const providerList = providers.resource.values.filter(provider => {
|
||||
if (configurationWizard && provider.configurable) {
|
||||
const providerList = providers.data.filter<AuthProvider>((provider): provider is AuthProvider => {
|
||||
if (configurationWizard && provider?.configurable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -89,56 +89,51 @@ export const AuthenticationProviders: PlaceholderComponent<IConfigurationPlaceho
|
||||
{translate('administration_configuration_wizard_configuration_anonymous_access')}
|
||||
</Switch>
|
||||
) : null}
|
||||
<Loader state={providers} inline>
|
||||
{() => styled(styles)(
|
||||
<>
|
||||
{providerList.map(provider => {
|
||||
const links = authProviderService.getServiceDescriptionLinks(provider);
|
||||
let disabled = provider.requiredFeatures.some(feat => !serverConfig.enabledFeatures?.includes(feat));
|
||||
const tooltip = disabled ? `Following services need to be enabled: "${provider.requiredFeatures.join(', ')}"` : '';
|
||||
|
||||
if (
|
||||
!localProvider
|
||||
{providerList.map(provider => {
|
||||
const links = authProviderService.getServiceDescriptionLinks(provider);
|
||||
let disabled = provider.requiredFeatures.some(feat => !serverConfig.enabledFeatures?.includes(feat));
|
||||
const tooltip = disabled ? `Following services need to be enabled: "${provider.requiredFeatures.join(', ')}"` : '';
|
||||
|
||||
if (
|
||||
!localProvider
|
||||
&& primaryProvider?.id === provider.id
|
||||
&& serverConfig.enabledAuthProviders?.length === 1
|
||||
&& serverConfig.enabledAuthProviders.includes(provider.id)
|
||||
) {
|
||||
disabled = true;
|
||||
}
|
||||
) {
|
||||
disabled = true;
|
||||
}
|
||||
|
||||
if (provider.private || (configurationWizard && (disabled || provider.id !== AUTH_PROVIDER_LOCAL_ID))) {
|
||||
return null;
|
||||
}
|
||||
if (provider.private || (configurationWizard && (disabled || provider.id !== AUTH_PROVIDER_LOCAL_ID))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Switch
|
||||
key={provider.id}
|
||||
id={`authProvider_${provider.id}`}
|
||||
title={tooltip}
|
||||
value={provider.id}
|
||||
name="enabledAuthProviders"
|
||||
state={serverConfig}
|
||||
description={(
|
||||
<>
|
||||
{provider.description}
|
||||
{links.map(link => {
|
||||
const Description = link.description();
|
||||
return <Description key={link.id} configurationWizard={configurationWizard} />;
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
mod={['primary']}
|
||||
disabled={disabled}
|
||||
small
|
||||
autoHide
|
||||
>
|
||||
{provider.label}
|
||||
</Switch>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
</Loader>
|
||||
return (
|
||||
<Switch
|
||||
key={provider.id}
|
||||
id={`authProvider_${provider.id}`}
|
||||
title={tooltip}
|
||||
value={provider.id}
|
||||
name="enabledAuthProviders"
|
||||
state={serverConfig}
|
||||
description={(
|
||||
<>
|
||||
{provider.description}
|
||||
{links.map(link => {
|
||||
const Description = link.description();
|
||||
return <Description key={link.id} configurationWizard={configurationWizard} />;
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
mod={['primary']}
|
||||
disabled={disabled}
|
||||
small
|
||||
autoHide
|
||||
>
|
||||
{provider.label}
|
||||
</Switch>
|
||||
);
|
||||
})}
|
||||
</Group>
|
||||
{configurationWizard && localProvider && (
|
||||
<ServerConfigurationAdminForm serverConfig={serverConfig} />
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 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 styled from 'reshadow';
|
||||
|
||||
import { UserMetaParametersResource } from '@cloudbeaver/core-authentication';
|
||||
import { BASE_CONTAINERS_STYLES, Container, ObjectPropertyInfoForm, useResource, useStyles } from '@cloudbeaver/core-blocks';
|
||||
import type { UserInfo } from '@cloudbeaver/core-sdk';
|
||||
import type { ComponentStyle } from '@cloudbeaver/core-theming';
|
||||
|
||||
interface Props {
|
||||
user: UserInfo;
|
||||
className?: string;
|
||||
style?: ComponentStyle;
|
||||
}
|
||||
|
||||
export const MetaParameterInfoForm = observer<Props>(function MetaParameterInfoForm({
|
||||
user,
|
||||
className,
|
||||
style,
|
||||
}) {
|
||||
const userMetaParameters = useResource(MetaParameterInfoForm, UserMetaParametersResource, undefined);
|
||||
const styles = useStyles(style, BASE_CONTAINERS_STYLES);
|
||||
|
||||
if (!userMetaParameters.data.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return styled(styles)(
|
||||
<Container className={className} wrap gap>
|
||||
<ObjectPropertyInfoForm
|
||||
state={user.metaParameters}
|
||||
properties={userMetaParameters.data}
|
||||
category={null}
|
||||
disabled
|
||||
readOnly
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
});
|
||||
+5
-62
@@ -9,14 +9,12 @@
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import styled from 'reshadow';
|
||||
|
||||
import { UserMetaParametersResource } from '@cloudbeaver/core-authentication';
|
||||
import { TabPanel } from '@cloudbeaver/core-ui';
|
||||
import { BASE_CONTAINERS_STYLES, ColoredContainer, Container, Group, InputField, Loader, ObjectPropertyInfoForm, useResource, useTranslate, useStyles } from '@cloudbeaver/core-blocks';
|
||||
|
||||
import { useStyles } from '@cloudbeaver/core-blocks';
|
||||
import type { UserInfo } from '@cloudbeaver/core-sdk';
|
||||
import type { ComponentStyle } from '@cloudbeaver/core-theming';
|
||||
import { TabPanel } from '@cloudbeaver/core-ui';
|
||||
|
||||
import { AuthProvidersList } from '../AuthProviders/ConfigurationsList';
|
||||
import { MetaParametersForm } from './MetaParametersForm';
|
||||
|
||||
interface Props {
|
||||
user: UserInfo;
|
||||
@@ -29,66 +27,11 @@ export const MetaParameterInfoPanel = observer<Props>(function MetaParameterInfo
|
||||
className,
|
||||
style,
|
||||
}) {
|
||||
const userMetaParameters = useResource(MetaParameterInfoPanel, UserMetaParametersResource, undefined);
|
||||
const styles = useStyles(style, BASE_CONTAINERS_STYLES);
|
||||
const translate = useTranslate();
|
||||
const styles = useStyles(style);
|
||||
|
||||
return styled(styles)(
|
||||
<TabPanel tabId='info' className={className}>
|
||||
<ColoredContainer wrap overflow parent gap>
|
||||
<Container medium gap>
|
||||
<Group form gap>
|
||||
<Container wrap gap>
|
||||
<InputField
|
||||
type="text"
|
||||
name="userId"
|
||||
minLength={1}
|
||||
state={user}
|
||||
mod='surface'
|
||||
disabled
|
||||
readOnly
|
||||
required
|
||||
tiny
|
||||
fill
|
||||
>
|
||||
{translate('plugin_user_profile_info_id')}
|
||||
</InputField>
|
||||
<InputField
|
||||
type="text"
|
||||
name="displayName"
|
||||
minLength={1}
|
||||
state={user}
|
||||
mod='surface'
|
||||
disabled
|
||||
readOnly
|
||||
required
|
||||
tiny
|
||||
fill
|
||||
>
|
||||
{translate('plugin_user_profile_info_displayName')}
|
||||
</InputField>
|
||||
</Container>
|
||||
|
||||
<Loader state={userMetaParameters} inline>
|
||||
{() => userMetaParameters.data.length > 0 && styled(styles)(
|
||||
<Container wrap gap>
|
||||
<ObjectPropertyInfoForm
|
||||
state={user.metaParameters}
|
||||
properties={userMetaParameters.data}
|
||||
category={null}
|
||||
disabled
|
||||
readOnly
|
||||
/>
|
||||
</Container>
|
||||
)}
|
||||
</Loader>
|
||||
</Group>
|
||||
<Group box medium overflow>
|
||||
<AuthProvidersList user={user} providers={user.linkedAuthProviders} />
|
||||
</Group>
|
||||
</Container>
|
||||
|
||||
</ColoredContainer>
|
||||
<MetaParametersForm user={user} />
|
||||
</TabPanel>
|
||||
);
|
||||
});
|
||||
|
||||
+1
-1
@@ -9,9 +9,9 @@
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import styled from 'reshadow';
|
||||
|
||||
import type { ComponentStyle } from '@cloudbeaver/core-theming';
|
||||
import { Tab } from '@cloudbeaver/core-ui';
|
||||
|
||||
import type { ComponentStyle } from '@cloudbeaver/core-theming';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 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 styled from 'reshadow';
|
||||
|
||||
import { BASE_CONTAINERS_STYLES, ColoredContainer, Container, Group, InputField, useTranslate, useStyles } from '@cloudbeaver/core-blocks';
|
||||
import type { UserInfo } from '@cloudbeaver/core-sdk';
|
||||
import type { ComponentStyle } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { AuthProvidersList } from '../AuthProviders/ConfigurationsList';
|
||||
import { MetaParameterInfoForm } from './MetaParameterInfoForm';
|
||||
|
||||
interface Props {
|
||||
user: UserInfo;
|
||||
className?: string;
|
||||
style?: ComponentStyle;
|
||||
}
|
||||
|
||||
export const MetaParametersForm = observer<Props>(function MetaParametersForm({
|
||||
user,
|
||||
className,
|
||||
style,
|
||||
}) {
|
||||
const styles = useStyles(style, BASE_CONTAINERS_STYLES);
|
||||
const translate = useTranslate();
|
||||
|
||||
return styled(styles)(
|
||||
<ColoredContainer wrap overflow parent gap>
|
||||
<Container medium gap>
|
||||
<Group form gap>
|
||||
<Container wrap gap>
|
||||
<InputField
|
||||
type="text"
|
||||
name="userId"
|
||||
minLength={1}
|
||||
state={user}
|
||||
mod='surface'
|
||||
disabled
|
||||
readOnly
|
||||
required
|
||||
tiny
|
||||
fill
|
||||
>
|
||||
{translate('plugin_user_profile_info_id')}
|
||||
</InputField>
|
||||
<InputField
|
||||
type="text"
|
||||
name="displayName"
|
||||
minLength={1}
|
||||
state={user}
|
||||
mod='surface'
|
||||
disabled
|
||||
readOnly
|
||||
required
|
||||
tiny
|
||||
fill
|
||||
>
|
||||
{translate('plugin_user_profile_info_displayName')}
|
||||
</InputField>
|
||||
</Container>
|
||||
<MetaParameterInfoForm user={user} />
|
||||
</Group>
|
||||
<Group box medium overflow>
|
||||
<AuthProvidersList user={user} providers={user.linkedAuthProviders} />
|
||||
</Group>
|
||||
</Container>
|
||||
|
||||
</ColoredContainer>
|
||||
);
|
||||
});
|
||||
+4
-8
@@ -12,7 +12,7 @@ import styled, { css } from 'reshadow';
|
||||
|
||||
import {
|
||||
BASE_CONTAINERS_STYLES, ColoredContainer, Container, getComputed, Group,
|
||||
InfoItem, Loader, TextPlaceholder, useResource, useStyles, useTranslate
|
||||
InfoItem, Loader, TextPlaceholder, useAutoLoad, useResource, useStyles, useTranslate
|
||||
} from '@cloudbeaver/core-blocks';
|
||||
import { Connection, ConnectionInfoProjectKey, ConnectionInfoResource, DBDriverResource, isCloudConnection } from '@cloudbeaver/core-connections';
|
||||
import type { TLocalizationToken } from '@cloudbeaver/core-localization';
|
||||
@@ -60,7 +60,7 @@ export const GrantedConnections: TabContainerPanelComponent<ITeamFormProps> = ob
|
||||
.map(project => project.id)
|
||||
);
|
||||
|
||||
const dbDriverResource = useResource(
|
||||
useResource(
|
||||
GrantedConnections,
|
||||
DBDriverResource,
|
||||
CachedMapAllKey,
|
||||
@@ -80,11 +80,7 @@ export const GrantedConnections: TabContainerPanelComponent<ITeamFormProps> = ob
|
||||
.filter(connection => state.state.grantedSubjects.includes(connection.id))
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (selected && !loaded) {
|
||||
state.load();
|
||||
}
|
||||
});
|
||||
useAutoLoad(state, selected && !loaded);
|
||||
|
||||
if (!selected) {
|
||||
return null;
|
||||
@@ -103,7 +99,7 @@ export const GrantedConnections: TabContainerPanelComponent<ITeamFormProps> = ob
|
||||
}
|
||||
|
||||
return styled(style)(
|
||||
<Loader state={[connectionsLoader, dbDriverResource, projects, state.state]}>
|
||||
<Loader state={[state.state]}>
|
||||
{() => styled(style)(
|
||||
<ColoredContainer parent gap vertical>
|
||||
{!connections.length ? (
|
||||
|
||||
+11
-2
@@ -13,12 +13,12 @@ import { useObservableRef } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { useTabState } from '@cloudbeaver/core-ui';
|
||||
import { isArraysEqual } from '@cloudbeaver/core-utils';
|
||||
import { ILoadableState, isArraysEqual } from '@cloudbeaver/core-utils';
|
||||
|
||||
import type { TeamFormMode } from '../ITeamFormProps';
|
||||
import type { IGrantedConnectionsTabState } from './IGrantedConnectionsTabState';
|
||||
|
||||
interface State {
|
||||
interface State extends ILoadableState {
|
||||
state: IGrantedConnectionsTabState;
|
||||
changed: boolean;
|
||||
edit: () => void;
|
||||
@@ -36,6 +36,15 @@ export function useGrantedConnections(team: TeamInfo, mode: TeamFormMode): Reado
|
||||
get changed() {
|
||||
return !isArraysEqual(this.state.initialGrantedSubjects, this.state.grantedSubjects);
|
||||
},
|
||||
isLoading() {
|
||||
return this.state.loading;
|
||||
},
|
||||
isLoaded() {
|
||||
return this.state.loaded;
|
||||
},
|
||||
isError() {
|
||||
return false;
|
||||
},
|
||||
edit() {
|
||||
this.state.editing = !this.state.editing;
|
||||
},
|
||||
|
||||
+10
-16
@@ -6,15 +6,13 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { computed } from 'mobx';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { UsersResource } from '@cloudbeaver/core-authentication';
|
||||
import { AdminUser, UsersResource } from '@cloudbeaver/core-authentication';
|
||||
import {
|
||||
BASE_CONTAINERS_STYLES, ColoredContainer, Container, Group,
|
||||
InfoItem, Loader, TextPlaceholder, useResource, useStyles, useTranslate
|
||||
BASE_CONTAINERS_STYLES, ColoredContainer, Container, getComputed, Group,
|
||||
InfoItem, Loader, TextPlaceholder, useAutoLoad, useResource, useStyles, useTranslate
|
||||
} from '@cloudbeaver/core-blocks';
|
||||
import { CachedMapAllKey } from '@cloudbeaver/core-sdk';
|
||||
import { TabContainerPanelComponent, useTab } from '@cloudbeaver/core-ui';
|
||||
@@ -51,24 +49,20 @@ export const GrantedUsers: TabContainerPanelComponent<ITeamFormProps> = observer
|
||||
const state = useGrantedUsers(formState.config, formState.mode);
|
||||
const { selected } = useTab(tabId);
|
||||
|
||||
const users = useResource(GrantedUsers, UsersResource, selected ? CachedMapAllKey : null);
|
||||
const users = useResource(GrantedUsers, UsersResource, CachedMapAllKey, { active: selected });
|
||||
|
||||
const grantedUsers = useMemo(() => computed(() => users.resource.values
|
||||
.filter(user => state.state.grantedUsers.includes(user.userId))
|
||||
), [state.state.grantedUsers, users.resource]);
|
||||
const grantedUsers = getComputed(() => users.data
|
||||
.filter<AdminUser>((user): user is AdminUser => !!user && state.state.grantedUsers.includes(user.userId))
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (selected && !state.state.loaded) {
|
||||
state.load();
|
||||
}
|
||||
}, [selected, state.state.loaded]);
|
||||
useAutoLoad(state, selected && !state.state.loaded);
|
||||
|
||||
if (!selected) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return styled(style)(
|
||||
<Loader state={[users, state.state]}>
|
||||
<Loader state={[state.state]}>
|
||||
{() => styled(style)(
|
||||
<ColoredContainer parent gap vertical>
|
||||
{!users.resource.values.length ? (
|
||||
@@ -80,7 +74,7 @@ export const GrantedUsers: TabContainerPanelComponent<ITeamFormProps> = observer
|
||||
{formState.mode === 'edit' && state.changed && <InfoItem info='ui_save_reminder' />}
|
||||
<Container gap overflow>
|
||||
<GrantedUserList
|
||||
grantedUsers={grantedUsers.get()}
|
||||
grantedUsers={grantedUsers}
|
||||
disabled={formState.disabled}
|
||||
onEdit={state.edit}
|
||||
onRevoke={state.revoke}
|
||||
|
||||
+11
-2
@@ -13,12 +13,12 @@ import { useObservableRef } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { useTabState } from '@cloudbeaver/core-ui';
|
||||
import { isArraysEqual } from '@cloudbeaver/core-utils';
|
||||
import { ILoadableState, isArraysEqual } from '@cloudbeaver/core-utils';
|
||||
|
||||
import type { TeamFormMode } from '../ITeamFormProps';
|
||||
import type { IGrantedUsersTabState } from './IGrantedUsersTabState';
|
||||
|
||||
interface State {
|
||||
interface State extends ILoadableState {
|
||||
state: IGrantedUsersTabState;
|
||||
changed: boolean;
|
||||
edit: () => void;
|
||||
@@ -36,6 +36,15 @@ export function useGrantedUsers(team: TeamInfo, mode: TeamFormMode): Readonly<St
|
||||
get changed() {
|
||||
return !isArraysEqual(this.state.initialGrantedUsers, this.state.grantedUsers);
|
||||
},
|
||||
isLoading() {
|
||||
return this.state.loading;
|
||||
},
|
||||
isLoaded() {
|
||||
return this.state.loaded;
|
||||
},
|
||||
isError() {
|
||||
return false;
|
||||
},
|
||||
edit() {
|
||||
this.state.editing = !this.state.editing;
|
||||
},
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 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 styled from 'reshadow';
|
||||
|
||||
import { TeamMetaParametersResource } from '@cloudbeaver/core-authentication';
|
||||
import { BASE_CONTAINERS_STYLES, Group, useTranslate, useStyles, useResource, GroupTitle, ObjectPropertyInfoForm } from '@cloudbeaver/core-blocks';
|
||||
|
||||
import type { ITeamFormState } from '../ITeamFormProps';
|
||||
|
||||
interface IProps {
|
||||
state: ITeamFormState;
|
||||
}
|
||||
|
||||
export const TeamMetaParameters = observer<IProps>(function TeamMetaParameters({
|
||||
state,
|
||||
}) {
|
||||
const teamMetaParameters = useResource(TeamMetaParameters, TeamMetaParametersResource, undefined);
|
||||
const translate = useTranslate();
|
||||
const style = useStyles(BASE_CONTAINERS_STYLES);
|
||||
|
||||
if (teamMetaParameters.data.length > 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return styled(style)(
|
||||
<Group small gap vertical overflow>
|
||||
<GroupTitle keepSize>{translate('authentication_team_meta_parameters')}</GroupTitle>
|
||||
<ObjectPropertyInfoForm
|
||||
state={state.config.metaParameters}
|
||||
properties={teamMetaParameters.data}
|
||||
disabled={state.disabled}
|
||||
keepSize
|
||||
tiny
|
||||
/>
|
||||
</Group>
|
||||
);
|
||||
});
|
||||
+4
-20
@@ -10,13 +10,13 @@ import { observer } from 'mobx-react-lite';
|
||||
import { useRef } from 'react';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { TeamMetaParametersResource } from '@cloudbeaver/core-authentication';
|
||||
import { BASE_CONTAINERS_STYLES, ColoredContainer, Group, InputField, SubmittingForm, Textarea, useTranslate, useStyles, useResource, Loader, GroupTitle, ObjectPropertyInfoForm } from '@cloudbeaver/core-blocks';
|
||||
import { ServerConfigResource } from '@cloudbeaver/core-root';
|
||||
import type { TabContainerPanelComponent } from '@cloudbeaver/core-ui';
|
||||
|
||||
import type { ITeamFormProps } from '../ITeamFormProps';
|
||||
import { Permissions } from './Permissions';
|
||||
import { TeamMetaParameters } from './TeamMetaParameters';
|
||||
|
||||
const styles = css`
|
||||
SubmittingForm {
|
||||
@@ -28,13 +28,10 @@ const styles = css`
|
||||
export const TeamOptions: TabContainerPanelComponent<ITeamFormProps> = observer(function TeamOptions({
|
||||
state,
|
||||
}) {
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
|
||||
const translate = useTranslate();
|
||||
const serverConfigResource = useResource(TeamOptions, ServerConfigResource, undefined);
|
||||
const teamMetaParameters = useResource(TeamOptions, TeamMetaParametersResource, undefined);
|
||||
|
||||
const style = useStyles(BASE_CONTAINERS_STYLES, styles);
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
const translate = useTranslate();
|
||||
const edit = state.mode === 'edit';
|
||||
|
||||
return styled(style)(
|
||||
@@ -74,20 +71,7 @@ export const TeamOptions: TabContainerPanelComponent<ITeamFormProps> = observer(
|
||||
</Textarea>
|
||||
</Group>
|
||||
{!serverConfigResource.resource.distributed && <Permissions state={state} />}
|
||||
<Loader state={teamMetaParameters} inline>
|
||||
{() => teamMetaParameters.data.length > 0 && styled(style)(
|
||||
<Group small gap vertical overflow>
|
||||
<GroupTitle keepSize>{translate('authentication_team_meta_parameters')}</GroupTitle>
|
||||
<ObjectPropertyInfoForm
|
||||
state={state.config.metaParameters}
|
||||
properties={teamMetaParameters.data}
|
||||
disabled={state.disabled}
|
||||
keepSize
|
||||
tiny
|
||||
/>
|
||||
</Group>
|
||||
)}
|
||||
</Loader>
|
||||
<TeamMetaParameters state={state} />
|
||||
</ColoredContainer>
|
||||
</SubmittingForm>
|
||||
);
|
||||
|
||||
+4
-2
@@ -11,7 +11,7 @@ import { useEffect } from 'react';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import type { TeamInfo } from '@cloudbeaver/core-authentication';
|
||||
import { Placeholder, useObjectRef, useExecutor, BASE_CONTAINERS_STYLES, IconOrImage, useTranslate, useStyles } from '@cloudbeaver/core-blocks';
|
||||
import { Placeholder, useObjectRef, useExecutor, BASE_CONTAINERS_STYLES, IconOrImage, useTranslate, useStyles, Loader } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { TabsState, TabList, UNDERLINE_TAB_STYLES, TabPanelList, BASE_TAB_STYLES } from '@cloudbeaver/core-ui';
|
||||
|
||||
@@ -150,7 +150,9 @@ export const TeamForm = observer<Props>(function TeamForm({
|
||||
<TabList style={style} disabled={false} />
|
||||
</team-top-bar-tabs>
|
||||
<team-top-bar-actions>
|
||||
<Placeholder container={service.actionsContainer} state={state} onCancel={onCancel} />
|
||||
<Loader suspense inline hideMessage hideException>
|
||||
<Placeholder container={service.actionsContainer} state={state} onCancel={onCancel} />
|
||||
</Loader>
|
||||
</team-top-bar-actions>
|
||||
</team-top-bar>
|
||||
<content-box>
|
||||
|
||||
+4
-2
@@ -10,7 +10,7 @@ import { observer } from 'mobx-react-lite';
|
||||
import styled, { css, use } from 'reshadow';
|
||||
|
||||
import type { TeamInfo } from '@cloudbeaver/core-authentication';
|
||||
import { TableItem, TableColumnValue, TableItemSelect, TableItemExpand, Placeholder, useStyles } from '@cloudbeaver/core-blocks';
|
||||
import { TableItem, TableColumnValue, TableItemSelect, TableItemExpand, Placeholder, useStyles, Loader } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
|
||||
|
||||
@@ -53,7 +53,9 @@ export const Team = observer<Props>(function Team({ team }) {
|
||||
<TableColumnValue title={team.teamName} ellipsis>{team.teamName || ''}</TableColumnValue>
|
||||
<TableColumnValue title={team.description} ellipsis>{team.description || ''}</TableColumnValue>
|
||||
<TableColumnValue flex {...use({ gap: true })}>
|
||||
<Placeholder container={service.teamDetailsInfoPlaceholder} team={team} />
|
||||
<Loader suspense small inline hideMessage>
|
||||
<Placeholder container={service.teamDetailsInfoPlaceholder} team={team} />
|
||||
</Loader>
|
||||
</TableColumnValue>
|
||||
</TableItem>
|
||||
);
|
||||
|
||||
+4
-2
@@ -11,7 +11,7 @@ import styled, { css, use } from 'reshadow';
|
||||
|
||||
import { AdminUser, UsersResource } from '@cloudbeaver/core-authentication';
|
||||
import {
|
||||
TableItem, TableColumnValue, TableItemSelect, TableItemExpand, Placeholder, Checkbox, useTranslate
|
||||
TableItem, TableColumnValue, TableItemSelect, TableItemExpand, Placeholder, Checkbox, useTranslate, Loader
|
||||
} from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
@@ -76,7 +76,9 @@ export const User = observer<Props>(function User({ user, displayAuthRole, selec
|
||||
/>
|
||||
</TableColumnValue>
|
||||
<TableColumnValue flex {...use({ gap: true })}>
|
||||
<Placeholder container={usersAdministrationService.userDetailsInfoPlaceholder} user={user} />
|
||||
<Loader suspense small inline hideMessage>
|
||||
<Placeholder container={usersAdministrationService.userDetailsInfoPlaceholder} user={user} />
|
||||
</Loader>
|
||||
</TableColumnValue>
|
||||
</TableItem>
|
||||
);
|
||||
|
||||
@@ -184,21 +184,17 @@ export const AuthDialog: DialogComponent<IAuthOptions, null> = observer(function
|
||||
</TabList>
|
||||
)}
|
||||
<SubmittingForm {...use({ form: !federate })} onSubmit={login}>
|
||||
<Loader state={dialogData.loadingState}>
|
||||
{() => federate
|
||||
? (
|
||||
<ConfigurationsList
|
||||
activeProvider={state.activeProvider}
|
||||
activeConfiguration={state.activeConfiguration}
|
||||
providers={dialogData.configurations}
|
||||
onAuthorize={(provider, configuration) => {
|
||||
state.setActiveConfiguration(provider, configuration);
|
||||
}}
|
||||
onClose={rejectDialog}
|
||||
/>
|
||||
)
|
||||
: renderForm(state.activeProvider)}
|
||||
</Loader>
|
||||
{federate ? (
|
||||
<ConfigurationsList
|
||||
activeProvider={state.activeProvider}
|
||||
activeConfiguration={state.activeConfiguration}
|
||||
providers={dialogData.configurations}
|
||||
onAuthorize={(provider, configuration) => {
|
||||
state.setActiveConfiguration(provider, configuration);
|
||||
}}
|
||||
onClose={rejectDialog}
|
||||
/>
|
||||
) : renderForm(state.activeProvider)}
|
||||
</SubmittingForm>
|
||||
</CommonDialogBody>
|
||||
{!federate && (
|
||||
|
||||
@@ -26,7 +26,6 @@ interface IData {
|
||||
destroyed: boolean;
|
||||
configure: boolean;
|
||||
adminPageActive: boolean;
|
||||
loadingState: ILoadableState;
|
||||
providers: AuthProvider[];
|
||||
configurations: AuthProvider[];
|
||||
|
||||
@@ -52,7 +51,7 @@ export function useAuthDialogState(accessRequest: boolean, providerId: string |
|
||||
|
||||
const primaryId = authProvidersResource.resource.getPrimary();
|
||||
const adminPageActive = administrationScreenService.isAdministrationPageActive;
|
||||
const providers = authProvidersResource.resource.values
|
||||
const providers = authProvidersResource.data
|
||||
.filter(notEmptyProvider)
|
||||
.sort(compareProviders);
|
||||
|
||||
@@ -129,7 +128,6 @@ export function useAuthDialogState(accessRequest: boolean, providerId: string |
|
||||
exception: null,
|
||||
authenticating: false,
|
||||
destroyed: false,
|
||||
loadingState: authProvidersResource,
|
||||
|
||||
get configure(): boolean {
|
||||
if (state.activeProvider) {
|
||||
@@ -197,7 +195,7 @@ export function useAuthDialogState(accessRequest: boolean, providerId: string |
|
||||
}
|
||||
|
||||
function notEmptyProvider(obj: any): obj is AuthProvider {
|
||||
return typeof obj === 'object';
|
||||
return !!obj && typeof obj === 'object';
|
||||
}
|
||||
|
||||
function compareProviders(providerA: AuthProvider, providerB: AuthProvider): number {
|
||||
|
||||
+7
-5
@@ -10,7 +10,7 @@ import { observer } from 'mobx-react-lite';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import {
|
||||
TableItem, TableColumnValue, TableItemSelect, TableItemExpand, StaticImage, Placeholder
|
||||
TableItem, TableColumnValue, TableItemSelect, TableItemExpand, StaticImage, Placeholder, Loader
|
||||
} from '@cloudbeaver/core-blocks';
|
||||
import { DatabaseConnection, DBDriverResource, IConnectionInfoParams } from '@cloudbeaver/core-connections';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
@@ -65,10 +65,12 @@ export const Connection = observer<Props>(function Connection({ connectionKey, c
|
||||
{projectName !== undefined && (
|
||||
<TableColumnValue title={projectName ?? ''} expand ellipsis>{projectName}</TableColumnValue>)}
|
||||
<TableColumnValue flex>
|
||||
<Placeholder
|
||||
container={connectionsAdministrationService.connectionDetailsPlaceholder}
|
||||
connection={connection}
|
||||
/>
|
||||
<Loader suspense small inline hideMessage>
|
||||
<Placeholder
|
||||
container={connectionsAdministrationService.connectionDetailsPlaceholder}
|
||||
connection={connection}
|
||||
/>
|
||||
</Loader>
|
||||
</TableColumnValue>
|
||||
</TableItem>
|
||||
);
|
||||
|
||||
@@ -175,7 +175,9 @@ export const ConnectionForm = observer<Props>(function ConnectionForm({
|
||||
<TabList style={style} disabled={state.disabled} />
|
||||
</connection-top-bar-tabs>
|
||||
<connection-top-bar-actions>
|
||||
<Placeholder container={service.actionsContainer} state={state} onCancel={onCancel} />
|
||||
<Loader suspense inline hideMessage hideException>
|
||||
<Placeholder container={service.actionsContainer} state={state} onCancel={onCancel} />
|
||||
</Loader>
|
||||
</connection-top-bar-actions>
|
||||
</connection-top-bar>
|
||||
<content-box>
|
||||
|
||||
+28
-30
@@ -6,7 +6,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { computed, observable } from 'mobx';
|
||||
import { computed, observable, runInAction } from 'mobx';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useMemo, useState } from 'react';
|
||||
import styled, { css } from 'reshadow';
|
||||
@@ -64,21 +64,23 @@ export const DriverProperties: TabContainerPanelComponent<IConnectionFormProps>
|
||||
DriverProperties,
|
||||
DBDriverResource,
|
||||
{ key: (selected && formState.config.driverId) || null, includes: ['includeDriverProperties'] as const },
|
||||
{
|
||||
active: selected && !!formState.config.driverId,
|
||||
onData: driver => {
|
||||
for (const key of Object.keys(formState.config.properties)) {
|
||||
if (driver.driverProperties.some(property => property.id === key)
|
||||
|| state.propertiesList.some(property => property.key === key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
state.add(key, formState.config.properties[key]);
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
runInAction(() => {
|
||||
if (driver.data) {
|
||||
for (const key of Object.keys(formState.config.properties)) {
|
||||
if (
|
||||
driver.data.driverProperties.some(property => property.id === key)
|
||||
|| state.propertiesList.some(property => property.key === key)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
state.add(key, formState.config.properties[key]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const joinedProperties = useMemo(() => computed<IProperty[]>(() => ([
|
||||
...state.propertiesList,
|
||||
...(driver.data?.driverProperties
|
||||
@@ -96,21 +98,17 @@ export const DriverProperties: TabContainerPanelComponent<IConnectionFormProps>
|
||||
])), [driver.data]);
|
||||
|
||||
return styled(style)(
|
||||
<Loader state={driver}>
|
||||
{() => styled(style)(
|
||||
<ColoredContainer parent>
|
||||
<Group box keepSize large>
|
||||
<PropertiesTable
|
||||
properties={joinedProperties.get()}
|
||||
propertiesState={formState.config.properties}
|
||||
readOnly={formState.readonly}
|
||||
filterable
|
||||
onAdd={state.add}
|
||||
onRemove={state.remove}
|
||||
/>
|
||||
</Group>
|
||||
</ColoredContainer>
|
||||
)}
|
||||
</Loader>
|
||||
<ColoredContainer parent>
|
||||
<Group box keepSize large>
|
||||
<PropertiesTable
|
||||
properties={joinedProperties.get()}
|
||||
propertiesState={formState.config.properties}
|
||||
readOnly={formState.readonly}
|
||||
filterable
|
||||
onAdd={state.add}
|
||||
onRemove={state.remove}
|
||||
/>
|
||||
</Group>
|
||||
</ColoredContainer>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -99,17 +99,6 @@ export const Options: TabContainerPanelComponent<IConnectionFormProps> = observe
|
||||
const optionsHook = useOptions(state);
|
||||
const { credentialsSavingEnabled } = useAdministrationSettings();
|
||||
|
||||
const handleDriverSelect = useCallback(async (value?: string, name?: string, prev?: string) => {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const prevDriver = prev ? await driverMap.resource.load(prev) : undefined;
|
||||
const newDriver = await driverMap.resource.load(value);
|
||||
|
||||
optionsHook.setDefaults(newDriver, prevDriver);
|
||||
}, []);
|
||||
|
||||
const handleAuthModelSelect = useCallback(async (value?: string, name?: string, prev?: string) => {
|
||||
const model = applicableAuthModels.find(model => model?.id === value);
|
||||
|
||||
@@ -125,10 +114,8 @@ export const Options: TabContainerPanelComponent<IConnectionFormProps> = observe
|
||||
DBDriverResource,
|
||||
{ key: config.driverId || null, includes: ['includeProviderProperties'] as const },
|
||||
{
|
||||
onData: (data, resource, prevData) => {
|
||||
if (data.id !== prevData?.id) {
|
||||
optionsHook.setDefaults(data);
|
||||
}
|
||||
onData: (data, resource, prevDriver) => {
|
||||
optionsHook.setDefaults(data, prevDriver);
|
||||
},
|
||||
}
|
||||
);
|
||||
@@ -222,7 +209,6 @@ export const Options: TabContainerPanelComponent<IConnectionFormProps> = observe
|
||||
loading={driverMap.isLoading()}
|
||||
tiny
|
||||
fill
|
||||
onSelect={handleDriverSelect}
|
||||
>
|
||||
{translate('connections_connection_driver')}
|
||||
</Combobox>
|
||||
|
||||
+9
-8
@@ -9,10 +9,9 @@
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { BASE_CONTAINERS_STYLES, Group, Placeholder, PlaceholderElement, useStyles } from '@cloudbeaver/core-blocks';
|
||||
import { BASE_CONTAINERS_STYLES, Group, Loader, Placeholder, PlaceholderElement, useStyles } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
|
||||
|
||||
import { ElementsTreeSettingsService, IElementsTreeSettingsProps } from './ElementsTreeSettingsService';
|
||||
|
||||
const expandStyles = css`
|
||||
@@ -47,12 +46,14 @@ export const NavigationTreeSettings = observer<Props>(function NavigationTreeSet
|
||||
return styled(styles)(
|
||||
<settings className={className}>
|
||||
<Group keepSize form gap dense>
|
||||
<Placeholder
|
||||
container={elementsTreeSettingsService.placeholder}
|
||||
elements={elements}
|
||||
tree={tree}
|
||||
style={style}
|
||||
/>
|
||||
<Loader suspense>
|
||||
<Placeholder
|
||||
container={elementsTreeSettingsService.placeholder}
|
||||
elements={elements}
|
||||
tree={tree}
|
||||
style={style}
|
||||
/>
|
||||
</Loader>
|
||||
</Group>
|
||||
</settings>
|
||||
);
|
||||
|
||||
+1
-3
@@ -48,9 +48,7 @@ export const VirtualFolderPanel: NavNodeTransformViewComponent = observer(functi
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
untracked(() => {
|
||||
navNodeViewService.logDuplicates(nodeId, duplicates);
|
||||
});
|
||||
navNodeViewService.logDuplicates(nodeId, duplicates);
|
||||
});
|
||||
|
||||
const objects = dbObject.data
|
||||
|
||||
+1
-3
@@ -52,9 +52,7 @@ export const ObjectPropertyTable = observer<ObjectPropertyTableProps>(function O
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
untracked(() => {
|
||||
navNodeViewService.logDuplicates(objectId, duplicates);
|
||||
});
|
||||
navNodeViewService.logDuplicates(objectId, duplicates);
|
||||
});
|
||||
|
||||
const objects = dbObject.data.filter(object => nodes.includes(object?.id ?? '')) as DBObject[];
|
||||
|
||||
@@ -10,12 +10,13 @@ import { observer } from 'mobx-react-lite';
|
||||
import { useMemo } from 'react';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { useResource, useUserData } from '@cloudbeaver/core-blocks';
|
||||
import { getComputed, useResource, useUserData } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { NavNodeInfoResource, NavTreeResource, ProjectsNavNodeService } from '@cloudbeaver/core-navigation-tree';
|
||||
import { ProjectInfoResource, ProjectsService } from '@cloudbeaver/core-projects';
|
||||
import { IResourceManagerParams, ResourceManagerResource, RESOURCES_NODE_PATH } from '@cloudbeaver/core-resource-manager';
|
||||
import { resourceKeyList } from '@cloudbeaver/core-sdk';
|
||||
import { isArraysEqual } from '@cloudbeaver/core-utils';
|
||||
import { CaptureView } from '@cloudbeaver/core-view';
|
||||
import { NavigationTreeService, ElementsTreeLoader, IElementsTreeSettings, createElementsTreeSettings, validateElementsTreeSettings, getNavigationTreeUserSettingsId } from '@cloudbeaver/plugin-navigation-tree';
|
||||
|
||||
@@ -79,9 +80,13 @@ export const ResourceManagerTree: React.FC<Props> = observer(function ResourceMa
|
||||
const resourceManagerService = useService(ResourceManagerService);
|
||||
const navTreeResource = useService(NavTreeResource);
|
||||
|
||||
const key: IResourceManagerParams[] = projectsService.activeProjectIds.map(id => ({ projectId: id }));
|
||||
const key = getComputed<IResourceManagerParams[]>(
|
||||
() => projectsService.activeProjects.map(project => ({ projectId: project.id })),
|
||||
isArraysEqual
|
||||
);
|
||||
useResource(ResourceManagerTree, ResourceManagerResource, resourceKeyList(key));
|
||||
|
||||
|
||||
const settings = useUserData<IElementsTreeSettings>(
|
||||
getNavigationTreeUserSettingsId(root),
|
||||
createElementsTreeSettings,
|
||||
@@ -166,7 +171,6 @@ export const ResourceManagerTree: React.FC<Props> = observer(function ResourceMa
|
||||
|
||||
const settingsElements = useMemo(() => ([ProjectsSettingsPlaceholderElement]), []);
|
||||
|
||||
|
||||
return styled(styles)(
|
||||
<CaptureView view={navTreeService}>
|
||||
<ResourceManagerTreeCaptureViewContext resourceTypeId={resourceTypeId} />
|
||||
|
||||
Reference in New Issue
Block a user