Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions webapp/packages/core-connections/src/CONNECTION_CONFIG_SCHEMA.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2026 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 { DriverConfigurationType, NetworkHandlerAuthType } from '@cloudbeaver/core-sdk';
import { schema } from '@cloudbeaver/core-utils';

export const CONNECTION_PROPERTIES_SCHEMA = schema.record(schema.string(), schema.any());

export const CONNECTION_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(),
});

export const CONNECTION_CONFIG_SCHEMA = schema.object({
authModelId: schema.string().optional(),
configurationType: schema.enum([DriverConfigurationType.Manual, DriverConfigurationType.Url]).optional(),
connectionId: schema.string().optional(),
credentials: schema.record(schema.string(), schema.any()).optional(),
dataSourceId: schema.string().optional(),
databaseName: schema.string().optional(),
description: schema.string().optional(),
driverId: schema.string().optional(),
folder: schema.string().optional(),
host: schema.string().optional(),
mainPropertyValues: schema.record(schema.string(), schema.any()).optional(),
expertSettingsValues: schema.record(schema.string(), schema.any()).optional(),
name: schema.string().optional(),
networkHandlersConfig: schema.array(CONNECTION_NETWORK_HANDLER_SCHEMA).optional(),
port: schema.string().optional(),
properties: CONNECTION_PROPERTIES_SCHEMA.optional(),
providerProperties: schema.record(schema.string(), schema.any()).optional(),
saveCredentials: schema.boolean().optional(),
selectedSecretId: schema.string().optional(),
serverName: schema.string().optional(),
sharedCredentials: schema.boolean().optional(),
url: schema.string().optional(),
userName: schema.string().optional(),
userPassword: schema.string().optional(),
});
1 change: 1 addition & 0 deletions webapp/packages/core-connections/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,4 @@ export * from './useDBDriver.js';
export * from './USER_NAME_PROPERTY_ID.js';
export * from './parseConnectionKey.js';
export * from './DBDriverExpertSettingsResource.js';
export * from './CONNECTION_CONFIG_SCHEMA.js';
17 changes: 17 additions & 0 deletions webapp/packages/plugin-connection-preferences/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# dependencies
/node_modules

# testing
/coverage

# production
/lib

