mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
Merge pull request #635 from dbeaver/fix/ssh-key-saved-hint
fix(plugin-connections): CB-1306 CB-1564 saved state for ssh key
This commit is contained in:
@@ -44,6 +44,7 @@ const styles = css`
|
||||
`;
|
||||
|
||||
type BaseProps = Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'onChange' | 'style'> & ILayoutSizeProps & {
|
||||
description?: string;
|
||||
labelTooltip?: string;
|
||||
mod?: 'surface';
|
||||
style?: ComponentStyle;
|
||||
@@ -82,6 +83,7 @@ export const Textarea: TextareaType = observer(function Textarea({
|
||||
small,
|
||||
medium,
|
||||
large,
|
||||
description,
|
||||
labelTooltip,
|
||||
mod,
|
||||
embedded,
|
||||
@@ -106,7 +108,12 @@ export const Textarea: TextareaType = observer(function Textarea({
|
||||
|
||||
return styled(useStyles(baseFormControlStyles, styles, style))(
|
||||
<field className={className} {...use({ tiny, small, medium, large, embedded })}>
|
||||
<field-label as='label' title={labelTooltip || rest.title}>{children}{required && ' *'}</field-label>
|
||||
<field-label
|
||||
as='label'
|
||||
title={labelTooltip || rest.title}
|
||||
>
|
||||
{children}{required && ' *'}
|
||||
</field-label>
|
||||
<textarea
|
||||
{...rest}
|
||||
value={value ?? ''}
|
||||
@@ -115,6 +122,11 @@ export const Textarea: TextareaType = observer(function Textarea({
|
||||
onChange={handleChange}
|
||||
{...use({ mod })}
|
||||
/>
|
||||
{description && (
|
||||
<field-description>
|
||||
{description}
|
||||
</field-description>
|
||||
)}
|
||||
</field>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@ export const defaultENLocale = [
|
||||
['ui_processing_ok', 'Ok'],
|
||||
['ui_processing_create', 'Create'],
|
||||
['ui_processing_save', 'Save'],
|
||||
['ui_processing_saved', 'Saved'],
|
||||
['ui_configure', 'Configure'],
|
||||
['ui_apply', 'Apply'],
|
||||
['ui_error_message', 'Error:'],
|
||||
|
||||
@@ -13,6 +13,7 @@ export const defaultITLocale = [
|
||||
['ui_processing_ok', 'Ok'],
|
||||
['ui_processing_create', 'Crea'],
|
||||
['ui_processing_save', 'Salva'],
|
||||
['ui_processing_saved', 'Saved'],
|
||||
['ui_configure', 'Configure'],
|
||||
['ui_apply', 'Applica'],
|
||||
['ui_error_message', 'Errore:'],
|
||||
|
||||
@@ -11,6 +11,7 @@ export const defaultRULocale = [
|
||||
['ui_processing_ok', 'Принять'],
|
||||
['ui_processing_create', 'Создать'],
|
||||
['ui_processing_save', 'Сохранить'],
|
||||
['ui_processing_saved', 'Сохранено'],
|
||||
['ui_configure', 'Настроить'],
|
||||
['ui_apply', 'Применить'],
|
||||
['ui_error_message', 'Ошибка:'],
|
||||
|
||||
@@ -13,6 +13,7 @@ export const defaultZHLocale = [
|
||||
['ui_processing_ok', '好'],
|
||||
['ui_processing_create', '创建'],
|
||||
['ui_processing_save', '保存'],
|
||||
['ui_processing_saved', 'Saved'],
|
||||
['ui_configure', '配置'],
|
||||
['ui_apply', '应用'],
|
||||
['ui_error_message', '错误:'],
|
||||
|
||||
+65
-38
@@ -6,7 +6,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { DatabaseConnection, DBDriverResource, SSH_TUNNEL_ID } from '@cloudbeaver/core-connections';
|
||||
import { DBDriverResource, SSH_TUNNEL_ID } from '@cloudbeaver/core-connections';
|
||||
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
|
||||
import type { IExecutionContextProvider } from '@cloudbeaver/core-executor';
|
||||
import { NetworkHandlerAuthType, NetworkHandlerConfigInput } from '@cloudbeaver/core-sdk';
|
||||
@@ -118,27 +118,32 @@ export class ConnectionSSHTabService extends Bootstrap {
|
||||
}
|
||||
|
||||
for (const handler of config.networkHandlersConfig) {
|
||||
if (handler.enabled && this.isChanged(handler, info)) {
|
||||
if (handler.authType === NetworkHandlerAuthType.PublicKey && !handler.key?.length) {
|
||||
if (handler.enabled) {
|
||||
const initial = info?.networkHandlersConfig.find(h => h.id === handler.id);
|
||||
if (this.isChanged(handler, initial)) {
|
||||
if (handler.savePassword && !handler.userName?.length) {
|
||||
validation.error("Field SSH 'User' can't be empty");
|
||||
}
|
||||
|
||||
if (!handler.properties?.host?.length) {
|
||||
validation.error("Field SSH 'Host' can't be empty");
|
||||
}
|
||||
|
||||
const port = Number(handler.properties?.port);
|
||||
if (Number.isNaN(port) || port < 1) {
|
||||
validation.error("Field SSH 'Port' can't be empty");
|
||||
}
|
||||
}
|
||||
|
||||
const keyAuth = handler.authType === NetworkHandlerAuthType.PublicKey;
|
||||
const keySaved = initial?.key === '';
|
||||
if (keyAuth && !keySaved && !handler.key?.length) {
|
||||
validation.error("Field SSH 'Private key' can't be empty");
|
||||
}
|
||||
|
||||
if (handler.savePassword) {
|
||||
if (!handler.userName?.length) {
|
||||
validation.error("Field SSH 'User' can't be empty");
|
||||
}
|
||||
if (handler.authType === NetworkHandlerAuthType.Password && !handler.password?.length) {
|
||||
validation.error("Field SSH 'Password' can't be empty");
|
||||
}
|
||||
}
|
||||
|
||||
if (!handler.properties?.host?.length) {
|
||||
validation.error("Field SSH 'Host' can't be empty");
|
||||
}
|
||||
|
||||
const port = Number(handler.properties?.port);
|
||||
if (Number.isNaN(port) || port < 1) {
|
||||
validation.error("Field SSH 'Port' can't be empty");
|
||||
const passwordSaved = initial?.password === '' && initial.authType === handler.authType;
|
||||
if (!keyAuth && handler.savePassword && !passwordSaved && !handler.password?.length) {
|
||||
validation.error("Field SSH 'Password' can't be empty");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -160,8 +165,16 @@ export class ConnectionSSHTabService extends Bootstrap {
|
||||
const configs: NetworkHandlerConfigInput[] = [];
|
||||
|
||||
for (const handler of state.config.networkHandlersConfig) {
|
||||
if (this.isChanged(handler, state.info)) {
|
||||
configs.push({ ...handler, key: handler.authType === NetworkHandlerAuthType.PublicKey ? handler.key : undefined });
|
||||
const initial = state.info?.networkHandlersConfig.find(h => h.id === handler.id);
|
||||
const passwordChanged = this.isPasswordChanged(handler, initial);
|
||||
const keyChanged = this.isKeyChanged(handler, initial);
|
||||
|
||||
if (this.isChanged(handler, initial) || passwordChanged || keyChanged) {
|
||||
configs.push({
|
||||
...handler,
|
||||
key: handler.authType === NetworkHandlerAuthType.PublicKey && keyChanged ? handler.key : undefined,
|
||||
password: passwordChanged ? handler.password : undefined,
|
||||
});
|
||||
}
|
||||
if (handler.enabled && !handler.savePassword) {
|
||||
credentialsState.requireNetworkHandler(handler.id);
|
||||
@@ -185,31 +198,45 @@ export class ConnectionSSHTabService extends Bootstrap {
|
||||
}
|
||||
}
|
||||
|
||||
private isChanged(handler: NetworkHandlerConfigInput, info?: DatabaseConnection) {
|
||||
const initialConfig = info?.networkHandlersConfig.find(h => h.id === handler.id);
|
||||
if (!initialConfig && !handler.enabled) {
|
||||
private isChanged(handler: NetworkHandlerConfigInput, initial?: NetworkHandlerConfigInput) {
|
||||
if (!initial && !handler.enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const port = Number(initialConfig?.properties?.port);
|
||||
const port = Number(initial?.properties?.port);
|
||||
const formPort = Number(handler.properties?.port);
|
||||
|
||||
const passwordChanged = (
|
||||
(((initialConfig?.password === null && handler.password !== null) || initialConfig?.password === '') && handler.password !== '')
|
||||
|| !!handler.password?.length
|
||||
);
|
||||
|
||||
if (handler.enabled !== initialConfig?.enabled
|
||||
|| handler.authType !== initialConfig?.authType
|
||||
|| handler.savePassword !== initialConfig?.savePassword
|
||||
|| handler.userName !== initialConfig?.userName
|
||||
|| passwordChanged
|
||||
|| handler.key !== initialConfig?.key
|
||||
|| handler.properties?.host !== initialConfig?.properties?.host
|
||||
if (handler.enabled !== initial?.enabled
|
||||
|| handler.authType !== initial?.authType
|
||||
|| handler.savePassword !== initial?.savePassword
|
||||
|| handler.userName !== initial?.userName
|
||||
|| handler.properties?.host !== initial?.properties?.host
|
||||
|| port !== formPort) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private isPasswordChanged(handler: NetworkHandlerConfigInput, initial?: NetworkHandlerConfigInput) {
|
||||
if (!initial && !handler.enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
(((initial?.password === null && handler.password !== null) || initial?.password === '') && handler.password !== '')
|
||||
|| !!handler.password?.length
|
||||
);
|
||||
}
|
||||
|
||||
private isKeyChanged(handler: NetworkHandlerConfigInput, initial?: NetworkHandlerConfigInput) {
|
||||
if (!initial && !handler.enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
(((initial?.key === null && handler.key !== null) || initial?.key === '') && handler.key !== '')
|
||||
|| !!handler.key?.length
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useState } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { useAdministrationSettings } from '@cloudbeaver/core-administration';
|
||||
@@ -72,14 +72,11 @@ export const SSH: TabContainerPanelComponent<IConnectionFormProps> = observer(fu
|
||||
const disabled = formDisabled || loading;
|
||||
const enabled = state.enabled || false;
|
||||
const keyAuth = state.authType === NetworkHandlerAuthType.PublicKey;
|
||||
const passwordFilled = (initialConfig?.password === null && state.password !== '') || (state.password?.length || 0) > 0;
|
||||
const passwordFilled = (initialConfig?.password === null && state.password !== '') || !!state.password?.length;
|
||||
const testAvailable = keyAuth ? !!state.key?.length : passwordFilled;
|
||||
const passwordLabel = keyAuth ? 'Passphrase' : translate('connections_network_handler_ssh_tunnel_password');
|
||||
let passwordHint = '';
|
||||
|
||||
if (initialConfig?.password === '' && initialConfig.authType === state.authType) {
|
||||
passwordHint = '••••••';
|
||||
}
|
||||
const passwordSaved = initialConfig?.password === '' && initialConfig.authType === state.authType;
|
||||
const keySaved = initialConfig?.key === '';
|
||||
|
||||
const handleKeyUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0];
|
||||
@@ -96,6 +93,10 @@ export const SSH: TabContainerPanelComponent<IConnectionFormProps> = observer(fu
|
||||
}
|
||||
};
|
||||
|
||||
const authTypeChangeHandler = useCallback(() => {
|
||||
state.password = '';
|
||||
}, []);
|
||||
|
||||
return styled(styles)(
|
||||
<SubmittingForm>
|
||||
<ColoredContainer parent>
|
||||
@@ -116,6 +117,7 @@ export const SSH: TabContainerPanelComponent<IConnectionFormProps> = observer(fu
|
||||
valueSelector={value => value.label}
|
||||
disabled={disabled || readonly || !enabled}
|
||||
tiny
|
||||
onSelect={authTypeChangeHandler}
|
||||
>
|
||||
{translate('connections_network_handler_ssh_tunnel_auth_type')}
|
||||
</Combobox>
|
||||
@@ -153,6 +155,7 @@ export const SSH: TabContainerPanelComponent<IConnectionFormProps> = observer(fu
|
||||
disabled={disabled || !enabled}
|
||||
readOnly={readonly}
|
||||
mod='surface'
|
||||
required={state.savePassword}
|
||||
tiny
|
||||
>
|
||||
{translate('connections_network_handler_ssh_tunnel_user')}
|
||||
@@ -160,13 +163,13 @@ export const SSH: TabContainerPanelComponent<IConnectionFormProps> = observer(fu
|
||||
<InputField
|
||||
type="password"
|
||||
name="password"
|
||||
placeholder={passwordHint}
|
||||
autoComplete='new-password'
|
||||
state={state}
|
||||
disabled={disabled || !enabled}
|
||||
readOnly={readonly}
|
||||
mod='surface'
|
||||
required={!keyAuth}
|
||||
required={!keyAuth && state.savePassword}
|
||||
description={passwordSaved ? translate('ui_processing_saved') : undefined}
|
||||
tiny
|
||||
>
|
||||
{passwordLabel}
|
||||
@@ -178,6 +181,7 @@ export const SSH: TabContainerPanelComponent<IConnectionFormProps> = observer(fu
|
||||
state={state}
|
||||
disabled={disabled || !enabled}
|
||||
readOnly={readonly}
|
||||
description={keySaved ? translate('ui_processing_saved') : undefined}
|
||||
required
|
||||
medium
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user