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:
Alexey
2022-01-25 16:11:42 +03:00
committed by GitHub
7 changed files with 95 additions and 48 deletions
@@ -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', '错误:'],
@@ -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
>