# misc
.DS_Store
.env*

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
53 changes: 53 additions & 0 deletions webapp/packages/plugin-connection-preferences/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"name": "@cloudbeaver/plugin-connection-preferences",
"type": "module",
"sideEffects": [
"./lib/module.js",
"./lib/index.js",
"src/**/*.css",
"src/**/*.scss",
"public/**/*"
],
"version": "0.1.0",
"description": "",
"license": "Apache-2.0",
"exports": {
".": "./lib/index.js",
"./module": "./lib/module.js"
},
"scripts": {
"build": "tsc -b",
"clean": "rimraf --glob lib",
"lint": "eslint ./src/ --ext .ts,.tsx",
"validate-dependencies": "core-cli-validate-dependencies"
},
"dependencies": {
"@cloudbeaver/core-blocks": "workspace:*",
"@cloudbeaver/core-connections": "workspace:*",
"@cloudbeaver/core-data-context": "workspace:*",
"@cloudbeaver/core-di": "workspace:*",
"@cloudbeaver/core-events": "workspace:*",
"@cloudbeaver/core-executor": "workspace:*",
"@cloudbeaver/core-localization": "workspace:*",
"@cloudbeaver/core-navigation-tree": "workspace:*",
"@cloudbeaver/core-projects": "workspace:*",
"@cloudbeaver/core-sdk": "workspace:*",
"@cloudbeaver/core-ui": "workspace:*",
"@cloudbeaver/core-utils": "workspace:*",
"@cloudbeaver/core-view": "workspace:*",
"@dbeaver/js-helpers": "workspace:*",
"mobx": "^6",
"mobx-react-lite": "^4",
"react": "^19",
"react-dom": "^19",
"tslib": "^2"
},
"devDependencies": {
"@cloudbeaver/core-cli": "workspace:*",
"@cloudbeaver/tsconfig": "workspace:*",
"@types/react": "^19",
"rimraf": "^6",
"typescript": "^5",
"typescript-plugin-css-modules": "^5"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2026 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 { DATA_CONTEXT_CONNECTION } from '@cloudbeaver/core-connections';
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { ActionService, MenuService } from '@cloudbeaver/core-view';
import { DATA_CONTEXT_NAV_NODE, EObjectFeature } from '@cloudbeaver/core-navigation-tree';

import { ACTION_CONNECTION_PREFERENCES } from './actions/ACTION_CONNECTION_PREFERENCES.js';
import { ConnectionPreferencesPanelService } from './ConnectionPreferencesPanelService.js';

@injectable(() => [ActionService, MenuService, ConnectionPreferencesPanelService])
export class ConnectionPreferencesBootstrap extends Bootstrap {
constructor(
private readonly actionService: ActionService,
private readonly menuService: MenuService,
private readonly connectionPreferencesPanelService: ConnectionPreferencesPanelService,
) {
super();
}

override register(): void {
this.menuService.addCreator({
root: true,
contexts: [DATA_CONTEXT_CONNECTION, DATA_CONTEXT_NAV_NODE],
isApplicable: context => {
const node = context.get(DATA_CONTEXT_NAV_NODE)!;
return node.objectFeatures.includes(EObjectFeature.dataSource);
},
getItems: (context, items) => [...items, ACTION_CONNECTION_PREFERENCES],
});

this.actionService.addHandler({
id: 'connection-preferences',
actions: [ACTION_CONNECTION_PREFERENCES],
contexts: [DATA_CONTEXT_CONNECTION],
handler: async context => {
const connectionKey = context.get(DATA_CONTEXT_CONNECTION)!;
await this.connectionPreferencesPanelService.open(connectionKey);
},
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2026 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 { Form, Loader, Placeholder, StatusMessage, useForm, useObjectRef } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { ENotificationType, NotificationService } from '@cloudbeaver/core-events';
import type { ConnectionConfig } from '@cloudbeaver/core-sdk';
import { ExecutionContext } from '@cloudbeaver/core-executor';
import { TabList, TabPanelList, TabsState, type IFormState } from '@cloudbeaver/core-ui';
import { getFirstException } from '@cloudbeaver/core-utils';

import { ConnectionPreferencesFormService } from './ConnectionPreferencesFormService.js';
import type { ConnectionPreferencesFormState } from './ConnectionPreferencesFormState.js';
import type { IConnectionPreferencesFormState } from './IConnectionPreferencesFormState.js';
import { ConnectionPreferencesFormActionsContext, type IConnectionPreferencesFormActionsContext } from './ConnectionPreferencesFormActionsContext.js';
import { getConnectionPreferencesFormInfoPart } from './ConnectionPreferencesFormInfo/getConnectionPreferencesFormInfoPart.js';

export interface ConnectionPreferencesFormProps {
formState: ConnectionPreferencesFormState;
onCancel?: () => void;
onSave?: (config: ConnectionConfig) => void;
}

export const ConnectionPreferencesForm = observer<ConnectionPreferencesFormProps>(function ConnectionPreferencesForm({ formState, onCancel, onSave = () => { } }) {
const connectionPreferencesFormServicee = useService(ConnectionPreferencesFormService);
const notificationService = useService(NotificationService);

const infoPart = getConnectionPreferencesFormInfoPart(formState);
const exception = getFirstException(formState.exception);

const form = useForm({
onSubmit: async event => {

Check warning on line 39 in webapp/packages/plugin-connection-preferences/src/ConnectionPreferencesForm/ConnectionPreferencesForm.tsx

View workflow job for this annotation

GitHub Actions / Frontend / Lint

'event' is defined but never used
const context = new ExecutionContext<IFormState<IConnectionPreferencesFormState>>(formState);

const saved = await formState.save(context);

if (saved) {
notificationService.notify(
{
title: 'core_connections_connection_update_success',
message: infoPart.state.name,
},
ENotificationType.Success,
);

onSave(infoPart.state);
}
},
});

const actionsContext = useObjectRef<IConnectionPreferencesFormActionsContext>(() => ({
save: () => form.submit(new SubmitEvent('submit')),
onCancel,
}));

return (
<Form context={form} contents>
<TabsState container={connectionPreferencesFormServicee.parts} localState={formState.parts} formState={formState}>
<div className="tw:flex tw:flex-col tw:flex-1 tw:h-full tw:overflow-auto theme-background-secondary theme-text-on-secondary">
<div className="tw:relative tw:flex tw:pt-4 tw:border-b-2 theme-border-color-background theme-background-secondary theme-text-on-secondary">
<div className="tw:flex-1 tw:overflow-hidden">
<div className="tw:h-6 tw:px-4 tw:flex tw:items-center tw:gap-2 theme-typography-caption tw:overflow-hidden">
<StatusMessage
type={exception ? ENotificationType.Error : ENotificationType.Info}
message={formState.statusMessage}
exception={exception}
/>
</div>
<TabList className="tw:relative tw:flex-shrink-0 tw:items-center" disabled={formState.isDisabled} underline big />
</div>
<div className="tw:flex tw:items-center tw:px-6 tw:gap-4">
<Loader suspense inline hideMessage hideException>
<ConnectionPreferencesFormActionsContext.Provider value={actionsContext}>
<Placeholder container={connectionPreferencesFormServicee.actionsContainer} formState={formState} />
</ConnectionPreferencesFormActionsContext.Provider>
</Loader>
</div>
</div>
<div className="tw:relative tw:flex tw:flex-1 tw:flex-col tw:overflow-auto theme-background-secondary theme-border-color-background">
<TabPanelList />
</div>
</div>
</TabsState>
</Form>
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2026 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 { createContext } from 'react';

export interface IConnectionPreferencesFormActionsContext {
save: () => Promise<void>;
onCancel?: () => void;
}

export const ConnectionPreferencesFormActionsContext = createContext<IConnectionPreferencesFormActionsContext | null>(null);
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2026 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 { Button, type PlaceholderComponent, useTranslate } from '@cloudbeaver/core-blocks';

import { ConnectionPreferencesFormActionsContext } from './ConnectionPreferencesFormActionsContext.js';
import type { IConnectionPreferencesFormProps } from './IConnectionPreferencesFormState.js';

export const ConnectionPreferencesFormBaseActions: PlaceholderComponent<IConnectionPreferencesFormProps> = observer(function ConnectionPreferencesFormBaseActions({ formState }) {
const actions = useContext(ConnectionPreferencesFormActionsContext);

if (!actions) {
throw new Error('ConnectionPreferencesFormActionsContext not provided');
}

const translate = useTranslate();

return (
<>
{actions.onCancel && (
<Button type="button" disabled={formState.isDisabled} variant="secondary" onClick={actions.onCancel}>
{translate('ui_processing_cancel')}
</Button>
)}
<Button type="button" disabled={formState.isDisabled || formState.isReadOnly || !formState.isChanged} loader onClick={actions['save']}>
{translate(formState.mode === 'edit' ? 'ui_processing_save' : 'ui_processing_create')}
</Button>
</>
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2026 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 { useTab, type TabContainerPanelComponent } from '@cloudbeaver/core-ui';
import { InputField, Textarea, useAutoLoad, useTranslate, useResource, Group, ColoredContainer, IconOrImage } from '@cloudbeaver/core-blocks';
import { ProjectInfoResource } from '@cloudbeaver/core-projects';
import { DBDriverResource } from '@cloudbeaver/core-connections';

import type { IConnectionPreferencesFormProps } from '../IConnectionPreferencesFormState.js';
import { getConnectionPreferencesFormInfoPart } from './getConnectionPreferencesFormInfoPart.js';


export const ConnectionPreferencesFormInfo: TabContainerPanelComponent<IConnectionPreferencesFormProps> = observer(function ConnectionPreferencesFormInfo({ formState, tabId }) {
const translate = useTranslate();
const infoPart = getConnectionPreferencesFormInfoPart(formState);
const tab = useTab(tabId);

useAutoLoad(ConnectionPreferencesFormInfo, infoPart, tab.selected);

const projectInfoResource = useResource(ConnectionPreferencesFormInfo, ProjectInfoResource, formState.state.projectId);
const dbDriverResource = useResource(ConnectionPreferencesFormInfo, DBDriverResource, infoPart.state.driverId ?? null);

return (
<ColoredContainer wrap overflow parent gap>
<Group gap small>
{dbDriverResource.data && (
<InputField value={dbDriverResource.data.name ?? dbDriverResource.data.id} readOnly fill>
<div className='tw:flex tw:items-center tw:gap-1'>
{dbDriverResource.data.icon && <IconOrImage className='tw:size-4' icon={dbDriverResource.data.icon} />}
{translate('connections_connection_driver')}
</div>
</InputField>
)}
<InputField type="text" name="name" state={infoPart.state} readOnly fill>
{translate('connections_connection_name')}
</InputField>
{projectInfoResource.data && (
<InputField value={projectInfoResource.data.name} readOnly fill>
{translate('plugin_projects_project_select_label')}
</InputField>
)}
<InputField type="text" name="folder" state={infoPart.state} autoHide readOnly tiny fill>
{translate('plugin_connections_connection_form_part_main_folder')}
</InputField>
<Textarea name="description" rows={3} state={infoPart.state} readOnly>
{translate('connections_connection_description')}
</Textarea>
</Group>
</ColoredContainer>
);
});
Loading
Loading