Merge branch 'devel' into CB-4386-unify-feature-naming

This commit is contained in:
s.teleshev
2023-12-18 17:20:46 +01:00
100 changed files with 711 additions and 499 deletions
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Model
Bundle-SymbolicName: io.cloudbeaver.model;singleton:=true
Bundle-Version: 1.0.42.qualifier
Bundle-Release-Date: 20231204
Bundle-Version: 1.0.43.qualifier
Bundle-Release-Date: 20231225
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.model</artifactId>
<version>1.0.42-SNAPSHOT</version>
<version>1.0.43-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -173,6 +173,11 @@ public class WebPropertyInfo {
return new String[0];
}
@Property
public boolean isRequired() {
return property.isRequired();
}
public boolean hasFeature(@NotNull String feature) {
return property.hasFeature(feature);
}
@@ -55,6 +55,7 @@ public class WebAuthProviderDescriptor extends AbstractDescriptor {
private final boolean trusted;
private final boolean isPrivate;
private final String[] requiredFeatures;
private final boolean isRequired;
public WebAuthProviderDescriptor(IConfigurationElement cfg) {
super(cfg);
@@ -64,6 +65,7 @@ public class WebAuthProviderDescriptor extends AbstractDescriptor {
this.configurable = CommonUtils.toBoolean(cfg.getAttribute("configurable"));
this.trusted = CommonUtils.toBoolean(cfg.getAttribute("trusted"));
this.isPrivate = CommonUtils.toBoolean(cfg.getAttribute("private"));
this.isRequired = CommonUtils.toBoolean(cfg.getAttribute("required"));
for (IConfigurationElement cfgElement : cfg.getChildren("configuration")) {
for (IConfigurationElement propGroup : ArrayUtils.safeArray(cfgElement.getChildren(PropertyDescriptor.TAG_PROPERTY_GROUP))) {
@@ -124,6 +126,10 @@ public class WebAuthProviderDescriptor extends AbstractDescriptor {
return isPrivate;
}
public boolean isRequired() {
return isRequired;
}
public List<PropertyDescriptor> getConfigurationParameters() {
return new ArrayList<>(configurationParameters.values());
}
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Community Product
Bundle-SymbolicName: io.cloudbeaver.product.ce;singleton:=true
Bundle-Version: 23.3.0.qualifier
Bundle-Release-Date: 20231204
Bundle-Version: 23.3.1.qualifier
Bundle-Release-Date: 20231225
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.product.ce</artifactId>
<version>23.3.0-SNAPSHOT</version>
<version>23.3.1-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -2,8 +2,8 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Base JDBC drivers
Bundle-SymbolicName: io.cloudbeaver.resources.drivers.base;singleton:=true
Bundle-Version: 1.0.87.qualifier
Bundle-Release-Date: 20231204
Bundle-Version: 1.0.88.qualifier
Bundle-Release-Date: 20231225
Bundle-Vendor: DBeaver Corp
Bundle-ActivationPolicy: lazy
Automatic-Module-Name: io.cloudbeaver.resources.drivers.base
@@ -9,6 +9,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.resources.drivers.base</artifactId>
<version>1.0.87-SNAPSHOT</version>
<version>1.0.88-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Server
Bundle-SymbolicName: io.cloudbeaver.server;singleton:=true
Bundle-Version: 23.3.0.qualifier
Bundle-Release-Date: 20231204
Bundle-Version: 23.3.1.qualifier
Bundle-Release-Date: 20231225
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-Activator: io.cloudbeaver.server.CBPlatformActivator
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.server</artifactId>
<version>23.3.0-SNAPSHOT</version>
<version>23.3.1-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -36,6 +36,8 @@ type ObjectPropertyInfo {
order: Int!
# Supported configuration types (for driver properties)
supportedConfigurationTypes: [ String! ]
# Required parameters
required: Boolean! @since(version: "23.3.1")
}
enum ObjectPropertyLength {
@@ -83,6 +83,10 @@ public class WebAuthProviderInfo {
return descriptor.isPrivate();
}
public boolean isRequired() {
return descriptor.isRequired();
}
public boolean isSupportProvisioning() {
return descriptor.getInstance() instanceof SMProvisioner;
}
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Administration
Bundle-SymbolicName: io.cloudbeaver.service.admin;singleton:=true
Bundle-Version: 1.0.86.qualifier
Bundle-Release-Date: 20231204
Bundle-Version: 1.0.87.qualifier
Bundle-Release-Date: 20231225
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.admin</artifactId>
<version>1.0.86-SNAPSHOT</version>
<version>1.0.87-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Authentication
Bundle-SymbolicName: io.cloudbeaver.service.auth;singleton:=true
Bundle-Version: 1.0.86.qualifier
Bundle-Release-Date: 20231204
Bundle-Version: 1.0.87.qualifier
Bundle-Release-Date: 20231225
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.auth</artifactId>
<version>1.0.86-SNAPSHOT</version>
<version>1.0.87-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -72,6 +72,8 @@ type AuthProviderInfo {
credentialProfiles: [AuthProviderCredentialsProfile!]!
requiredFeatures: [String!]!
required: Boolean!
}
type AuthInfo {
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Data Transfer
Bundle-SymbolicName: io.cloudbeaver.service.data.transfer;singleton:=true
Bundle-Version: 1.0.87.qualifier
Bundle-Release-Date: 20231204
Bundle-Version: 1.0.88.qualifier
Bundle-Release-Date: 20231225
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.data.transfer</artifactId>
<version>1.0.87-SNAPSHOT</version>
<version>1.0.88-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - File System
Bundle-SymbolicName: io.cloudbeaver.service.fs;singleton:=true
Bundle-Version: 1.0.4.qualifier
Bundle-Release-Date: 20231204
Bundle-Version: 1.0.5.qualifier
Bundle-Release-Date: 20231225
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.fs</artifactId>
<version>1.0.4-SNAPSHOT</version>
<version>1.0.5-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Metadata
Bundle-SymbolicName: io.cloudbeaver.service.metadata;singleton:=true
Bundle-Version: 1.0.90.qualifier
Bundle-Release-Date: 20231204
Bundle-Version: 1.0.91.qualifier
Bundle-Release-Date: 20231225
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.metadata</artifactId>
<version>1.0.90-SNAPSHOT</version>
<version>1.0.91-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Resource manager NIO implementation
Bundle-SymbolicName: io.cloudbeaver.service.rm.nio;singleton:=true
Bundle-Version: 1.0.4.qualifier
Bundle-Release-Date: 20231204
Bundle-Version: 1.0.5.qualifier
Bundle-Release-Date: 20231225
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.rm.nio</artifactId>
<version>1.0.4-SNAPSHOT</version>
<version>1.0.5-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Resource manager
Bundle-SymbolicName: io.cloudbeaver.service.rm;singleton:=true
Bundle-Version: 1.0.39.qualifier
Bundle-Release-Date: 20231204
Bundle-Version: 1.0.40.qualifier
Bundle-Release-Date: 20231225
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.rm</artifactId>
<version>1.0.39-SNAPSHOT</version>
<version>1.0.40-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: Cloudbeaver Web Service - Security
Bundle-Vendor: DBeaver Corp
Bundle-SymbolicName: io.cloudbeaver.service.security;singleton:=true
Bundle-Version: 1.0.42.qualifier
Bundle-Release-Date: 20231204
Bundle-Version: 1.0.43.qualifier
Bundle-Release-Date: 20231225
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -9,8 +9,8 @@
icon="platform:/plugin/org.jkiss.dbeaver.model/icons/tree/key.png">
<credentials>
<propertyGroup label="General">
<property id="user" label="User name" type="string" description="User name" admin="true" user="true" identifying="true"/>
<property id="password" label="User password" type="string" description="User password" encryption="hash" admin="true" user="true"/>
<property id="user" label="User name" type="string" description="User name" admin="true" user="true" identifying="true" required="true"/>
<property id="password" label="User password" type="string" description="User password" encryption="hash" admin="true" user="true" required="true"/>
</propertyGroup>
</credentials>
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.security</artifactId>
<version>1.0.42-SNAPSHOT</version>
<version>1.0.43-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: CloudBeaver SLF4j Binding
Bundle-SymbolicName: io.cloudbeaver.slf4j;singleton:=true
Bundle-Version: 1.0.2.qualifier
Bundle-Release-Date: 20231204
Bundle-Version: 1.0.3.qualifier
Bundle-Release-Date: 20231225
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.slf4j</artifactId>
<version>1.0.2-SNAPSHOT</version>
<version>1.0.3-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.ce.drivers.feature"
label="Cloudbeaver CE 3rd Party Drivers"
version="1.0.110.qualifier"
version="1.0.111.qualifier"
provider-name="DBeaver Corp">
<plugin id="io.cloudbeaver.resources.drivers.base" download-size="0" install-size="0" version="0.0.0"/>
@@ -9,6 +9,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.ce.drivers.feature</artifactId>
<version>1.0.110-SNAPSHOT</version>
<version>1.0.111-SNAPSHOT</version>
<packaging>eclipse-feature</packaging>
</project>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.product.ce.feature"
label="%featureName"
version="23.3.0.qualifier"
version="23.3.1.qualifier"
provider-name="%providerName"
plugin="io.cloudbeaver.product.ce">
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.product.ce.feature</artifactId>
<version>23.3.0-SNAPSHOT</version>
<version>23.3.1-SNAPSHOT</version>
<packaging>eclipse-feature</packaging>
<build>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.server.feature"
label="%featureName"
version="23.3.0.qualifier"
version="23.3.1.qualifier"
provider-name="%providerName"
plugin="io.cloudbeaver.server">
@@ -10,6 +10,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.server.feature</artifactId>
<version>23.3.0-SNAPSHOT</version>
<version>23.3.1-SNAPSHOT</version>
<packaging>eclipse-feature</packaging>
</project>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.ws.feature"
label="%featureName"
version="1.0.40.qualifier"
version="1.0.41.qualifier"
provider-name="%providerName"
plugin="io.cloudbeaver.model">
@@ -10,6 +10,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.ws.feature</artifactId>
<version>1.0.40-SNAPSHOT</version>
<version>1.0.41-SNAPSHOT</version>
<packaging>eclipse-feature</packaging>
</project>
+1 -1
View File
@@ -19,7 +19,7 @@
<properties>
<dbeaver-product>CloudBeaver CE</dbeaver-product>
<dbeaver-version>23.3.0</dbeaver-version>
<dbeaver-version>23.3.1</dbeaver-version>
</properties>
<modules>
@@ -2,7 +2,7 @@
<?pde version="3.5"?>
<product name="CloudBeaver ce Server" uid="io.cloudbeaver.product" id="io.cloudbeaver.product.ce.product"
application="io.cloudbeaver.product.ce.application" version="23.3.0.qualifier" useFeatures="true"
application="io.cloudbeaver.product.ce.application" version="23.3.1.qualifier" useFeatures="true"
includeLaunchers="false">
<configIni use="default">
+1 -1
View File
@@ -9,7 +9,7 @@
<version>1.0.0-SNAPSHOT</version>
<relativePath>../../</relativePath>
</parent>
<version>23.3.0-SNAPSHOT</version>
<version>23.3.1-SNAPSHOT</version>
<artifactId>web-server</artifactId>
<packaging>eclipse-repository</packaging>
<name>Cloudbeaver Server Product</name>
@@ -8,7 +8,6 @@
import { observer } from 'mobx-react-lite';
import { filterLayoutFakeProps, getLayoutProps } from '../../Containers/filterLayoutFakeProps';
import elementsSizeStyles from '../../Containers/shared/ElementsSize.m.css';
import { s } from '../../s';
import { useS } from '../../useS';
import { Field } from '../Field';
@@ -21,7 +21,7 @@ export interface IChangeData {
export interface IFormContext {
ref: HTMLFormElement | null;
onValidate: SyncExecutor;
onSubmit: SyncExecutor;
onSubmit: SyncExecutor<SubmitEvent | undefined>;
onChange: IExecutor<IChangeData>;
parent: IFormContext | null;
disableEnterSubmit: boolean;
@@ -86,6 +86,7 @@ export const Textarea: TextareaType = observer(function Textarea({
</FieldLabel>
<textarea
{...rest}
required={required}
className={s(styles, { textarea: true })}
value={value ?? ''}
name={name}
@@ -22,7 +22,7 @@ interface IOptions {
export function useForm(options?: IOptions): IFormContext {
let parentForm = useContext(FormContext);
const [submittingExecutor] = useState(() => new SyncExecutor());
const [submittingExecutor] = useState(() => new SyncExecutor<SubmitEvent | undefined>());
const [validationExecutor] = useState(() => new SyncExecutor());
const [changeExecutor] = useState(() => new Executor<IChangeData>());
@@ -49,7 +49,7 @@ export function useForm(options?: IOptions): IFormContext {
useExecutor({
executor: submittingExecutor,
handlers: [() => options?.onSubmit?.()],
handlers: [event => options?.onSubmit?.(event)],
});
const context = useObjectRef<IFormContext>(
@@ -93,7 +93,7 @@ export function useForm(options?: IOptions): IFormContext {
event?.preventDefault();
if (this.validate()) {
this.onSubmit.execute();
this.onSubmit.execute(event);
}
}
},
@@ -70,6 +70,7 @@ export const RenderField = observer<RenderFieldProps>(function RenderField({
const controlType = getPropertyControlType(property);
const password = property.features.includes('password');
const required = property.required && !readOnly;
const value = getValue(property.value, controlType);
const defaultValue = getValue(property.defaultValue, controlType);
@@ -79,6 +80,7 @@ export const RenderField = observer<RenderFieldProps>(function RenderField({
if (controlType === 'file' && state) {
return (
<InputFileTextContent
required={required}
tooltip={property.description}
labelTooltip={property.displayName || property.description}
name={property.id!}
@@ -118,6 +120,7 @@ export const RenderField = observer<RenderFieldProps>(function RenderField({
if (state !== undefined) {
return (
<FieldCheckbox
required={required}
id={property.id}
name={property.id!}
state={state}
@@ -149,6 +152,7 @@ export const RenderField = observer<RenderFieldProps>(function RenderField({
if (state !== undefined) {
return (
<Combobox
required={required}
name={property.id!}
state={state}
items={property.validValues!}
@@ -168,6 +172,7 @@ export const RenderField = observer<RenderFieldProps>(function RenderField({
return (
<Combobox
required={required}
name={property.id!}
items={property.validValues!}
keySelector={value => value}
@@ -188,6 +193,7 @@ export const RenderField = observer<RenderFieldProps>(function RenderField({
if (state !== undefined) {
return (
<Textarea
required={required}
title={state[property.id!]}
labelTooltip={property.description || property.displayName}
description={description}
@@ -204,6 +210,7 @@ export const RenderField = observer<RenderFieldProps>(function RenderField({
return (
<Textarea
required={required}
title={value}
labelTooltip={property.description || property.displayName}
description={description}
@@ -221,6 +228,7 @@ export const RenderField = observer<RenderFieldProps>(function RenderField({
if (state !== undefined) {
return (
<InputField
required={required}
type={password ? 'password' : 'text'}
title={password ? property.description || property.displayName : state[property.id!]}
labelTooltip={property.description || property.displayName}
@@ -243,6 +251,7 @@ export const RenderField = observer<RenderFieldProps>(function RenderField({
return (
<InputField
required={required}
type={password ? 'password' : 'text'}
title={password ? property.description || property.displayName : value}
labelTooltip={property.description || property.displayName}
@@ -22,6 +22,8 @@ interface IUseExecutorOptions<T> {
export function useExecutor<T>(options: IUseExecutorOptions<T>): void {
const props = useObjectRef(options);
const executor = props.executor;
const next: IExecutorHandlersCollection<T> | undefined = props.next;
const before: IExecutorHandlersCollection<T> | undefined = props.before;
useEffect(() => {
if (!executor) {
@@ -30,8 +32,6 @@ export function useExecutor<T>(options: IUseExecutorOptions<T>): void {
const handlers: Array<IExecutorHandler<T>> = [];
const postHandlers: Array<IExecutorHandler<T>> = [];
const next: IExecutorHandlersCollection<T> | undefined = props.next;
const before: IExecutorHandlersCollection<T> | undefined = props.before;
if (props.handlers) {
for (let i = 0; i < props.handlers.length; i++) {
@@ -49,12 +49,12 @@ export function useExecutor<T>(options: IUseExecutorOptions<T>): void {
}
}
if (props.next) {
executor.next(props.next);
if (before) {
executor.before(before);
}
if (props.before) {
executor.next(props.before);
if (next) {
executor.next(next);
}
return () => {
@@ -74,5 +74,7 @@ export function useExecutor<T>(options: IUseExecutorOptions<T>): void {
executor.removeBefore(before);
}
};
}, [executor, props.handlers?.length, props.postHandlers?.length]);
}, [
executor, props.handlers?.length, props.postHandlers?.length, before, next,
]);
}
@@ -11,7 +11,7 @@ import type { IAsyncContextLoader, IContextLoader, ISyncContextLoader } from './
import type { IExecutorHandler } from './IExecutorHandler';
import type { ExecutorDataFilter, ExecutorDataMap, IChainLink, IExecutorHandlersCollection } from './IExecutorHandlersCollection';
export class ExecutorHandlersCollection<T = unknown, TResult = any | Promise<any>> implements IExecutorHandlersCollection<T, TResult> {
export class ExecutorHandlersCollection<T = void, TResult = any | Promise<any>> implements IExecutorHandlersCollection<T, TResult> {
handlers: Array<IExecutorHandler<T, TResult>>;
postHandlers: Array<IExecutorHandler<T, TResult>>;
chain: Array<IChainLink<T, TResult>>;
@@ -7,6 +7,7 @@ query getAuthModels {
requiresLocalConfiguration
requiredAuth
properties {
required
id
displayName
description
@@ -9,6 +9,7 @@ query getDataTransferProcessors {
order
icon
properties {
required
id
displayName
description
@@ -10,4 +10,5 @@ fragment AuthProviderConfigurationParameters on ObjectPropertyInfo {
length
features
order
required
}
@@ -7,6 +7,7 @@ fragment AuthProviderInfo on AuthProviderInfo {
trusted
private
supportProvisioning
required
#configurationParameters {
# id
@@ -10,4 +10,5 @@ fragment DriverProviderPropertyInfo on ObjectPropertyInfo {
features
order
supportedConfigurationTypes
required
}
@@ -8,4 +8,5 @@ fragment NavNodeProperties on ObjectPropertyInfo {
features
value
order
required
}
@@ -6,6 +6,7 @@ fragment ObjectOriginInfo on ObjectOrigin {
details @include(if: $customIncludeOriginDetails) {
id
required
displayName
description
category
@@ -4,6 +4,7 @@ fragment ObjectPropertyInfo on ObjectPropertyInfo {
description
hint
category
required
dataType
value
validValues
@@ -9,5 +9,6 @@ fragment UserConnectionAuthProperties on ObjectPropertyInfo {
defaultValue
length
features
required
order
}
@@ -10,4 +10,5 @@ fragment UserConnectionNetworkHandlerProperties on ObjectPropertyInfo {
order
length
features
required
}
@@ -15,6 +15,7 @@ mutation getSqlExecutionPlanResult(
description
properties {
id
required
category
dataType
description
+17 -47
View File
@@ -6,74 +6,44 @@
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
import { useContext, useEffect, useMemo, useRef } from 'react';
import { TabPanel as BaseTabPanel, TabStateReturn } from 'reakit/Tab';
import { useContext, useMemo } from 'react';
import { TabPanel as BaseTabPanel } from 'reakit/Tab';
import { FormContext, getComputed, Loader } from '@cloudbeaver/core-blocks';
import { getComputed, Loader } from '@cloudbeaver/core-blocks';
import { DATA_CONTEXT_TABS_FORM_VALIDATION } from './DATA_CONTEXT_TABS_FORM_VALIDATION';
import { TabContext } from './TabContext';
import type { TabPanelProps } from './TabPanelProps';
import { TabsContext } from './TabsContext';
import { useTabsValidation } from './useTabsValidation';
export const TabPanel: React.FC<TabPanelProps> = observer(function TabPanel({ tabId, children, className, lazy }) {
const state = useContext(TabsContext);
const formContext = useContext(FormContext);
const panelRef = useRef<HTMLDivElement>(null);
const tabContextState = useContext(TabsContext);
if (!state) {
if (!tabContextState) {
throw new Error('Tabs context was not provided');
}
const validationTabId = state.context.get(DATA_CONTEXT_TABS_FORM_VALIDATION);
const panelRef = useTabsValidation(tabId);
const tabContext = useMemo(() => ({ tabId }), [tabId]);
const selected = getComputed(() => state.state.selectedId === tabId);
const enabled = getComputed(() => (lazy || state.lazy) && !selected);
useEffect(() => {
if (formContext === null) {
return;
}
async function trackValidity(event: Event) {
if (selected || state?.context.get(DATA_CONTEXT_TABS_FORM_VALIDATION) !== null) {
return;
}
state?.context.set(DATA_CONTEXT_TABS_FORM_VALIDATION, tabId);
await state?.open(tabId);
}
panelRef.current?.addEventListener('invalid', trackValidity, true);
return () => {
panelRef.current?.removeEventListener('invalid', trackValidity, true);
};
});
useEffect(() => {
if (selected && validationTabId === tabId) {
formContext?.validate();
state?.context.set(DATA_CONTEXT_TABS_FORM_VALIDATION, null);
}
});
const selected = getComputed(() => tabContextState.state.selectedId === tabId);
const enabled = getComputed(() => (lazy || tabContextState.lazy) && !selected);
if (enabled) {
return null;
}
if (typeof children === 'function') {
return (
<TabContext.Provider value={tabContext}>
<BaseTabPanel ref={panelRef} {...state.state} tabId={tabId} className={className}>
<Loader suspense>{(children as (state: TabStateReturn) => React.ReactNode)(state.state)}</Loader>
</BaseTabPanel>
</TabContext.Provider>
);
function renderChildren() {
if (typeof children === 'function') {
return children(tabContextState!.state);
}
return children;
}
return (
<TabContext.Provider value={tabContext}>
<BaseTabPanel ref={panelRef} {...state.state} tabId={tabId} className={className}>
<Loader suspense>{children}</Loader>
<BaseTabPanel ref={panelRef} {...tabContextState.state} tabId={tabId} className={className}>
<Loader suspense>{renderChildren()}</Loader>
</BaseTabPanel>
</TabContext.Provider>
);
@@ -17,6 +17,7 @@ import { isDefined, isNull, isUndefined, MetadataMap, MetadataValueGetter } from
import type { ITabData, ITabInfo, ITabsContainer } from './TabsContainer/ITabsContainer';
import { ITabsContext, type TabDirection, TabsContext } from './TabsContext';
import { TabsValidationProvider } from './TabsValidationProvider';
type ExtractContainerProps<T> = T extends void ? Record<string, any> : T;
@@ -292,5 +293,9 @@ export const TabsState = observer(function TabsState<T = Record<string, any>>({
);
}
return <TabsContext.Provider value={value}>{children}</TabsContext.Provider>;
return (
<TabsContext.Provider value={value}>
<TabsValidationProvider>{children}</TabsValidationProvider>
</TabsContext.Provider>
);
});
@@ -5,6 +5,10 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { createDataContext } from '@cloudbeaver/core-data-context';
import { createContext } from 'react';
export const DATA_CONTEXT_TABS_FORM_VALIDATION = createDataContext<string | null>('Tabs Form Validation', () => null);
export interface ITabsValidationContext {
invalidate: (tabId: string) => void;
}
export const TabsValidationContext = createContext<ITabsValidationContext | null>(null);
@@ -0,0 +1,71 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2023 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
import { useContext } from 'react';
import { useObjectRef } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
import { isDefined, isNotNullDefined } from '@cloudbeaver/core-utils';
import { TabsContext } from './TabsContext';
import { TabsValidationContext } from './TabsValidationContext';
export const TabsValidationProvider = observer(function TabsValidation({ children }: React.PropsWithChildren) {
const tabsContext = useContext(TabsContext);
const notificationService = useService(NotificationService);
if (!tabsContext) {
throw new Error('TabsState should be defined');
}
const innerState = useObjectRef(
() => ({
invalidTabs: new Set<string>(),
scheduledTabSwitch: -1,
invalidate(tabId: string) {
this.invalidTabs.add(tabId);
this.scheduleTabSwitch();
},
selectNextInvalidTab() {
const next = Array.from(this.invalidTabs)[0] as string | undefined;
if (!isDefined(next) || (isNotNullDefined(this.tabsContext.state.selectedId) && this.invalidTabs.has(this.tabsContext.state.selectedId))) {
return;
}
this.tabsContext.open(next).catch(() => {
this.notificationService.logError({ title: 'core_ui_form_save_error', message: 'core_ui_switch_tab_error' });
});
},
reset() {
this.invalidTabs.clear();
},
scheduleTabSwitch() {
this.clearScheduledTabSwitch();
this.scheduledTabSwitch = setTimeout(() => {
this.scheduledTabSwitch = -1;
this.selectNextInvalidTab();
this.reset();
}, 0) as any;
},
clearScheduledTabSwitch() {
if (this.scheduledTabSwitch !== -1) {
clearTimeout(this.scheduledTabSwitch);
this.scheduledTabSwitch = -1;
}
},
}),
{ tabsContext, notificationService },
['invalidate'],
);
return <TabsValidationContext.Provider value={innerState}>{children}</TabsValidationContext.Provider>;
});
@@ -0,0 +1,89 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2023 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 { useContext, useEffect, useRef } from 'react';
import { FormContext, getComputed, useExecutor, useObjectRef } from '@cloudbeaver/core-blocks';
import { ExecutorHandlersCollection } from '@cloudbeaver/core-executor';
import { TabsContext } from './TabsContext';
import { TabsValidationContext } from './TabsValidationContext';
export function useTabsValidation(tabId: string): React.RefObject<HTMLDivElement> {
const tabContextState = useContext(TabsContext);
const formContext = useContext(FormContext);
if (!tabContextState) {
throw new Error('Tabs context was not provided');
}
const panelRef = useRef<HTMLDivElement>(null);
const tabsValidationContext = useContext(TabsValidationContext);
const selected = getComputed(() => tabContextState.state.selectedId === tabId);
const tabPropsRef = useObjectRef({ tabId, tabsValidationContext });
const validationState = useObjectRef(
() => ({
beforeValidationCollection: new ExecutorHandlersCollection(),
invalidElement: null as HTMLInputElement | null,
invalidate(event: Event) {
tabPropsRef.tabsValidationContext?.invalidate(tabPropsRef.tabId);
// we want to store only first element in order in the form
if (this.invalidElement === null) {
this.invalidElement = event.target as HTMLInputElement | null;
}
},
reportValidity() {
if (this.invalidElement) {
this.invalidElement.reportValidity();
this.reset();
}
},
reset() {
this.invalidElement = null;
},
}),
false,
['invalidate'],
);
useEffect(() => {
const element = panelRef.current;
if (!element) {
return;
}
element.addEventListener('invalid', validationState.invalidate, true);
return () => {
element.removeEventListener('invalid', validationState.invalidate, true);
};
});
useEffect(() => {
if (selected) {
validationState.reportValidity();
}
}, [selected]);
useExecutor({
executor: formContext?.onValidate,
before: validationState.beforeValidationCollection,
});
useExecutor({
executor: validationState.beforeValidationCollection,
handlers: [
() => {
validationState.reset();
},
],
});
return panelRef;
}
@@ -1,4 +1,5 @@
export default [
['core_ui_empty', 'Empty'],
['core_ui_form_save_error', 'Error occurred when saving the form'],
['core_ui_switch_tab_error', 'Cannot switch tab during form validation'],
];
@@ -1,4 +1,5 @@
export default [
['core_ui_empty', 'Empty'],
['core_ui_form_save_error', 'Error occurred when saving the form'],
['core_ui_switch_tab_error', 'Cannot switch tab during form validation'],
];
@@ -1,4 +1,5 @@
export default [
['core_ui_empty', 'Пусто'],
['core_ui_form_save_error', 'Ошибка при сохранении формы'],
['core_ui_switch_tab_error', 'Невозможно переключить вкладку во время валидации формы'],
];
@@ -9,14 +9,14 @@ import { observer } from 'mobx-react-lite';
import { useEffect } from 'react';
import styled, { css } from 'reshadow';
import { IconOrImage, Loader, Placeholder, useExecutor, useObjectRef, useStyles, useTranslate } from '@cloudbeaver/core-blocks';
import { Form, IconOrImage, Loader, Placeholder, useExecutor, useForm, useObjectRef, useStyles, useTranslate } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import type { AdminAuthProviderConfiguration } from '@cloudbeaver/core-sdk';
import { BASE_TAB_STYLES, TabList, TabPanelList, TabsState, UNDERLINE_TAB_BIG_STYLES, UNDERLINE_TAB_STYLES } from '@cloudbeaver/core-ui';
import { AuthConfigurationFormService } from './AuthConfigurationFormService';
import { authConfigurationContext } from './Contexts/authConfigurationContext';
import type { IAuthConfigurationFormState } from './IAuthConfigurationFormProps';
import type { IAuthConfigurationFormActions, IAuthConfigurationFormState } from './IAuthConfigurationFormProps';
const tabsStyles = css`
TabList {
@@ -100,6 +100,12 @@ export const AuthConfigurationForm = observer<Props>(function AuthConfigurationF
const style = useStyles(BASE_TAB_STYLES, tabsStyles, UNDERLINE_TAB_STYLES, UNDERLINE_TAB_BIG_STYLES);
const styles = useStyles(style, topBarStyles, formStyles);
const service = useService(AuthConfigurationFormService);
const form = useForm({
onSubmit: async () => {
await state.save();
},
});
const actions = useObjectRef<IAuthConfigurationFormActions>({ save: form.submit });
useExecutor({
executor: state.submittingTask,
@@ -121,30 +127,33 @@ export const AuthConfigurationForm = observer<Props>(function AuthConfigurationF
}, []);
return styled(styles)(
<TabsState container={service.tabsContainer} state={state} onCancel={onCancel}>
<box className={className}>
<configuration-top-bar>
<configuration-top-bar-tabs>
<configuration-status-message>
{state.statusMessage && (
<>
<IconOrImage icon="/icons/info_icon.svg" />
{translate(state.statusMessage)}
</>
)}
</configuration-status-message>
<TabList style={style} disabled={false} />
</configuration-top-bar-tabs>
<configuration-top-bar-actions>
<Loader suspense inline hideMessage hideException>
<Placeholder container={service.actionsContainer} state={state} onCancel={onCancel} />
</Loader>
</configuration-top-bar-actions>
</configuration-top-bar>
<content-box>
<TabPanelList style={style} />
</content-box>
</box>
</TabsState>,
<Form context={form}>
<TabsState actions={actions} container={service.tabsContainer} state={state} onCancel={onCancel}>
<box className={className}>
<configuration-top-bar>
<configuration-top-bar-tabs>
<configuration-status-message>
{state.statusMessage && (
<>
<IconOrImage icon="/icons/info_icon.svg" />
{translate(state.statusMessage)}
</>
)}
</configuration-status-message>
<TabList style={style} disabled={false} />
</configuration-top-bar-tabs>
<configuration-top-bar-actions>
<Loader suspense inline hideMessage hideException>
<Placeholder actions={actions} container={service.actionsContainer} state={state} onCancel={onCancel} />
</Loader>
</configuration-top-bar-actions>
</configuration-top-bar>
<content-box>
<TabPanelList style={style} />
</content-box>
</box>
</TabsState>
</Form>
,
);
});
@@ -12,7 +12,7 @@ import { Button, PlaceholderComponent, useTranslate } from '@cloudbeaver/core-bl
import type { IAuthConfigurationFormProps } from './IAuthConfigurationFormProps';
export const AuthConfigurationFormBaseActions: PlaceholderComponent<IAuthConfigurationFormProps> = observer(
function AuthConfigurationFormBaseActions({ state, onCancel }) {
function AuthConfigurationFormBaseActions({ state, onCancel, actions }) {
const translate = useTranslate();
return (
@@ -22,7 +22,7 @@ export const AuthConfigurationFormBaseActions: PlaceholderComponent<IAuthConfigu
{translate('ui_processing_cancel')}
</Button>
)}
<Button type="button" disabled={state.disabled || state.readonly} mod={['unelevated']} loader onClick={state.save}>
<Button type="button" disabled={state.disabled || state.readonly} mod={['unelevated']} loader onClick={actions.save}>
{translate(state.mode === 'edit' ? 'ui_processing_save' : 'ui_processing_finish')}
</Button>
</>
@@ -30,8 +30,13 @@ export interface IAuthConfigurationFormState {
readonly setOptions: (mode: AuthConfigurationFormMode) => this;
}
export interface IAuthConfigurationFormActions {
save: () => void;
}
export interface IAuthConfigurationFormProps {
state: IAuthConfigurationFormState;
actions: IAuthConfigurationFormActions;
onCancel?: () => void;
}
@@ -14,7 +14,6 @@ import {
ColoredContainer,
Combobox,
FieldCheckbox,
Form,
Group,
GroupTitle,
InputField,
@@ -45,8 +44,6 @@ const emptyArray: AuthProviderConfigurationParametersFragment[] = [];
export const AuthConfigurationOptions: TabContainerPanelComponent<IAuthConfigurationFormProps> = observer(function AuthConfigurationOptions({
state,
}) {
const formRef = useRef<HTMLFormElement>(null);
const translate = useTranslate();
const copy = useClipboard();
const style = useStyles(styles);
@@ -63,133 +60,131 @@ export const AuthConfigurationOptions: TabContainerPanelComponent<IAuthConfigura
}, [state]);
return styled(style)(
<Form ref={formRef}>
<ColoredContainer parent gap overflow>
<ColoredContainer parent gap overflow>
<Group small gap>
<Combobox
name="providerId"
state={state.config}
items={providers.resource.configurable}
keySelector={provider => provider.id}
valueSelector={provider => provider.label}
iconSelector={provider => provider.icon}
titleSelector={provider => provider.description}
placeholder={translate('administration_identity_providers_choose_provider_placeholder')}
readOnly={state.readonly || edit}
disabled={state.disabled}
required
tiny
fill
onSelect={handleProviderSelect}
>
{translate('administration_identity_providers_provider')}
</Combobox>
<InputField name="id" state={state.config} readOnly={state.readonly || edit} disabled={state.disabled} required tiny fill>
{translate('administration_identity_providers_provider_id')}
</InputField>
<InputField name="displayName" state={state.config} minLength={1} disabled={state.disabled} readOnly={state.readonly} required tiny fill>
{translate('administration_identity_providers_provider_configuration_name')}
</InputField>
<Textarea name="description" state={state.config} disabled={state.disabled} readOnly={state.readonly}>
{translate('administration_identity_providers_provider_configuration_description')}
</Textarea>
<InputField name="iconURL" state={state.config} disabled={state.disabled} readOnly={state.readonly}>
{translate('administration_identity_providers_provider_configuration_icon_url')}
</InputField>
<FieldCheckbox
id={edit ? state.config.id : 'AuthConfigurationDisabled'}
name="disabled"
state={state.config}
disabled={state.disabled}
readOnly={state.readonly}
>
{translate('administration_identity_providers_provider_configuration_disabled')}
</FieldCheckbox>
</Group>
{parameters.isLoaded() && parameters.data && (
<>
{isUncategorizedExists && (
<Group small gap vertical>
<GroupTitle>{translate('administration_identity_providers_provider_configuration_parameters')}</GroupTitle>
<ObjectPropertyInfoForm
state={state.config.parameters}
properties={parameters.data}
category={null}
disabled={state.disabled}
readOnly={state.readonly}
/>
</Group>
)}
{categories.map(category => (
<Group key={category} small gap vertical>
<GroupTitle keepSize>{category}</GroupTitle>
<ObjectPropertyInfoForm
state={state.config.parameters}
properties={parameters.data!}
category={category}
disabled={state.disabled}
readOnly={state.readonly}
keepSize
/>
</Group>
))}
</>
)}
{(state.config.metadataLink || state.config.signInLink || state.config.signOutLink || state.config.acsLink) && (
<Group small gap>
<Combobox
name="providerId"
<GroupTitle>{translate('administration_identity_providers_provider_configuration_links')}</GroupTitle>
<InputField
name="signInLink"
state={state.config}
items={providers.resource.configurable}
keySelector={provider => provider.id}
valueSelector={provider => provider.label}
iconSelector={provider => provider.icon}
titleSelector={provider => provider.description}
placeholder={translate('administration_identity_providers_choose_provider_placeholder')}
readOnly={state.readonly || edit}
title={state.config.signInLink}
disabled={state.disabled}
required
tiny
fill
onSelect={handleProviderSelect}
autoHide
readOnly
onCustomCopy={() => copy(state.config.signInLink!, true)}
>
{translate('administration_identity_providers_provider')}
</Combobox>
<InputField name="id" state={state.config} readOnly={state.readonly || edit} disabled={state.disabled} required tiny fill>
{translate('administration_identity_providers_provider_id')}
Sign in
</InputField>
<InputField name="displayName" state={state.config} minLength={1} disabled={state.disabled} readOnly={state.readonly} required tiny fill>
{translate('administration_identity_providers_provider_configuration_name')}
</InputField>
<Textarea name="description" state={state.config} disabled={state.disabled} readOnly={state.readonly}>
{translate('administration_identity_providers_provider_configuration_description')}
</Textarea>
<InputField name="iconURL" state={state.config} disabled={state.disabled} readOnly={state.readonly}>
{translate('administration_identity_providers_provider_configuration_icon_url')}
</InputField>
<FieldCheckbox
id={edit ? state.config.id : 'AuthConfigurationDisabled'}
name="disabled"
<InputField
name="signOutLink"
state={state.config}
title={state.config.signOutLink}
disabled={state.disabled}
readOnly={state.readonly}
autoHide
readOnly
onCustomCopy={() => copy(state.config.signOutLink!, true)}
>
{translate('administration_identity_providers_provider_configuration_disabled')}
</FieldCheckbox>
Sign out
</InputField>
<InputField
name="redirectLink"
state={state.config}
title={state.config.redirectLink}
disabled={state.disabled}
autoHide
readOnly
onCustomCopy={() => copy(state.config.redirectLink!, true)}
>
Redirect
</InputField>
<InputField
name="acsLink"
state={state.config}
title={state.config.acsLink}
disabled={state.disabled}
autoHide
readOnly
onCustomCopy={() => copy(state.config.acsLink!, true)}
>
ACS
</InputField>
{state.config.metadataLink && (
<Link href={state.config.metadataLink} target="_blank" rel="noopener noreferrer">
{translate('administration_identity_providers_provider_configuration_links_metadata')}
</Link>
)}
</Group>
{parameters.isLoaded() && parameters.data && (
<>
{isUncategorizedExists && (
<Group small gap vertical>
<GroupTitle>{translate('administration_identity_providers_provider_configuration_parameters')}</GroupTitle>
<ObjectPropertyInfoForm
state={state.config.parameters}
properties={parameters.data}
category={null}
disabled={state.disabled}
readOnly={state.readonly}
/>
</Group>
)}
{categories.map(category => (
<Group key={category} small gap vertical>
<GroupTitle keepSize>{category}</GroupTitle>
<ObjectPropertyInfoForm
state={state.config.parameters}
properties={parameters.data!}
category={category}
disabled={state.disabled}
readOnly={state.readonly}
keepSize
/>
</Group>
))}
</>
)}
{(state.config.metadataLink || state.config.signInLink || state.config.signOutLink || state.config.acsLink) && (
<Group small gap>
<GroupTitle>{translate('administration_identity_providers_provider_configuration_links')}</GroupTitle>
<InputField
name="signInLink"
state={state.config}
title={state.config.signInLink}
disabled={state.disabled}
autoHide
readOnly
onCustomCopy={() => copy(state.config.signInLink!, true)}
>
Sign in
</InputField>
<InputField
name="signOutLink"
state={state.config}
title={state.config.signOutLink}
disabled={state.disabled}
autoHide
readOnly
onCustomCopy={() => copy(state.config.signOutLink!, true)}
>
Sign out
</InputField>
<InputField
name="redirectLink"
state={state.config}
title={state.config.redirectLink}
disabled={state.disabled}
autoHide
readOnly
onCustomCopy={() => copy(state.config.redirectLink!, true)}
>
Redirect
</InputField>
<InputField
name="acsLink"
state={state.config}
title={state.config.acsLink}
disabled={state.disabled}
autoHide
readOnly
onCustomCopy={() => copy(state.config.acsLink!, true)}
>
ACS
</InputField>
{state.config.metadataLink && (
<Link href={state.config.metadataLink} target="_blank" rel="noopener noreferrer">
{translate('administration_identity_providers_provider_configuration_links_metadata')}
</Link>
)}
</Group>
)}
</ColoredContainer>
</Form>,
)}
</ColoredContainer>
);
});
@@ -6,10 +6,9 @@
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
import { useRef } from 'react';
import styled, { css } from 'reshadow';
import { ColoredContainer, Form, Group, InputField, Textarea, useResource, useStyles, useTranslate } from '@cloudbeaver/core-blocks';
import { ColoredContainer, Group, InputField, Textarea, useResource, useStyles, useTranslate } from '@cloudbeaver/core-blocks';
import { ServerConfigResource } from '@cloudbeaver/core-root';
import type { TabContainerPanelComponent } from '@cloudbeaver/core-ui';
@@ -27,27 +26,24 @@ const styles = css`
export const TeamOptions: TabContainerPanelComponent<ITeamFormProps> = observer(function TeamOptions({ state }) {
const serverConfigResource = useResource(TeamOptions, ServerConfigResource, undefined);
const style = useStyles(styles);
const formRef = useRef<HTMLFormElement>(null);
const translate = useTranslate();
const edit = state.mode === 'edit';
return styled(style)(
<Form ref={formRef}>
<ColoredContainer parent gap overflow>
<Group small gap>
<InputField name="teamId" state={state.config} readOnly={state.readonly || edit} disabled={state.disabled} required tiny fill>
{translate('administration_teams_team_id')}
</InputField>
<InputField name="teamName" state={state.config} readOnly={state.readonly} disabled={state.disabled} tiny fill>
{translate('administration_teams_team_name')}
</InputField>
<Textarea name="description" state={state.config} readOnly={state.readonly} disabled={state.disabled} tiny fill>
{translate('administration_teams_team_description')}
</Textarea>
</Group>
{!serverConfigResource.resource.distributed && <Permissions state={state} />}
<TeamMetaParameters state={state} />
</ColoredContainer>
</Form>,
<ColoredContainer parent gap overflow>
<Group small gap>
<InputField name="teamId" state={state.config} readOnly={state.readonly || edit} disabled={state.disabled} required tiny fill>
{translate('administration_teams_team_id')}
</InputField>
<InputField name="teamName" state={state.config} readOnly={state.readonly} disabled={state.disabled} tiny fill>
{translate('administration_teams_team_name')}
</InputField>
<Textarea name="description" state={state.config} readOnly={state.readonly} disabled={state.disabled} tiny fill>
{translate('administration_teams_team_description')}
</Textarea>
</Group>
{!serverConfigResource.resource.distributed && <Permissions state={state} />}
<TeamMetaParameters state={state} />
</ColoredContainer>
);
});
@@ -10,13 +10,14 @@ import { useEffect } from 'react';
import styled, { css } from 'reshadow';
import type { TeamInfo } from '@cloudbeaver/core-authentication';
import { IconOrImage, Loader, Placeholder, useExecutor, useObjectRef, useStyles, useTranslate } from '@cloudbeaver/core-blocks';
import { Form, IconOrImage, Loader, Placeholder, useExecutor, useForm, useObjectRef, useStyles, useTranslate } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { BASE_TAB_STYLES, TabList, TabPanelList, TabsState, UNDERLINE_TAB_BIG_STYLES, UNDERLINE_TAB_STYLES } from '@cloudbeaver/core-ui';
import { teamContext } from './Contexts/teamContext';
import type { ITeamFormState } from './ITeamFormProps';
import { TeamFormService } from './TeamFormService';
import { ITeamFormActionsContext, TeamFormActionsContext } from './TeamFormActionsContext';
const tabsStyles = css`
TabList {
@@ -85,6 +86,14 @@ const formStyles = css`
flex-direction: column;
overflow: auto;
}
Form {
width: 100%;
height: 100%;
display: flex;
flex: 1;
flex-direction: column;
overflow: auto;
}
`;
interface Props {
@@ -100,6 +109,12 @@ export const TeamForm = observer<Props>(function TeamForm({ state, onCancel, onS
const style = [BASE_TAB_STYLES, tabsStyles, UNDERLINE_TAB_STYLES, UNDERLINE_TAB_BIG_STYLES];
const styles = useStyles(style, topBarStyles, formStyles);
const service = useService(TeamFormService);
const form = useForm({
onSubmit: state.save,
});
const actions = useObjectRef<ITeamFormActionsContext>({
save: async () => form.submit(),
});
useExecutor({
executor: state.submittingTask,
@@ -121,7 +136,8 @@ export const TeamForm = observer<Props>(function TeamForm({ state, onCancel, onS
}, []);
return styled(styles)(
<TabsState container={service.tabsContainer} localState={state.partsState} state={state} onCancel={onCancel}>
<Form context={form}>
<TabsState container={service.tabsContainer} localState={state.partsState} state={state} onCancel={onCancel}>
<box className={className}>
<team-top-bar>
<team-top-bar-tabs>
@@ -137,7 +153,9 @@ export const TeamForm = observer<Props>(function TeamForm({ state, onCancel, onS
</team-top-bar-tabs>
<team-top-bar-actions>
<Loader suspense inline hideMessage hideException>
<Placeholder container={service.actionsContainer} state={state} onCancel={onCancel} />
<TeamFormActionsContext.Provider value={actions}>
<Placeholder container={service.actionsContainer} state={state} onCancel={onCancel} />
</TeamFormActionsContext.Provider>
</Loader>
</team-top-bar-actions>
</team-top-bar>
@@ -145,6 +163,7 @@ export const TeamForm = observer<Props>(function TeamForm({ state, onCancel, onS
<TabPanelList style={style} />
</content-box>
</box>
</TabsState>,
</TabsState>
</Form>,
);
});
@@ -0,0 +1,8 @@
import { createContext } from 'react';
import type { ITeamFormState } from './ITeamFormProps';
export interface ITeamFormActionsContext {
save: ITeamFormState['save'];
}
export const TeamFormActionsContext = createContext<ITeamFormActionsContext | null>(null);
@@ -6,13 +6,20 @@
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
import { useContext } from 'react';
import { Button, PlaceholderComponent, useTranslate } from '@cloudbeaver/core-blocks';
import type { ITeamFormProps } from './ITeamFormProps';
import { TeamFormActionsContext } from './TeamFormActionsContext';
export const TeamFormBaseActions: PlaceholderComponent<ITeamFormProps> = observer(function TeamFormBaseActions({ state, onCancel }) {
const translate = useTranslate();
const actions = useContext(TeamFormActionsContext);
if (!actions) {
throw new Error('TeamFormActionsContext not provided');
}
return (
<>
@@ -21,7 +28,7 @@ export const TeamFormBaseActions: PlaceholderComponent<ITeamFormProps> = observe
{translate('ui_processing_cancel')}
</Button>
)}
<Button type="button" disabled={state.disabled || state.readonly} mod={['unelevated']} loader onClick={state.save}>
<Button type="button" disabled={state.disabled || state.readonly} mod={['unelevated']} loader onClick={actions.save}>
{translate(state.mode === 'edit' ? 'ui_processing_save' : 'ui_processing_finish')}
</Button>
</>
@@ -46,6 +46,7 @@ export const AuthProviderForm = observer<Props>(function AuthProviderForm({ prov
parameter.user && (
<InputField
key={`${provider.id}${configuration?.id ?? ''}${parameter.id}`}
required={provider.required}
title={parameter.description}
type={parameter.encryption === 'none' ? 'text' : 'password'}
name={parameter.id}
@@ -1,79 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2023 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 { computed, makeObservable, observable } from 'mobx';
import { ConnectionsManagerService, DBDriver, DBDriverResource } from '@cloudbeaver/core-connections';
import { IInitializableController, injectable } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
import { ProjectsService } from '@cloudbeaver/core-projects';
import { CachedMapAllKey } from '@cloudbeaver/core-resource';
import { isArraysEqual } from '@cloudbeaver/core-utils';
import { PublicConnectionFormService } from '@cloudbeaver/plugin-connections';
@injectable()
export class CustomConnectionController implements IInitializableController {
isLoading: boolean;
onClose!: () => void;
get drivers(): DBDriver[] {
return this.dbDriverResource.enabledDrivers;
}
constructor(
private readonly dbDriverResource: DBDriverResource,
private readonly notificationService: NotificationService,
private readonly projectsService: ProjectsService,
private readonly publicConnectionFormService: PublicConnectionFormService,
private readonly connectionsManagerService: ConnectionsManagerService,
) {
this.isLoading = true;
makeObservable(this, {
isLoading: observable,
drivers: computed<DBDriver[]>({
equals: isArraysEqual,
}),
});
}
init(onClose: () => void): void {
this.loadDBDrivers();
this.onClose = onClose;
}
onDriverSelect = async (driverId: string) => {
await this.projectsService.load();
const projects = this.connectionsManagerService.createConnectionProjects;
if (projects.length === 0) {
this.notificationService.logError({ title: 'core_projects_no_default_project' });
return;
}
const state = await this.publicConnectionFormService.open(
projects[0].id,
{ driverId },
this.drivers.map(driver => driver.id),
);
if (state) {
this.onClose();
}
};
private async loadDBDrivers() {
try {
await this.dbDriverResource.load(CachedMapAllKey);
} catch (exception: any) {
this.notificationService.logException(exception, "Can't load database drivers");
} finally {
this.isLoading = false;
}
}
}
@@ -1,30 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2023 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 { useTranslate } from '@cloudbeaver/core-blocks';
import { useController } from '@cloudbeaver/core-di';
import type { DialogComponent } from '@cloudbeaver/core-dialogs';
import { CustomConnectionController } from './CustomConnectionController';
import { DriverSelectorDialog } from './DriverSelectorDialog/DriverSelectorDialog';
export const CustomConnectionDialog: DialogComponent<null, null> = observer(function CustomConnectionDialog({ rejectDialog }) {
const controller = useController(CustomConnectionController, rejectDialog);
const translate = useTranslate();
return (
<DriverSelectorDialog
title={translate('basicConnection_connectionDialog_newConnection')}
drivers={controller.drivers}
isLoading={controller.isLoading}
onSelect={controller.onDriverSelect}
onClose={rejectDialog}
/>
);
});
@@ -1,36 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2023 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 { CommonDialogBody, CommonDialogHeader, CommonDialogWrapper, Loader, s, useS } from '@cloudbeaver/core-blocks';
import type { IDriver } from './Driver';
import { DriverSelector } from './DriverSelector';
import styles from './DriverSelectorDialog.m.css';
interface IProps {
title: string;
drivers: IDriver[];
isLoading: boolean;
onSelect: (driverId: string) => void;
onClose: () => void;
}
export const DriverSelectorDialog = observer<IProps>(function DriverSelectorDialog({ title, drivers, isLoading, onSelect, onClose }) {
const style = useS(styles);
return (
<CommonDialogWrapper size="large" fixedSize>
<CommonDialogHeader title={title} onReject={onClose} />
<CommonDialogBody noBodyPadding noOverflow>
{isLoading && <Loader />}
{!isLoading && <DriverSelector className={s(style, { driverSelector: true })} drivers={drivers} onSelect={onSelect} />}
</CommonDialogBody>
</CommonDialogWrapper>
);
});
@@ -14,8 +14,8 @@ import { ActionService, MenuService } from '@cloudbeaver/core-view';
import { MENU_CONNECTIONS } from '@cloudbeaver/plugin-connections';
import { ACTION_CONNECTION_CUSTOM } from './Actions/ACTION_CONNECTION_CUSTOM';
import { CustomConnectionDialog } from './CustomConnection/CustomConnectionDialog';
import { CustomConnectionSettingsService } from './CustomConnectionSettingsService';
import { DriverSelectorDialog } from './DriverSelector/DriverSelectorDialog';
@injectable()
export class CustomConnectionPluginBootstrap extends Bootstrap {
@@ -67,6 +67,6 @@ export class CustomConnectionPluginBootstrap extends Bootstrap {
load(): void | Promise<void> {}
private async openConnectionsDialog() {
await this.commonDialogService.open(CustomConnectionDialog, null);
await this.commonDialogService.open(DriverSelectorDialog, null);
}
}
@@ -0,0 +1,40 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2023 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 { CommonDialogBody, CommonDialogHeader, CommonDialogWrapper, s, useResource, useS, useTranslate } from '@cloudbeaver/core-blocks';
import { DBDriverResource } from '@cloudbeaver/core-connections';
import type { DialogComponent } from '@cloudbeaver/core-dialogs';
import { ProjectInfoResource } from '@cloudbeaver/core-projects';
import { CachedMapAllKey } from '@cloudbeaver/core-resource';
import { DriverSelector } from './DriverSelector';
import styles from './DriverSelectorDialog.m.css';
import { useDriverSelectorDialog } from './useDriverSelectorDialog';
export const DriverSelectorDialog: DialogComponent<null> = observer(function DriverSelectorDialog({ rejectDialog }) {
const translate = useTranslate();
const style = useS(styles);
useResource(DriverSelectorDialog, ProjectInfoResource, CachedMapAllKey, { forceSuspense: true });
const dbDriverResource = useResource(DriverSelectorDialog, DBDriverResource, CachedMapAllKey);
const enabledDrivers = dbDriverResource.resource.enabledDrivers;
const dialog = useDriverSelectorDialog(
enabledDrivers.map(driver => driver.id),
rejectDialog,
);
return (
<CommonDialogWrapper size="large" fixedSize>
<CommonDialogHeader title={translate('basicConnection_connectionDialog_newConnection')} />
<CommonDialogBody noBodyPadding noOverflow>
<DriverSelector className={s(style, { driverSelector: true })} drivers={enabledDrivers} onSelect={dialog.select} />
</CommonDialogBody>
</CommonDialogWrapper>
);
});
@@ -0,0 +1,47 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2023 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 { action } from 'mobx';
import { useObservableRef } from '@cloudbeaver/core-blocks';
import { ConnectionsManagerService } from '@cloudbeaver/core-connections';
import { useService } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
import { PublicConnectionFormService } from '@cloudbeaver/plugin-connections';
interface State {
select(driverId: string): Promise<void>;
}
export function useDriverSelectorDialog(drivers: string[], onSelect?: () => void) {
const notificationService = useService(NotificationService);
const connectionsManagerService = useService(ConnectionsManagerService);
const publicConnectionFormService = useService(PublicConnectionFormService);
const state: State = useObservableRef(
() => ({
async select(driverId: string) {
const projects = this.connectionsManagerService.createConnectionProjects;
if (projects.length === 0) {
this.notificationService.logError({ title: 'core_projects_no_default_project' });
return;
}
const state = await this.publicConnectionFormService.open(projects[0].id, { driverId }, this.drivers);
if (state) {
onSelect?.();
}
},
}),
{ select: action.bound },
{ drivers, notificationService, connectionsManagerService, publicConnectionFormService },
);
return state;
}
@@ -0,0 +1,9 @@
import { createContext } from 'react';
import type { IConnectionFormState } from './IConnectionFormProps';
export interface IConnectionFormActionsContext {
save: IConnectionFormState['save'];
test: IConnectionFormState['test'];
}
export const ConnectionFormActionsContext = createContext<IConnectionFormActionsContext | null>(null);
@@ -0,0 +1,7 @@
.form {
overflow: auto;
display: flex;
flex-direction: column;
flex: 1;
height: 100%;
}
@@ -9,7 +9,7 @@ import { observer } from 'mobx-react-lite';
import { useEffect } from 'react';
import styled, { css } from 'reshadow';
import { ExceptionMessage, Loader, Placeholder, StatusMessage, useExecutor, useObjectRef, useStyles } from '@cloudbeaver/core-blocks';
import { ExceptionMessage, Form, Loader, Placeholder, StatusMessage, useExecutor, useForm, useObjectRef, useStyles } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { ENotificationType } from '@cloudbeaver/core-events';
import type { ConnectionConfig } from '@cloudbeaver/core-sdk';
@@ -18,6 +18,8 @@ import { BASE_TAB_STYLES, TabList, TabPanelList, TabsState, UNDERLINE_TAB_BIG_ST
import { ConnectionFormService } from './ConnectionFormService';
import { connectionConfigContext } from './Contexts/connectionConfigContext';
import type { IConnectionFormState } from './IConnectionFormProps';
import connectionFormStyles from './ConnectionForm.m.css';
import { ConnectionFormActionsContext, IConnectionFormActionsContext } from './ConnectFormActionsContext';
const tabsStyles = css`
TabList {
@@ -106,6 +108,21 @@ export const ConnectionForm = observer<ConnectionFormProps>(function ConnectionF
const style = [BASE_TAB_STYLES, tabsStyles, UNDERLINE_TAB_STYLES, UNDERLINE_TAB_BIG_STYLES];
const styles = useStyles(style, topBarStyles, formStyles);
const service = useService(ConnectionFormService);
const form = useForm({
onSubmit: event => {
if (event?.type === 'test') {
state.test();
} else {
state.save();
}
},
});
const actionsContext = useObjectRef<IConnectionFormActionsContext>(() => ({
save: async () => form.submit(new SubmitEvent('submit')),
test: async () => form.submit(new SubmitEvent('test')),
}));
useExecutor({
executor: state.submittingTask,
@@ -139,25 +156,29 @@ export const ConnectionForm = observer<ConnectionFormProps>(function ConnectionF
}
return styled(styles)(
<TabsState container={service.tabsContainer} localState={state.partsState} state={state} onCancel={onCancel}>
<box className={className}>
<connection-top-bar>
<connection-top-bar-tabs>
<connection-status-message>
<StatusMessage type={ENotificationType.Info} message={state.statusMessage} />
</connection-status-message>
<TabList style={style} disabled={state.disabled} />
</connection-top-bar-tabs>
<connection-top-bar-actions>
<Loader suspense inline hideMessage hideException>
<Placeholder container={service.actionsContainer} state={state} onCancel={onCancel} />
</Loader>
</connection-top-bar-actions>
</connection-top-bar>
<content-box>
<TabPanelList style={style} />
</content-box>
</box>
</TabsState>,
<Form context={form} className={connectionFormStyles.form}>
<TabsState container={service.tabsContainer} localState={state.partsState} state={state} onCancel={onCancel}>
<box className={className}>
<connection-top-bar>
<connection-top-bar-tabs>
<connection-status-message>
<StatusMessage type={ENotificationType.Info} message={state.statusMessage} />
</connection-status-message>
<TabList style={style} disabled={state.disabled} />
</connection-top-bar-tabs>
<connection-top-bar-actions>
<Loader suspense inline hideMessage hideException>
<ConnectionFormActionsContext.Provider value={actionsContext}>
<Placeholder container={service.actionsContainer} state={state} onCancel={onCancel} />
</ConnectionFormActionsContext.Provider>
</Loader>
</connection-top-bar-actions>
</connection-top-bar>
<content-box>
<TabPanelList style={style} />
</content-box>
</box>
</TabsState>
</Form>,
);
});
@@ -6,18 +6,26 @@
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
import { useContext } from 'react';
import { AUTH_PROVIDER_LOCAL_ID } from '@cloudbeaver/core-authentication';
import { Button, getComputed, PlaceholderComponent, useResource, useTranslate } from '@cloudbeaver/core-blocks';
import { DatabaseAuthModelsResource, DBDriverResource } from '@cloudbeaver/core-connections';
import { useAuthenticationAction } from '@cloudbeaver/core-ui';
import { ConnectionFormActionsContext } from './ConnectFormActionsContext';
import type { IConnectionFormProps } from './IConnectionFormProps';
export const ConnectionFormBaseActions: PlaceholderComponent<IConnectionFormProps> = observer(function ConnectionFormBaseActions({
state,
onCancel,
}) {
const actions = useContext(ConnectionFormActionsContext);
if (!actions) {
throw new Error('ConnectionFormActionsContext not provided');
}
const translate = useTranslate();
const driverMap = useResource(ConnectionFormBaseActions, DBDriverResource, state.config.driverId || null);
@@ -40,10 +48,10 @@ export const ConnectionFormBaseActions: PlaceholderComponent<IConnectionFormProp
{translate('ui_processing_cancel')}
</Button>
)}
<Button title={translate('connections_connection_test_tooltip')} type="button" disabled={state.disabled || !authorized} mod={['outlined']} loader onClick={state.test}>
<Button type="button" disabled={state.disabled || !authorized} mod={['outlined']} loader onClick={actions.test}>
{translate('connections_connection_test')}
</Button>
<Button type="button" disabled={state.disabled || state.readonly} mod={['unelevated']} loader onClick={state.save}>
<Button type="button" disabled={state.disabled || state.readonly} mod={['unelevated']} loader onClick={actions.save}>
{translate(state.mode === 'edit' ? 'ui_processing_save' : 'ui_processing_finish')}
</Button>
</>
@@ -95,7 +95,7 @@ export const SSH: TabContainerPanelComponent<Props> = observer(function SSH({ st
<Form>
<ColoredContainer parent>
<Group form gap keepSize large>
<Switch name="enabled" state={handlerState} mod={['primary']} disabled={disabled || readonly}>
<Switch id="ssh-enable-switch" name="enabled" state={handlerState} mod={['primary']} disabled={disabled || readonly}>
{translate('connections_network_handler_ssh_tunnel_enable')}
</Switch>
<Combobox
@@ -45,7 +45,7 @@ export const SSHKeyUploader = observer<Props>(function SSHKeyUploader({ state, s
disabled={disabled}
readOnly={readonly}
description={saved ? translate('ui_processing_saved') : undefined}
required={state.savePassword}
required={state.savePassword && !saved}
medium
>
{translate('connections_network_handler_ssh_tunnel_private_key')}
@@ -8,16 +8,15 @@
import { observer } from 'mobx-react-lite';
import { SSH_TUNNEL_ID } from '@cloudbeaver/core-connections';
import { TabContainerPanelComponent, useTab } from '@cloudbeaver/core-ui';
import type { TabContainerPanelComponent } from '@cloudbeaver/core-ui';
import type { IConnectionFormProps } from '../IConnectionFormProps';
import { SSH } from './SSH';
export const SSHPanel: TabContainerPanelComponent<IConnectionFormProps> = observer(function SSHPanel(props) {
const state = props.state.config.networkHandlersConfig?.find(state => state.id === SSH_TUNNEL_ID);
const tab = useTab(props.tabId);
if (!state || !tab.selected) {
if (!state) {
return null;
}
@@ -61,7 +61,7 @@ export const SSL: TabContainerPanelComponent<Props> = observer(function SSL({ st
<Form>
<ColoredContainer parent>
<Group gap form large vertical>
<Switch name="enabled" state={handlerState} description={handler.description} mod={['primary']} disabled={disabled || readonly}>
<Switch id="ssl-enable-switch" name="enabled" state={handlerState} description={handler.description} mod={['primary']} disabled={disabled || readonly}>
{translate('connections_public_connection_ssl_enable')}
</Switch>
{isUncategorizedExists && (
@@ -10,7 +10,7 @@ import { observer } from 'mobx-react-lite';
import { useResource } from '@cloudbeaver/core-blocks';
import { DBDriverResource, NetworkHandlerResource } from '@cloudbeaver/core-connections';
import { CachedMapAllKey } from '@cloudbeaver/core-resource';
import { TabContainerTabComponent, useTab } from '@cloudbeaver/core-ui';
import type { TabContainerTabComponent } from '@cloudbeaver/core-ui';
import type { IConnectionFormProps } from '../IConnectionFormProps';
import { getSSLDefaultConfig } from './getSSLDefaultConfig';
@@ -18,7 +18,6 @@ import { getSSLDriverHandler } from './getSSLDriverHandler';
import { SSL } from './SSL';
export const SSLPanel: TabContainerTabComponent<IConnectionFormProps> = observer(function SSLPanel(props) {
const tab = useTab(props.tabId);
const networkHandlerResource = useResource(SSLPanel, NetworkHandlerResource, CachedMapAllKey);
const dbDriverResource = useResource(SSLPanel, DBDriverResource, props.state.config.driverId ?? null);
@@ -30,7 +29,7 @@ export const SSLPanel: TabContainerTabComponent<IConnectionFormProps> = observer
const handlerState = props.state.config.networkHandlersConfig?.find(h => h.id === handler?.id);
if (!handler || !handlerState || !tab.selected) {
if (!handler || !handlerState) {
return null;
}
@@ -7,42 +7,46 @@
*/
import { observer } from 'mobx-react-lite';
import { Button, Group, GroupItem, GroupTitle, InputField, useTranslate } from '@cloudbeaver/core-blocks';
import { Button, Form, Group, GroupItem, GroupTitle, InputField, useForm, useTranslate } from '@cloudbeaver/core-blocks';
import { useChangePassword } from './useChangePassword';
export const ChangePassword = observer(function ChangePassword() {
const translate = useTranslate();
const state = useChangePassword();
const disabled = state.submitting;
const form = useForm({
onSubmit: state.changePassword,
});
return (
<Group form gap>
<GroupTitle>{translate('plugin_user_profile_authentication_change_password')}</GroupTitle>
<InputField type="password" name="oldPassword" state={state.config} disabled={disabled} mapValue={value => value.trim()} small required>
{translate('plugin_user_profile_authentication_change_password_current_password')}
</InputField>
<InputField
type="password"
name="password"
autoComplete="new-password"
state={state.config}
disabled={disabled}
mapValue={value => value.trim()}
small
required
>
{translate('plugin_user_profile_authentication_change_password_new_password')}
</InputField>
<InputField type="password" name="repeatedPassword" state={state.config} disabled={disabled} mapValue={value => value.trim()} small required>
{translate('plugin_user_profile_authentication_change_password_repeat_password')}
</InputField>
<GroupItem>
<Button disabled={disabled || !state.formFilled} loading={state.submitting} type="button" mod={['unelevated']} onClick={state.changePassword}>
{translate('plugin_user_profile_authentication_change_password_submit_label')}
</Button>
</GroupItem>
</Group>
<Form context={form}>
<Group form gap>
<GroupTitle>{translate('plugin_user_profile_authentication_change_password')}</GroupTitle>
<InputField type="password" name="oldPassword" state={state.config} disabled={disabled} mapValue={value => value.trim()} small required>
{translate('plugin_user_profile_authentication_change_password_current_password')}
</InputField>
<InputField
type="password"
name="password"
autoComplete="new-password"
state={state.config}
disabled={disabled}
mapValue={value => value.trim()}
small
required
>
{translate('plugin_user_profile_authentication_change_password_new_password')}
</InputField>
<InputField type="password" name="repeatedPassword" state={state.config} disabled={disabled} mapValue={value => value.trim()} small required>
{translate('plugin_user_profile_authentication_change_password_repeat_password')}
</InputField>
<GroupItem>
<Button disabled={disabled || !state.formFilled} loading={state.submitting} type="button" mod={['unelevated']} onClick={() => form.submit()}>
{translate('plugin_user_profile_authentication_change_password_submit_label')}
</Button>
</GroupItem>
</Group>
</Form>
);
});
+1 -1
View File
@@ -5,7 +5,7 @@
"src/**/*.scss",
"public/**/*"
],
"version": "23.3.0",
"version": "23.3.1",
"description": "CloudBeaver Community",
"license": "Apache-2.0",
"main": "dist/index.js",