From 5a902385fc9a5a8e967402f8427c76f9bff96e29 Mon Sep 17 00:00:00 2001 From: sergeyteleshev Date: Tue, 30 Jun 2026 16:54:13 +0200 Subject: [PATCH 01/10] dbeaver/pro#9522 integrate profile selector to connection form --- .../@dbeaver/ui-kit/src/Select/Select.css | 7 ++- .../@dbeaver/ui-kit/src/Select/Select.tsx | 12 ++++ .../ui-kit/src/Select/SelectField.tsx | 55 +++++++++++++++---- .../core-app/public/icons/preload/icons.svg | 5 ++ .../core-blocks/src/FormControls/Select.css | 4 +- .../core-blocks/src/FormControls/Select.tsx | 3 + webapp/packages/core-ui/src/Form/FormPart.ts | 3 + webapp/packages/core-ui/src/Form/IFormPart.ts | 1 + .../src/SSH/ConnectionFormSSHPart.ts | 26 ++++++++- .../src/SSH/SSH.tsx | 4 +- .../src/index.ts | 1 + 11 files changed, 105 insertions(+), 16 deletions(-) diff --git a/webapp/common-react/@dbeaver/ui-kit/src/Select/Select.css b/webapp/common-react/@dbeaver/ui-kit/src/Select/Select.css index 3bed93e9941..dbff08ea8c0 100644 --- a/webapp/common-react/@dbeaver/ui-kit/src/Select/Select.css +++ b/webapp/common-react/@dbeaver/ui-kit/src/Select/Select.css @@ -83,8 +83,13 @@ } } + .dbv-kit-select__group { + border-top: 1px solid var(--dbv-kit-select-border-color); + } + .dbv-kit-select__item { - display: block; + display: flex; + align-items: center; width: 100%; font-weight: var(--dbv-kit-select-font-weight); font-size: var(--dbv-kit-select-font-size); diff --git a/webapp/common-react/@dbeaver/ui-kit/src/Select/Select.tsx b/webapp/common-react/@dbeaver/ui-kit/src/Select/Select.tsx index f5967c7ba1b..991e94e9495 100644 --- a/webapp/common-react/@dbeaver/ui-kit/src/Select/Select.tsx +++ b/webapp/common-react/@dbeaver/ui-kit/src/Select/Select.tsx @@ -14,6 +14,7 @@ import { SelectLabel as AriaSelectLabel, SelectValue as AriaSelectValue, SelectArrow as AriaSelectArrow, + SelectGroup as AriaSelectGroup, type SelectProviderProps, type SelectProps, type SelectLabelProps, @@ -21,6 +22,7 @@ import { type SelectItemProps, type SelectValueProps, type SelectArrowProps, + type SelectGroupProps, useSelectContext, useSelectStore, } from '@ariakit/react'; @@ -69,6 +71,14 @@ export function SelectLabel({ children, className, ...props }: SelectLabelProps) ); } +export function SelectGroup({ children, className, ...props }: SelectGroupProps) { + return ( + + {children} + + ); +} + export { useSelectContext, useSelectStore, @@ -79,11 +89,13 @@ export { type SelectItemProps, type SelectValueProps, type SelectArrowProps, + type SelectGroupProps, }; Select.Provider = SelectProvider; Select.Popover = SelectPopover; Select.Item = SelectItem; Select.Label = SelectLabel; +Select.Group = SelectGroup; Select.Value = AriaSelectValue; Select.Arrow = AriaSelectArrow; diff --git a/webapp/common-react/@dbeaver/ui-kit/src/Select/SelectField.tsx b/webapp/common-react/@dbeaver/ui-kit/src/Select/SelectField.tsx index 40a12bda79a..90e9d758473 100644 --- a/webapp/common-react/@dbeaver/ui-kit/src/Select/SelectField.tsx +++ b/webapp/common-react/@dbeaver/ui-kit/src/Select/SelectField.tsx @@ -8,7 +8,7 @@ import { useState } from 'react'; import clsx from 'clsx'; -import { SelectProvider, Select, SelectPopover, SelectItem, SelectLabel, type SelectProviderProps } from './Select.js'; +import { SelectProvider, Select, SelectPopover, SelectItem, SelectLabel, SelectGroup, type SelectProviderProps } from './Select.js'; import './SelectField.css'; export interface SelectItem { @@ -47,6 +47,12 @@ export interface SelectFieldProps> { */ itemDisabled?: PropertyGetter; + /** + * When true for an item, it acts as a group boundary — items are split into + * SelectGroup chunks separated by a CSS border. Separator items are not rendered. + */ + isSeparator?: PropertyGetter; + value?: T; onChange?: (value: T) => void; @@ -94,6 +100,18 @@ function getValueByPath(item: Item, getter: PropertyGetter(items: ItemType[], isSeparator: PropertyGetter): ItemType[][] { + const groups: ItemType[][] = [[]]; + for (const item of items) { + if (isSeparator(item)) { + groups.push([]); + } else { + groups[groups.length - 1]!.push(item); + } + } + return groups.filter(g => g.length > 0); +} + export function SelectField>({ items, value, @@ -102,6 +120,7 @@ export function SelectField>({ itemValueSerialized, itemRender, itemDisabled, + isSeparator, label, noItemsPlaceholder = 'No items', description, @@ -159,6 +178,30 @@ export function SelectField>({ const selectedItem = currentValue !== undefined ? items.find(item => getItemValueSerialized(item) === currentValueSerialized) : undefined; const displayValue = selectedRender ? selectedRender(currentValue, selectedItem) : selectedItem ? renderItem(selectedItem) : ''; + function renderSelectItem(item: ItemType) { + return ( + + {renderItem(item)} + + ); + } + + function renderItems(): React.ReactNode { + if (items.length === 0) { + return
{noItemsPlaceholder}
; + } + + if (isSeparator) { + return splitIntoGroups(items, isSeparator).map(group => ( + + {group.map(renderSelectItem)} + + )); + } + + return items.map(renderSelectItem); + } + return (
handleChange(val)} store={store}> @@ -171,15 +214,7 @@ export function SelectField>({ {description && {description}} - {items.length === 0 ? ( -
{noItemsPlaceholder}
- ) : ( - items.map(item => ( - - {renderItem(item)} - - )) - )} + {renderItems()}
diff --git a/webapp/packages/core-app/public/icons/preload/icons.svg b/webapp/packages/core-app/public/icons/preload/icons.svg index e51b3db417b..6110a8789f7 100644 --- a/webapp/packages/core-app/public/icons/preload/icons.svg +++ b/webapp/packages/core-app/public/icons/preload/icons.svg @@ -576,4 +576,9 @@ + + + + + \ No newline at end of file diff --git a/webapp/packages/core-blocks/src/FormControls/Select.css b/webapp/packages/core-blocks/src/FormControls/Select.css index 4165351a0ab..5e16aa9fe8b 100644 --- a/webapp/packages/core-blocks/src/FormControls/Select.css +++ b/webapp/packages/core-blocks/src/FormControls/Select.css @@ -75,10 +75,11 @@ } .select__item { - display: inline-flex; + display: flex; gap: 8px; letter-spacing: normal; align-items: center; + min-width: 0; } .select__item--placeholder { @@ -91,6 +92,7 @@ } .select__input-icon { + flex-shrink: 0; width: 16px; & .select__icon { diff --git a/webapp/packages/core-blocks/src/FormControls/Select.tsx b/webapp/packages/core-blocks/src/FormControls/Select.tsx index 867b3dcfd04..c80ab268c93 100644 --- a/webapp/packages/core-blocks/src/FormControls/Select.tsx +++ b/webapp/packages/core-blocks/src/FormControls/Select.tsx @@ -36,6 +36,7 @@ export type SelectBaseProps = Omit< titleSelector?: (item: TValue) => string | undefined; iconSelector?: (item: TValue) => string | React.ReactElement | undefined; isDisabled?: (item: TValue) => boolean; + isSeparator?: (item: TValue) => boolean; onSwitch?: (state: boolean) => void; inline?: boolean; children?: string; @@ -84,6 +85,7 @@ export const Select: SelectType = observer(function Select({ iconSelector, titleSelector, isDisabled, + isSeparator, onSelect, onSwitch, ...rest @@ -191,6 +193,7 @@ export const Select: SelectType = observer(function Select({ itemValueSerialized={serializedKeySelector} itemRender={itemRender} itemDisabled={itemDisabled} + isSeparator={isSeparator} name={name} disabled={disabled || readOnly} noItemsPlaceholder={translate('combobox_no_results_placeholder')} diff --git a/webapp/packages/core-ui/src/Form/FormPart.ts b/webapp/packages/core-ui/src/Form/FormPart.ts index 2169687af28..e3223f9360e 100644 --- a/webapp/packages/core-ui/src/Form/FormPart.ts +++ b/webapp/packages/core-ui/src/Form/FormPart.ts @@ -19,6 +19,7 @@ export abstract class FormPart impl state: TPartState; initialState: TPartState; isSaving: boolean; + isReadOnly: boolean; exception: Error | null; promise: Promise | null; @@ -35,6 +36,7 @@ export abstract class FormPart impl this.initialState = initialState; this.state = toJS(this.initialState); this.isSaving = false; + this.isReadOnly = false; this.exception = null; this.promise = null; @@ -54,6 +56,7 @@ export abstract class FormPart impl exception: observable.ref, promise: observable.ref, isSaving: observable.ref, + isReadOnly: observable.ref, loaded: observable, loading: observable, setInitialState: action, diff --git a/webapp/packages/core-ui/src/Form/IFormPart.ts b/webapp/packages/core-ui/src/Form/IFormPart.ts index dc70be0b760..af2cf62a7aa 100644 --- a/webapp/packages/core-ui/src/Form/IFormPart.ts +++ b/webapp/packages/core-ui/src/Form/IFormPart.ts @@ -12,6 +12,7 @@ export interface IFormPart extends ILoadableState { readonly initialState: TState; isSaving: boolean; readonly isDisabled: boolean; + isReadOnly: boolean; readonly isChanged: boolean; diff --git a/webapp/packages/plugin-connection-network-handlers/src/SSH/ConnectionFormSSHPart.ts b/webapp/packages/plugin-connection-network-handlers/src/SSH/ConnectionFormSSHPart.ts index 3272ab2b8a1..e0220eeb9e0 100644 --- a/webapp/packages/plugin-connection-network-handlers/src/SSH/ConnectionFormSSHPart.ts +++ b/webapp/packages/plugin-connection-network-handlers/src/SSH/ConnectionFormSSHPart.ts @@ -8,6 +8,7 @@ import { FormPart, formValidationContext, type IFormState } from '@cloudbeaver/core-ui'; import type { IExecutionContextProvider } from '@cloudbeaver/core-executor'; +import { action, makeObservable, observable, toJS } from 'mobx'; import { DriverConfigurationType, type NetworkHandlerConfigInput, type NetworkHandlerDescriptor } from '@cloudbeaver/core-sdk'; import { ConnectionInfoNetworkHandlersResource } from '@cloudbeaver/core-connections'; import { @@ -19,12 +20,13 @@ import { validateSSHConfig, type INetworkHandlerConfig, } from '@cloudbeaver/plugin-network-handlers'; -import { toJS } from 'mobx'; import type { ConnectionFormOptionsPart, IConnectionFormState } from '@cloudbeaver/plugin-connections'; const getDefaultState = (): INetworkHandlerConfig => SSH_DEFAULT_HANDLER_CONFIG() as INetworkHandlerConfig; export class ConnectionFormSSHPart extends FormPart { + networkProfileConfig: INetworkHandlerConfig | null; + constructor( formState: IFormState, private readonly networkHandlerResource: NetworkHandlerResource, @@ -32,12 +34,32 @@ export class ConnectionFormSSHPart extends FormPart = observer(function SSH({ fo Date: Wed, 1 Jul 2026 08:07:10 +0200 Subject: [PATCH 02/10] dbeaver/pro#9522 handles edge cases --- webapp/packages/core-app/public/icons/preload/icons.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/packages/core-app/public/icons/preload/icons.svg b/webapp/packages/core-app/public/icons/preload/icons.svg index 6110a8789f7..23efb21e38f 100644 --- a/webapp/packages/core-app/public/icons/preload/icons.svg +++ b/webapp/packages/core-app/public/icons/preload/icons.svg @@ -581,4 +581,4 @@ - \ No newline at end of file + From 602fa495d4c28fe86384baa8583fc6703f419d53 Mon Sep 17 00:00:00 2001 From: sergeyteleshev Date: Wed, 1 Jul 2026 09:07:33 +0200 Subject: [PATCH 03/10] dbeaver/pro#9522 uses rest instead of default getter --- .../src/SSH/ConnectionFormSSHPart.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/packages/plugin-connection-network-handlers/src/SSH/ConnectionFormSSHPart.ts b/webapp/packages/plugin-connection-network-handlers/src/SSH/ConnectionFormSSHPart.ts index e0220eeb9e0..3368aeed0c7 100644 --- a/webapp/packages/plugin-connection-network-handlers/src/SSH/ConnectionFormSSHPart.ts +++ b/webapp/packages/plugin-connection-network-handlers/src/SSH/ConnectionFormSSHPart.ts @@ -51,7 +51,7 @@ export class ConnectionFormSSHPart extends FormPart Date: Wed, 1 Jul 2026 11:25:12 +0200 Subject: [PATCH 04/10] dbeaver/pro#9522 adds preprocessing to handle null/undefined differences arrived from backend for both: connection form network handlers & profile form network handlers --- .../src/CONNECTION_NETWORK_HANDLER_SCHEMA.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/webapp/packages/plugin-network-handlers/src/CONNECTION_NETWORK_HANDLER_SCHEMA.ts b/webapp/packages/plugin-network-handlers/src/CONNECTION_NETWORK_HANDLER_SCHEMA.ts index 7abdb9326b4..3c97a3dbaa4 100644 --- a/webapp/packages/plugin-network-handlers/src/CONNECTION_NETWORK_HANDLER_SCHEMA.ts +++ b/webapp/packages/plugin-network-handlers/src/CONNECTION_NETWORK_HANDLER_SCHEMA.ts @@ -9,16 +9,18 @@ import { NetworkHandlerAuthType } from '@cloudbeaver/core-sdk'; import { schema } from '@cloudbeaver/core-utils'; +const nullToUndefined = (val: T) => (val === null ? undefined : val); + export const NETWORK_HANDLER_SCHEMA = schema.object({ id: schema.string(), - authType: schema.nativeEnum(NetworkHandlerAuthType).optional(), - enabled: schema.boolean().optional(), - key: schema.string().optional(), - password: schema.string().optional(), - properties: schema.record(schema.string(), schema.any()).optional(), - savePassword: schema.boolean().optional(), - secureProperties: schema.record(schema.string(), schema.any()).optional(), - userName: schema.string().optional(), + authType: schema.preprocess(nullToUndefined, schema.nativeEnum(NetworkHandlerAuthType).optional()), + enabled: schema.preprocess(nullToUndefined, schema.boolean().optional()), + key: schema.preprocess(nullToUndefined, schema.string().optional()), + password: schema.preprocess(nullToUndefined, schema.string().optional()), + properties: schema.preprocess(nullToUndefined, schema.record(schema.string(), schema.any()).optional()), + savePassword: schema.preprocess(nullToUndefined, schema.boolean().optional()), + secureProperties: schema.preprocess(nullToUndefined, schema.record(schema.string(), schema.any()).optional()), + userName: schema.preprocess(nullToUndefined, schema.string().optional()), }); export type INetworkHandlerConfig = schema.infer; From c02175de8788167e4eb2a8c6b7156a99ab62950d Mon Sep 17 00:00:00 2001 From: sergeyteleshev Date: Wed, 1 Jul 2026 11:38:25 +0200 Subject: [PATCH 05/10] dbeaver/pro#9522 eslint fix --- .../@dbeaver/ui-kit/src/Select/SelectField.tsx | 18 ++++++++---------- .../common-react/@dbeaver/ui-kit/src/index.ts | 2 +- .../core-blocks/src/FormControls/Select.tsx | 4 ++-- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/webapp/common-react/@dbeaver/ui-kit/src/Select/SelectField.tsx b/webapp/common-react/@dbeaver/ui-kit/src/Select/SelectField.tsx index 90e9d758473..2023fb9ed60 100644 --- a/webapp/common-react/@dbeaver/ui-kit/src/Select/SelectField.tsx +++ b/webapp/common-react/@dbeaver/ui-kit/src/Select/SelectField.tsx @@ -11,7 +11,7 @@ import clsx from 'clsx'; import { SelectProvider, Select, SelectPopover, SelectItem, SelectLabel, SelectGroup, type SelectProviderProps } from './Select.js'; import './SelectField.css'; -export interface SelectItem { +export interface ISelectItem { value: T; label: string; disabled?: boolean; @@ -19,7 +19,7 @@ export interface SelectItem { type PropertyGetter = (item: ItemType) => ValueType; -export interface SelectFieldProps> { +export interface ISelectFieldProps> { /** Options array - can be SelectOption objects or arbitrary objects */ items: ItemType[]; @@ -112,7 +112,7 @@ function splitIntoGroups(items: ItemType[], isSeparator: PropertyGette return groups.filter(g => g.length > 0); } -export function SelectField>({ +export function SelectField>({ items, value, onChange, @@ -134,18 +134,18 @@ export function SelectField>({ autoFocusItemsOnShow, name, id, -}: SelectFieldProps) { +}: ISelectFieldProps) { const getItemValue = (item: ItemType): T => - getValueByPath(item, itemValue, i => ('value' in i ? (i as unknown as SelectItem).value : (i as unknown as T))); + getValueByPath(item, itemValue, i => ('value' in i ? (i as unknown as ISelectItem).value : (i as unknown as T))); const getItemValueSerialized = (item: ItemType): string => getValueByPath(getItemValue(item), itemValueSerialized, key => JSON.stringify(key)); const renderItem = (item: ItemType): React.ReactNode => - getValueByPath(item, itemRender, i => ('label' in i ? (i as unknown as SelectItem).label : String(i))); + getValueByPath(item, itemRender, i => ('label' in i ? (i as unknown as ISelectItem).label : String(i))); const isItemDisabled = (item: ItemType): boolean => - getValueByPath(item, itemDisabled, i => ('disabled' in i ? Boolean((i as unknown as SelectItem).disabled) : false)); + getValueByPath(item, itemDisabled, i => ('disabled' in i ? Boolean((i as unknown as ISelectItem).disabled) : false)); const [selectedValue, setSelectedValue] = useState(() => { if (value !== undefined) { @@ -193,9 +193,7 @@ export function SelectField>({ if (isSeparator) { return splitIntoGroups(items, isSeparator).map(group => ( - - {group.map(renderSelectItem)} - + {group.map(renderSelectItem)} )); } diff --git a/webapp/common-react/@dbeaver/ui-kit/src/index.ts b/webapp/common-react/@dbeaver/ui-kit/src/index.ts index 3e79b447b75..9af7a55de20 100644 --- a/webapp/common-react/@dbeaver/ui-kit/src/index.ts +++ b/webapp/common-react/@dbeaver/ui-kit/src/index.ts @@ -48,7 +48,7 @@ export { export * from './Combobox/Combobox.js'; export * from './Command/Command.js'; export { Popover, usePopoverStore, type PopoverStore } from './Popover/Popover.js'; -export { SelectField, type SelectFieldProps, type SelectItem } from './Select/SelectField.js'; +export { SelectField, type ISelectFieldProps, type ISelectItem } from './Select/SelectField.js'; export { Spinner, type SpinnerProps } from './Spinner/Spinner.js'; export { Switch, useSwitchContext, type SwitchProps } from './Switch/Switch.js'; export { SwitchProvider, type SwitchProviderProps } from './Switch/SwitchProvider.js'; diff --git a/webapp/packages/core-blocks/src/FormControls/Select.tsx b/webapp/packages/core-blocks/src/FormControls/Select.tsx index c80ab268c93..ad869fed000 100644 --- a/webapp/packages/core-blocks/src/FormControls/Select.tsx +++ b/webapp/packages/core-blocks/src/FormControls/Select.tsx @@ -57,12 +57,12 @@ type ObjectProps = SelectBaseProps(props: ControlledProps): React.JSX.Element; (props: ObjectProps): React.JSX.Element; } -export const Select: SelectType = observer(function Select({ +export const Select: ISelectType = observer(function Select({ value: controlledValue, defaultValue, name, From 40a9e72afb1805f3d6da1d89e42b6778c6a9a71b Mon Sep 17 00:00:00 2001 From: sergeyteleshev Date: Thu, 2 Jul 2026 17:38:43 +0200 Subject: [PATCH 06/10] dbeaver/pro#9522 refactors profiles --- .../src/SSH/ConnectionFormSSHPart.ts | 28 ++----------------- .../src/SSH/SSH.tsx | 2 +- .../src/CONNECTION_NETWORK_HANDLER_SCHEMA.ts | 1 + 3 files changed, 5 insertions(+), 26 deletions(-) diff --git a/webapp/packages/plugin-connection-network-handlers/src/SSH/ConnectionFormSSHPart.ts b/webapp/packages/plugin-connection-network-handlers/src/SSH/ConnectionFormSSHPart.ts index 3368aeed0c7..bb7742e1b67 100644 --- a/webapp/packages/plugin-connection-network-handlers/src/SSH/ConnectionFormSSHPart.ts +++ b/webapp/packages/plugin-connection-network-handlers/src/SSH/ConnectionFormSSHPart.ts @@ -8,7 +8,7 @@ import { FormPart, formValidationContext, type IFormState } from '@cloudbeaver/core-ui'; import type { IExecutionContextProvider } from '@cloudbeaver/core-executor'; -import { action, makeObservable, observable, toJS } from 'mobx'; +import { toJS } from 'mobx'; import { DriverConfigurationType, type NetworkHandlerConfigInput, type NetworkHandlerDescriptor } from '@cloudbeaver/core-sdk'; import { ConnectionInfoNetworkHandlersResource } from '@cloudbeaver/core-connections'; import { @@ -25,8 +25,6 @@ import type { ConnectionFormOptionsPart, IConnectionFormState } from '@cloudbeav const getDefaultState = (): INetworkHandlerConfig => SSH_DEFAULT_HANDLER_CONFIG() as INetworkHandlerConfig; export class ConnectionFormSSHPart extends FormPart { - networkProfileConfig: INetworkHandlerConfig | null; - constructor( formState: IFormState, private readonly networkHandlerResource: NetworkHandlerResource, @@ -34,32 +32,12 @@ export class ConnectionFormSSHPart extends FormPart { const validation = contexts.getContext(formValidationContext); - if (!this.isChanged || !this.state.enabled) { + if (!this.isChanged || !this.state.enabled || this.isReadOnly) { return; } - for (const error of validateSSHConfig(this.state, this.networkProfileConfig || this.initialState)) { + for (const error of validateSSHConfig(this.state, this.initialState)) { validation.error(error); } } diff --git a/webapp/packages/plugin-connection-network-handlers/src/SSH/SSH.tsx b/webapp/packages/plugin-connection-network-handlers/src/SSH/SSH.tsx index d1a71e0d18d..cd3aab85c68 100644 --- a/webapp/packages/plugin-connection-network-handlers/src/SSH/SSH.tsx +++ b/webapp/packages/plugin-connection-network-handlers/src/SSH/SSH.tsx @@ -35,7 +35,7 @@ export const SSH: TabContainerPanelComponent = observer(function SSH({ fo (val: T) => (val === null ? undefined : val); export const NETWORK_HANDLER_SCHEMA = schema.object({ From c43512c24180492eb5124257514e67f79a6c1817 Mon Sep 17 00:00:00 2001 From: sergeyteleshev Date: Thu, 2 Jul 2026 21:31:59 +0200 Subject: [PATCH 07/10] dbeaver/pro#9522 removes preprocess --- .../src/SSH/ConnectionFormSSHPart.ts | 2 +- .../src/CONNECTION_NETWORK_HANDLER_SCHEMA.ts | 19 ++++++++----------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/webapp/packages/plugin-connection-network-handlers/src/SSH/ConnectionFormSSHPart.ts b/webapp/packages/plugin-connection-network-handlers/src/SSH/ConnectionFormSSHPart.ts index bb7742e1b67..3722edf2cf9 100644 --- a/webapp/packages/plugin-connection-network-handlers/src/SSH/ConnectionFormSSHPart.ts +++ b/webapp/packages/plugin-connection-network-handlers/src/SSH/ConnectionFormSSHPart.ts @@ -8,7 +8,6 @@ import { FormPart, formValidationContext, type IFormState } from '@cloudbeaver/core-ui'; import type { IExecutionContextProvider } from '@cloudbeaver/core-executor'; -import { toJS } from 'mobx'; import { DriverConfigurationType, type NetworkHandlerConfigInput, type NetworkHandlerDescriptor } from '@cloudbeaver/core-sdk'; import { ConnectionInfoNetworkHandlersResource } from '@cloudbeaver/core-connections'; import { @@ -20,6 +19,7 @@ import { validateSSHConfig, type INetworkHandlerConfig, } from '@cloudbeaver/plugin-network-handlers'; +import { toJS } from 'mobx'; import type { ConnectionFormOptionsPart, IConnectionFormState } from '@cloudbeaver/plugin-connections'; const getDefaultState = (): INetworkHandlerConfig => SSH_DEFAULT_HANDLER_CONFIG() as INetworkHandlerConfig; diff --git a/webapp/packages/plugin-network-handlers/src/CONNECTION_NETWORK_HANDLER_SCHEMA.ts b/webapp/packages/plugin-network-handlers/src/CONNECTION_NETWORK_HANDLER_SCHEMA.ts index eb6b890d916..7abdb9326b4 100644 --- a/webapp/packages/plugin-network-handlers/src/CONNECTION_NETWORK_HANDLER_SCHEMA.ts +++ b/webapp/packages/plugin-network-handlers/src/CONNECTION_NETWORK_HANDLER_SCHEMA.ts @@ -9,19 +9,16 @@ import { NetworkHandlerAuthType } from '@cloudbeaver/core-sdk'; import { schema } from '@cloudbeaver/core-utils'; -// TODO remove it -const nullToUndefined = (val: T) => (val === null ? undefined : val); - export const NETWORK_HANDLER_SCHEMA = schema.object({ id: schema.string(), - authType: schema.preprocess(nullToUndefined, schema.nativeEnum(NetworkHandlerAuthType).optional()), - enabled: schema.preprocess(nullToUndefined, schema.boolean().optional()), - key: schema.preprocess(nullToUndefined, schema.string().optional()), - password: schema.preprocess(nullToUndefined, schema.string().optional()), - properties: schema.preprocess(nullToUndefined, schema.record(schema.string(), schema.any()).optional()), - savePassword: schema.preprocess(nullToUndefined, schema.boolean().optional()), - secureProperties: schema.preprocess(nullToUndefined, schema.record(schema.string(), schema.any()).optional()), - userName: schema.preprocess(nullToUndefined, schema.string().optional()), + authType: schema.nativeEnum(NetworkHandlerAuthType).optional(), + enabled: schema.boolean().optional(), + key: schema.string().optional(), + password: schema.string().optional(), + properties: schema.record(schema.string(), schema.any()).optional(), + savePassword: schema.boolean().optional(), + secureProperties: schema.record(schema.string(), schema.any()).optional(), + userName: schema.string().optional(), }); export type INetworkHandlerConfig = schema.infer; From 0be4844543a3a4e01accc1691b08bffbf1d2cd2c Mon Sep 17 00:00:00 2001 From: sergeyteleshev Date: Thu, 9 Jul 2026 21:36:55 +0200 Subject: [PATCH 08/10] dbeaver/pro#9522 adds header & footer items --- .../@dbeaver/ui-kit/src/Select/Select.css | 20 ++++++++++++++- .../ui-kit/src/Select/SelectField.tsx | 25 +++++++++++++++++-- .../core-blocks/src/FormControls/Select.tsx | 6 +++++ 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/webapp/common-react/@dbeaver/ui-kit/src/Select/Select.css b/webapp/common-react/@dbeaver/ui-kit/src/Select/Select.css index dbff08ea8c0..30f191505e8 100644 --- a/webapp/common-react/@dbeaver/ui-kit/src/Select/Select.css +++ b/webapp/common-react/@dbeaver/ui-kit/src/Select/Select.css @@ -56,7 +56,9 @@ z-index: 1000; min-width: 100%; max-height: var(--dbv-kit-select-popover-height); - overflow-y: auto; + overflow-y: hidden; + display: flex; + flex-direction: column; background-color: var(--dbv-kit-select-popover-background); color: var(--dbv-kit-select-popover-foreground); border-color: var(--dbv-kit-select-border-color); @@ -83,6 +85,22 @@ } } + .dbv-kit-select__popover-items { + overflow-y: auto; + flex: 1; + min-height: 0; + } + + .dbv-kit-select__popover-header { + border-bottom: 1px solid var(--dbv-kit-select-border-color); + flex-shrink: 0; + } + + .dbv-kit-select__popover-footer { + border-top: 1px solid var(--dbv-kit-select-border-color); + flex-shrink: 0; + } + .dbv-kit-select__group { border-top: 1px solid var(--dbv-kit-select-border-color); } diff --git a/webapp/common-react/@dbeaver/ui-kit/src/Select/SelectField.tsx b/webapp/common-react/@dbeaver/ui-kit/src/Select/SelectField.tsx index 2023fb9ed60..1f2f09c64c7 100644 --- a/webapp/common-react/@dbeaver/ui-kit/src/Select/SelectField.tsx +++ b/webapp/common-react/@dbeaver/ui-kit/src/Select/SelectField.tsx @@ -71,6 +71,18 @@ export interface ISelectFieldProps> { noItemsPlaceholder?: React.ReactNode; + /** + * Items pinned above the scrollable list, always visible at the top of the popover. + * Uses the same item API (itemValue, itemRender, itemDisabled) as the main items list. + */ + headerItems?: ItemType[]; + + /** + * Items pinned below the scrollable list, always visible at the bottom of the popover. + * Uses the same item API (itemValue, itemRender, itemDisabled) as the main items list. + */ + footerItems?: ItemType[]; + /** * Custom renderer for the selected value, overrides itemRenderer for the selected state * Only needed for special formatting of the selected value different from list items @@ -123,6 +135,8 @@ export function SelectField>({ isSeparator, label, noItemsPlaceholder = 'No items', + headerItems, + footerItems, description, disabled, required, @@ -159,7 +173,8 @@ export function SelectField>({ const handleChange = (newValue: string | readonly string[]) => { // TODO: add support for multi-select - const newItem = items.find(item => getItemValueSerialized(item) === newValue); + const allItems = [...(headerItems ?? []), ...items, ...(footerItems ?? [])]; + const newItem = allItems.find(item => getItemValueSerialized(item) === newValue); if (!newItem) { return; } @@ -212,7 +227,13 @@ export function SelectField>({ {description && {description}} - {renderItems()} + {headerItems && headerItems.length > 0 && ( + {headerItems.map(renderSelectItem)} + )} +
{renderItems()}
+ {footerItems && footerItems.length > 0 && ( + {footerItems.map(renderSelectItem)} + )}
diff --git a/webapp/packages/core-blocks/src/FormControls/Select.tsx b/webapp/packages/core-blocks/src/FormControls/Select.tsx index ad869fed000..3678acd6710 100644 --- a/webapp/packages/core-blocks/src/FormControls/Select.tsx +++ b/webapp/packages/core-blocks/src/FormControls/Select.tsx @@ -41,6 +41,8 @@ export type SelectBaseProps = Omit< inline?: boolean; children?: string; portal?: boolean; + headerItems?: TValue[]; + footerItems?: TValue[]; }; type ControlledProps = SelectBaseProps & { @@ -78,6 +80,8 @@ export const Select: ISelectType = observer(function Select({ inline, description, placeholder, + headerItems, + footerItems, id, keySelector = v => v, valueSelector = v => v, @@ -194,6 +198,8 @@ export const Select: ISelectType = observer(function Select({ itemRender={itemRender} itemDisabled={itemDisabled} isSeparator={isSeparator} + headerItems={headerItems} + footerItems={footerItems} name={name} disabled={disabled || readOnly} noItemsPlaceholder={translate('combobox_no_results_placeholder')} From aaec6135e5ba7c0bcacbb70bd2ff5355fd60b923 Mon Sep 17 00:00:00 2001 From: sergeyteleshev Date: Thu, 9 Jul 2026 21:58:16 +0200 Subject: [PATCH 09/10] dbeaver/pro#9522 truncates items values --- webapp/packages/core-blocks/src/FormControls/Select.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webapp/packages/core-blocks/src/FormControls/Select.tsx b/webapp/packages/core-blocks/src/FormControls/Select.tsx index 3678acd6710..d0dff201cdf 100644 --- a/webapp/packages/core-blocks/src/FormControls/Select.tsx +++ b/webapp/packages/core-blocks/src/FormControls/Select.tsx @@ -156,9 +156,9 @@ export const Select: ISelectType = observer(function Select({ function itemRender(item: (typeof items)[number]): React.ReactNode { return ( -
+
{renderIcon(item)} - {valueSelector(item)} + {valueSelector(item)}
); } From 1aee6dc47a277e2861f38efd31c2a963f32fd76f Mon Sep 17 00:00:00 2001 From: Ainur Date: Fri, 10 Jul 2026 17:05:43 +0200 Subject: [PATCH 10/10] dbeaver/pro#9522 fix retrieving creds from profiles --- .../src/io/cloudbeaver/WebSessionProjectImpl.java | 7 +++++++ .../io/cloudbeaver/service/core/impl/WebServiceCore.java | 6 +----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/WebSessionProjectImpl.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/WebSessionProjectImpl.java index 5e89a602413..ae8ed9d3c90 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/WebSessionProjectImpl.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/WebSessionProjectImpl.java @@ -357,6 +357,13 @@ public DataSourceDescriptor getDataSourceContainerFromInput(@NotNull WebConnecti return getInputConfigHandler(configInput).createDataSourceContainer(); } + public void updateDataSourceContainerFromInput( + @NotNull WebConnectionConfig configInput, + @NotNull DataSourceDescriptor dataSource + ) throws DBWebException { + getInputConfigHandler(configInput).updateDataSource(dataSource); + } + @NotNull public WebConnectionConfig getConnectionConfigInput(@Nullable Map configMap) { return new WebConnectionConfig(configMap == null ? Map.of() : configMap); diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/impl/WebServiceCore.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/impl/WebServiceCore.java index b92adc97347..be9090ef304 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/impl/WebServiceCore.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/impl/WebServiceCore.java @@ -601,11 +601,7 @@ private DataSourceDescriptor getDataSourceDescriptor( } testDataSource = (DataSourceDescriptor) dataSource.createCopy(dataSource.getRegistry()); - WebDataSourceUtils.setConnectionConfiguration( - testDataSource.getDriver(), - testDataSource.getConnectionConfiguration(), - configInput - ); + project.updateDataSourceContainerFromInput(configInput, testDataSource); if (configInput.getSelectedSecretId() != null) { try { dataSource.listSharedCredentials()