@@ -12,7 +12,7 @@ import type { TupleToUnion } from '@/utils/typeHelpers';
12
12
import type { Rule , RuleGroup } from ' @n8n/design-system/types' ;
13
13
import { useI18n } from ' @n8n/i18n' ;
14
14
import type { Validatable } from ' @n8n/design-system' ;
15
- import { computed , onMounted , reactive , ref } from ' vue' ;
15
+ import { computed , onMounted , reactive , ref , watch } from ' vue' ;
16
16
import { I18nT } from ' vue-i18n' ;
17
17
18
18
const locale = useI18n ();
@@ -24,20 +24,37 @@ const documentTitle = useDocumentTitle();
24
24
const loadingService = useLoadingService ();
25
25
26
26
const isConnected = ref (false );
27
+ const connectionType = ref <' ssh' | ' https' >(' ssh' );
28
+ const httpsUsername = ref (' ' );
29
+ const httpsPassword = ref (' ' );
30
+
27
31
const branchNameOptions = computed (() =>
28
32
sourceControlStore .preferences .branches .map ((branch ) => ({
29
33
value: branch ,
30
34
label: branch ,
31
35
})),
32
36
);
33
37
38
+ const connectionTypeOptions = [
39
+ { value: ' ssh' , label: ' SSH' },
40
+ { value: ' https' , label: ' HTTPS' },
41
+ ];
42
+
34
43
const onConnect = async () => {
35
44
loadingService .startLoading ();
36
45
loadingService .setLoadingText (locale .baseText (' settings.sourceControl.loading.connecting' ));
37
46
try {
38
- await sourceControlStore . savePreferences ( {
47
+ const connectionData : any = {
39
48
repositoryUrl: sourceControlStore .preferences .repositoryUrl ,
40
- });
49
+ connectionType: connectionType .value ,
50
+ };
51
+
52
+ if (connectionType .value === ' https' ) {
53
+ connectionData .username = httpsUsername .value ;
54
+ connectionData .password = httpsPassword .value ;
55
+ }
56
+
57
+ await sourceControlStore .savePreferences (connectionData );
41
58
await sourceControlStore .getBranches ();
42
59
isConnected .value = true ;
43
60
toast .showMessage ({
@@ -66,6 +83,8 @@ const onDisconnect = async () => {
66
83
loadingService .startLoading ();
67
84
await sourceControlStore .disconnect (true );
68
85
isConnected .value = false ;
86
+ httpsUsername .value = ' ' ;
87
+ httpsPassword .value = ' ' ;
69
88
toast .showMessage ({
70
89
title: locale .baseText (' settings.sourceControl.toast.disconnected.title' ),
71
90
message: locale .baseText (' settings.sourceControl.toast.disconnected.message' ),
@@ -111,6 +130,8 @@ const initialize = async () => {
111
130
await sourceControlStore .getPreferences ();
112
131
if (sourceControlStore .preferences .connected ) {
113
132
isConnected .value = true ;
133
+ // Load connection type from preferences
134
+ connectionType .value = sourceControlStore .preferences .connectionType || ' ssh' ;
114
135
void sourceControlStore .getBranches ();
115
136
}
116
137
};
@@ -124,27 +145,55 @@ onMounted(async () => {
124
145
const formValidationStatus = reactive <Record <string , boolean >>({
125
146
repoUrl: false ,
126
147
keyGeneratorType: false ,
148
+ httpsUsername: false ,
149
+ httpsPassword: false ,
127
150
});
128
151
129
152
function onValidate(key : string , value : boolean ) {
130
153
formValidationStatus [key ] = value ;
131
154
}
132
155
133
- const repoUrlValidationRules: Array <Rule | RuleGroup > = [
134
- { name: ' REQUIRED' },
135
- {
136
- name: ' MATCH_REGEX' ,
137
- config: {
138
- regex:
139
- / ^ (?:git@| ssh:\/\/ git@| [\w -] + @)(?:[\w. -] + | \[ [0-9a-fA-F :] + ])(?::\d + )? [:\/ ][\w \-~ . ] + (?:\/ [\w \-~ . ] + )* (?:\. git)? (?:\/ . * )? $ / ,
140
- message: locale .baseText (' settings.sourceControl.repoUrlInvalid' ),
141
- },
142
- },
143
- ];
156
+ // Dynamic validation rules based on connection type
157
+ const repoUrlValidationRules = computed <Array <Rule | RuleGroup >>(() => {
158
+ const baseRules: Array <Rule | RuleGroup > = [{ name: ' REQUIRED' }];
159
+
160
+ if (connectionType .value === ' ssh' ) {
161
+ baseRules .push ({
162
+ name: ' MATCH_REGEX' ,
163
+ config: {
164
+ regex:
165
+ / ^ (?:git@| ssh:\/\/ git@| [\w -] + @)(?:[\w. -] + | \[ [0-9a-fA-F :] + ])(?::\d + )? [:\/ ][\w \-~ . ] + (?:\/ [\w \-~ . ] + )* (?:\. git)? (?:\/ . * )? $ / ,
166
+ message: locale .baseText (' settings.sourceControl.repoUrlInvalid' ),
167
+ },
168
+ });
169
+ } else {
170
+ baseRules .push ({
171
+ name: ' MATCH_REGEX' ,
172
+ config: {
173
+ regex: / ^ https? :\/\/ . + $ / ,
174
+ message: ' Please enter a valid HTTPS URL' ,
175
+ },
176
+ });
177
+ }
178
+
179
+ return baseRules ;
180
+ });
144
181
145
182
const keyGeneratorTypeValidationRules: Array <Rule | RuleGroup > = [{ name: ' REQUIRED' }];
183
+ const httpsCredentialValidationRules: Array <Rule | RuleGroup > = [{ name: ' REQUIRED' }];
184
+
185
+ const validForConnection = computed (() => {
186
+ if (connectionType .value === ' ssh' ) {
187
+ return formValidationStatus .repoUrl ;
188
+ } else {
189
+ return (
190
+ formValidationStatus .repoUrl &&
191
+ formValidationStatus .httpsUsername &&
192
+ formValidationStatus .httpsPassword
193
+ );
194
+ }
195
+ });
146
196
147
- const validForConnection = computed (() => formValidationStatus .repoUrl );
148
197
const branchNameValidationRules: Array <Rule | RuleGroup > = [{ name: ' REQUIRED' }];
149
198
150
199
async function refreshSshKey() {
@@ -189,6 +238,19 @@ const onSelectSshKeyType = (value: Validatable) => {
189
238
}
190
239
sourceControlStore .preferences .keyGeneratorType = sshKeyType ;
191
240
};
241
+
242
+ // Watch connection type changes to reset validation
243
+ watch (connectionType , () => {
244
+ // Reset validation status when switching connection types
245
+ formValidationStatus .repoUrl = false ;
246
+ formValidationStatus .httpsUsername = false ;
247
+ formValidationStatus .httpsPassword = false ;
248
+
249
+ // Clear repository URL if switching types and not connected
250
+ if (! isConnected .value ) {
251
+ sourceControlStore .preferences .repositoryUrl = ' ' ;
252
+ }
253
+ });
192
254
</script >
193
255
194
256
<template >
@@ -212,8 +274,26 @@ const onSelectSshKeyType = (value: Validatable) => {
212
274
<n8n-heading size =" xlarge" tag =" h2" class =" mb-s" >{{
213
275
locale.baseText('settings.sourceControl.gitConfig')
214
276
}}</n8n-heading >
277
+
278
+ <!-- Connection Type Selector -->
279
+ <div v-if =" !isConnected" :class =" $style.group" >
280
+ <label for =" connectionType" >Connection Type</label >
281
+ <n8n-form-input
282
+ id =" connectionType"
283
+ v-model =" connectionType"
284
+ label =" "
285
+ type =" select"
286
+ name =" connectionType"
287
+ :options =" connectionTypeOptions"
288
+ data-test-id =" source-control-connection-type-select"
289
+ />
290
+ </div >
291
+
292
+ <!-- Repository URL -->
215
293
<div :class =" $style.group" >
216
- <label for =" repoUrl" >{{ locale.baseText('settings.sourceControl.repoUrl') }}</label >
294
+ <label for =" repoUrl" >
295
+ {{ connectionType === 'ssh' ? 'SSH Repository URL' : 'HTTPS Repository URL' }}
296
+ </label >
217
297
<div :class =" $style.groupFlex" >
218
298
<n8n-form-input
219
299
id =" repoUrl"
@@ -224,7 +304,11 @@ const onSelectSshKeyType = (value: Validatable) => {
224
304
validate-on-blur
225
305
:validation-rules =" repoUrlValidationRules"
226
306
:disabled =" isConnected"
227
- :placeholder =" locale.baseText('settings.sourceControl.repoUrlPlaceholder')"
307
+ :placeholder ="
308
+ connectionType === 'ssh'
309
+ ? 'git@github.com:user/repository.git'
310
+ : 'https://github.com/user/repository.git'
311
+ "
228
312
@validate =" (value: boolean) => onValidate('repoUrl', value)"
229
313
/>
230
314
<n8n-button
@@ -238,8 +322,56 @@ const onSelectSshKeyType = (value: Validatable) => {
238
322
>{{ locale.baseText('settings.sourceControl.button.disconnect') }}</n8n-button
239
323
>
240
324
</div >
325
+ <n8n-notice v-if =" !isConnected && connectionType === 'ssh'" type =" info" class =" mt-s" >
326
+ Use SSH format: git@github.com:user/repository.git
327
+ </n8n-notice >
328
+ <n8n-notice v-if =" !isConnected && connectionType === 'https'" type =" info" class =" mt-s" >
329
+ Use HTTPS format: https://github.com/user/repository.git
330
+ </n8n-notice >
241
331
</div >
242
- <div v-if =" sourceControlStore.preferences.publicKey" :class =" $style.group" >
332
+
333
+ <!-- HTTPS Credentials -->
334
+ <div v-if =" connectionType === 'https' && !isConnected" :class =" $style.group" >
335
+ <label for =" httpsUsername" >Username / Access Token</label >
336
+ <n8n-form-input
337
+ id =" httpsUsername"
338
+ v-model =" httpsUsername"
339
+ label =" "
340
+ name =" httpsUsername"
341
+ type =" text"
342
+ validate-on-blur
343
+ :validation-rules =" httpsCredentialValidationRules"
344
+ placeholder =" Enter your username or personal access token"
345
+ @validate =" (value: boolean) => onValidate('httpsUsername', value)"
346
+ />
347
+ <n8n-notice type =" info" class =" mt-s" >
348
+ For GitHub, use a Personal Access Token instead of username
349
+ </n8n-notice >
350
+ </div >
351
+
352
+ <div v-if =" connectionType === 'https' && !isConnected" :class =" $style.group" >
353
+ <label for =" httpsPassword" >Password / Token Secret</label >
354
+ <n8n-form-input
355
+ id =" httpsPassword"
356
+ v-model =" httpsPassword"
357
+ label =" "
358
+ name =" httpsPassword"
359
+ type =" password"
360
+ validate-on-blur
361
+ :validation-rules =" httpsCredentialValidationRules"
362
+ placeholder =" Enter your password or token secret"
363
+ @validate =" (value: boolean) => onValidate('httpsPassword', value)"
364
+ />
365
+ <n8n-notice type =" warning" class =" mt-s" >
366
+ Credentials will be securely stored and encrypted
367
+ </n8n-notice >
368
+ </div >
369
+
370
+ <!-- SSH Key (only for SSH connection) -->
371
+ <div
372
+ v-if =" connectionType === 'ssh' && sourceControlStore.preferences.publicKey"
373
+ :class =" $style.group"
374
+ >
243
375
<label >{{ locale.baseText('settings.sourceControl.sshKey') }}</label >
244
376
<div :class =" { [$style.sshInput]: !isConnected }" >
245
377
<n8n-form-input
@@ -287,6 +419,7 @@ const onSelectSshKeyType = (value: Validatable) => {
287
419
</I18nT >
288
420
</n8n-notice >
289
421
</div >
422
+
290
423
<n8n-button
291
424
v-if =" !isConnected"
292
425
size =" large"
@@ -296,6 +429,8 @@ const onSelectSshKeyType = (value: Validatable) => {
296
429
@click =" onConnect"
297
430
>{{ locale.baseText('settings.sourceControl.button.connect') }}</n8n-button
298
431
>
432
+
433
+ <!-- Connected Settings -->
299
434
<div v-if =" isConnected" data-test-id =" source-control-connected-content" >
300
435
<div :class =" $style.group" >
301
436
<hr />
0 commit comments