11/**
2+ * @import { FileValue } from './file_input.js';
3+ *
24 * @typedef Flavor
35 * @type {object }
46 * @property {string } label
57 * @property {string } value
68 * @property {string } icon
79 * @property {string } flavor
10+ * @property {string } connection_string
811 *
912 * @typedef ConnectionStatus
1013 * @type {object }
3639 * @property {boolean } dirty
3740 * @property {boolean } valid
3841 *
42+ * @typedef FieldsCache
43+ * @type {object }
44+ * @property {FileValue } privateKey
45+ *
3946 * @typedef Properties
4047 * @type {object }
4148 * @property {Connection } connection
4249 * @property {Array.<Flavor> } flavors
4350 * @property {boolean } disableFlavor
44- * @property {(c: Connection, state: FormState) => void } onChange
51+ * @property {FileValue? } cachedPrivateKeyFile
52+ * @property {(c: Connection, state: FormState, cache?: FieldsCache) => void } onChange
4553 */
4654import van from '../van.min.js' ;
4755import { Button } from './button.js' ;
4856import { Alert } from './alert.js' ;
49- import { getValue , emitEvent , loadStylesheet , resizeFrameHeightToElement , resizeFrameHeightOnDOMChange , isEqual } from '../utils.js' ;
57+ import { getValue , emitEvent , loadStylesheet , isEqual } from '../utils.js' ;
5058import { Input } from './input.js' ;
5159import { Slider } from './slider.js' ;
5260import { Checkbox } from './checkbox.js' ;
@@ -55,7 +63,8 @@ import { maxLength, minLength, sizeLimit } from '../form_validators.js';
5563import { RadioGroup } from './radio_group.js' ;
5664import { FileInput } from './file_input.js' ;
5765
58- const { div, hr, span } = van . tags ;
66+ const { div, hr, i, span } = van . tags ;
67+ const clearSentinel = '<clear>' ;
5968const secretsPlaceholder = '<hidden for safety reasons>' ;
6069const defaultPorts = {
6170 redshift : '5439' ,
@@ -76,8 +85,6 @@ const defaultPorts = {
7685const ConnectionForm = ( props , saveButton ) => {
7786 loadStylesheet ( 'connectionform' , stylesheet ) ;
7887
79- window . connectionFormConnection = props . connection ;
80-
8188 const connection = getValue ( props . connection ) ;
8289 const isEditMode = ! ! connection ?. connection_id ;
8390 const defaultPort = defaultPorts [ connection ?. sql_flavor ] ;
@@ -97,27 +104,32 @@ const ConnectionForm = (props, saveButton) => {
97104 const privateKeyPhrase = van . state ( connection ?. private_key_passphrase ) ;
98105 const httpPath = van . state ( connection ?. http_path ) ;
99106
107+ const privateKeyFile = van . state ( getValue ( props . cachedPrivateKeyFile ) ?? null ) ;
108+ van . derive ( ( ) => {
109+ const fileInputValue = privateKeyFile . val ;
110+ if ( fileInputValue ?. content ) {
111+ privateKey . val = fileInputValue . content . split ( ',' ) ?. [ 1 ] ?? '' ;
112+ }
113+ } ) ;
114+ const clearPrivateKeyPhrase = van . state ( false ) ;
115+
100116 if ( isEditMode ) {
101117 connectionPassword . val = '' ;
102118 privateKey . val = '' ;
103119 privateKeyPhrase . val = '' ;
104120 }
105121
106- const connectionUrl = connection ?. url ?? '' ;
107- let connectionStringPrefix = van . state ( '' ) ;
108- let connectionStringSuffix = van . state ( connectionUrl ) ;
109- if ( connectionUrl . includes ( '@' ) ) {
110- const [ prefixPart , sufixPart ] = connectionUrl . split ( '@' ) ;
111- connectionStringPrefix = van . state ( prefixPart ) ;
112- connectionStringSuffix = van . state ( sufixPart ?? '' ) ;
122+ const flavor = getValue ( props . flavors ) . find ( f => f . value === connectionFlavor . val ) ;
123+ const originalURLTemplate = van . state ( flavor . connection_string ) ;
124+ const [ prefixPart , sufixPart ] = originalURLTemplate . val . split ( '@' ) ;
125+
126+ const connectionStringPrefix = van . state ( prefixPart ) ;
127+ const connectionStringSuffix = van . state ( connection ?. url ?? '' ) ;
128+ if ( ! connectionStringSuffix . val ) {
129+ connectionStringSuffix . val = formatURL ( sufixPart ?? '' , connectionHost . val , connectionPort . val , connectionDatabase . val ) ;
113130 }
114131
115132 const updatedConnection = van . derive ( ( ) => {
116- let privateKeyValue = privateKey . val ?? '' ;
117- if ( privateKeyValue ) {
118- privateKeyValue = privateKeyValue . content ?. split ( ',' ) ?. [ 1 ] ?? '' ;
119- }
120-
121133 return {
122134 project_code : connection . project_code ,
123135 connection_id : connection . connection_id ,
@@ -134,8 +146,8 @@ const ConnectionForm = (props, saveButton) => {
134146 connect_by_url : connectByUrl . val ?? false ,
135147 url : connectionStringSuffix . val ,
136148 connect_by_key : connectByKey . val ?? false ,
137- private_key : privateKeyValue ,
138- private_key_passphrase : privateKeyPhrase . val ?? '' ,
149+ private_key : privateKey . val ?? '' ,
150+ private_key_passphrase : clearPrivateKeyPhrase . val ? clearSentinel : ( privateKeyPhrase . val ?? '' ) ,
139151 http_path : httpPath . val ?? '' ,
140152 } ;
141153 } ) ;
@@ -146,7 +158,7 @@ const ConnectionForm = (props, saveButton) => {
146158 const fieldsValidity = validityPerField . val ;
147159 const isValid = Object . keys ( fieldsValidity ) . length > 0 &&
148160 Object . values ( fieldsValidity ) . every ( v => v ) ;
149- props . onChange ?. ( updatedConnection . val , { dirty : dirty . val , valid : isValid } ) ;
161+ props . onChange ?. ( updatedConnection . val , { dirty : dirty . val , valid : isValid } , { privateKey : privateKeyFile . rawVal } ) ;
150162 } ) ;
151163
152164 const setFieldValidity = ( field , validity ) => {
@@ -185,12 +197,13 @@ const ConnectionForm = (props, saveButton) => {
185197 connection ,
186198 connectByKey ,
187199 connectionPassword ,
188- privateKey ,
200+ privateKeyFile ,
189201 privateKeyPhrase ,
202+ clearPrivateKeyPhrase ,
190203 ( value , state ) => {
191204 connectByKey . val = value . connect_by_key ;
192205 connectionPassword . val = value . password ;
193- privateKey . val = value . private_key ;
206+ privateKeyFile . val = value . private_key ;
194207 privateKeyPhrase . val = value . private_key_passphrase ;
195208 setFieldValidity ( 'key_pair_form' , state . valid ) ;
196209 } ,
@@ -223,6 +236,20 @@ const ConnectionForm = (props, saveButton) => {
223236 }
224237 } ) ;
225238
239+ van . derive ( ( ) => {
240+ const connectionHost_ = connectionHost . val ;
241+ const connectionPort_ = connectionPort . val ;
242+ const connectionDatabase_ = connectionDatabase . val ;
243+ const connectionHttpPath_ = httpPath . val ;
244+ const urlTemplate = originalURLTemplate . val ;
245+
246+ if ( ! connectByUrl . rawVal && urlTemplate . includes ( '@' ) ) {
247+ const [ originalURLPrefix , originalURLSuffix ] = urlTemplate . split ( '@' ) ;
248+ connectionStringPrefix . val = originalURLPrefix ;
249+ connectionStringSuffix . val = formatURL ( originalURLSuffix , connectionHost_ , connectionPort_ , connectionDatabase_ , connectionHttpPath_ ) ;
250+ }
251+ } ) ;
252+
226253 return div (
227254 { class : 'flex-column fx-gap-3 fx-align-stretch' , style : 'overflow-y: auto;' } ,
228255 div (
@@ -237,7 +264,10 @@ const ConnectionForm = (props, saveButton) => {
237264 height : 38 ,
238265 help : 'Type of database server to connect to. This determines the database driver and SQL dialect that will be used by TestGen.' ,
239266 testId : 'sql_flavor' ,
240- onChange : ( value ) => connectionFlavor . val = value ,
267+ onChange : ( value ) => {
268+ const flavor = getValue ( props . flavors ) . find ( f => f . value === value ) ;
269+ originalURLTemplate . val = flavor . connection_string ;
270+ } ,
241271 } ) ,
242272 Input ( {
243273 name : 'connection_name' ,
@@ -340,10 +370,6 @@ const ConnectionForm = (props, saveButton) => {
340370 return '' ;
341371 }
342372
343- if ( connectionStringPrefix . val === '' ) {
344- connectionStringPrefix . val = `${ connectionFlavor . rawVal } ://<username>:<password>` ;
345- }
346-
347373 return div (
348374 { class : 'flex-row fx-gap-3 fx-align-stretch' } ,
349375 Input ( {
@@ -466,6 +492,7 @@ const KeyPairConnectionForm = (
466492 password ,
467493 privateKey ,
468494 privateKeyPhrase ,
495+ clearPrivateKeyPhrase ,
469496 onValueChange ,
470497 useSecretsPlaceholder ,
471498) => {
@@ -512,16 +539,41 @@ const KeyPairConnectionForm = (
512539 if ( connectByKey . val ) {
513540 return div (
514541 { class : 'flex-column fx-gap-3' } ,
515- Input ( {
516- name : 'private_key_passphrase' ,
517- label : 'Private Key Passphrase' ,
518- value : privateKeyPhrase ,
519- height : 38 ,
520- type : 'password' ,
521- help : 'Passphrase used when creating the private key. Leave empty if the private key is not encrypted.' ,
522- placeholder : ( useSecretsPlaceholder && connection . private_key_passphrase ) ? secretsPlaceholder : '' ,
523- onChange : ( value , state ) => privateKeyPhraseFieldState . val = { value, valid : state . valid } ,
524- } ) ,
542+ div (
543+ { class : 'key-pair-passphrase-field' } ,
544+ Input ( {
545+ name : 'private_key_passphrase' ,
546+ label : 'Private Key Passphrase' ,
547+ value : privateKeyPhrase ,
548+ height : 38 ,
549+ type : 'password' ,
550+ help : 'Passphrase used when creating the private key. Leave empty if the private key is not encrypted.' ,
551+ placeholder : ( ) => ( useSecretsPlaceholder && connection . private_key_passphrase && ! clearPrivateKeyPhrase . val ) ? secretsPlaceholder : '' ,
552+ onChange : ( value , state ) => {
553+ if ( value ) {
554+ clearPrivateKeyPhrase . val = false ;
555+ }
556+ privateKeyPhraseFieldState . val = { value, valid : state . valid } ;
557+ } ,
558+ } ) ,
559+ ( ) => {
560+ const hasPrivateKeyPhrase = connection . private_key_passphrase || privateKeyPhraseFieldState . val ?. value ;
561+ if ( ! hasPrivateKeyPhrase ) {
562+ return '' ;
563+ }
564+
565+ return i (
566+ {
567+ class : 'material-symbols-rounded clickable text-secondary' ,
568+ onclick : ( ) => {
569+ clearPrivateKeyPhrase . val = true ;
570+ privateKeyPhraseFieldState . val = { value : '' , valid : true } ;
571+ } ,
572+ } ,
573+ 'clear' ,
574+ ) ;
575+ } ,
576+ ) ,
525577 FileInput ( {
526578 name : 'private_key' ,
527579 label : 'Upload private key (rsa_key.p8)' ,
@@ -548,8 +600,25 @@ const KeyPairConnectionForm = (
548600 ) ;
549601} ;
550602
603+ function formatURL ( url , host , port , database , httpPath ) {
604+ return url . replace ( '<host>' , host )
605+ . replace ( '<port>' , port )
606+ . replace ( '<db_name>' , database )
607+ . replace ( '<http_path>' , httpPath ) ;
608+ }
609+
551610const stylesheet = new CSSStyleSheet ( ) ;
552611stylesheet . replace ( `
612+ .key-pair-passphrase-field {
613+ position: relative;
614+ }
615+
616+ .key-pair-passphrase-field > i {
617+ position: absolute;
618+ top: 26px;
619+ right: 8px;
620+ }
621+
553622` ) ;
554623
555624export { ConnectionForm } ;
0 commit comments