Skip to content

Commit e4becd5

Browse files
committed
feat(source-control): add dropdown to select HTTPS or SSH for repository connection
1 parent ecc4f41 commit e4becd5

File tree

1 file changed

+153
-18
lines changed

1 file changed

+153
-18
lines changed

packages/frontend/editor-ui/src/views/SettingsSourceControl.vue

Lines changed: 153 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import type { TupleToUnion } from '@/utils/typeHelpers';
1212
import type { Rule, RuleGroup } from '@n8n/design-system/types';
1313
import { useI18n } from '@n8n/i18n';
1414
import type { Validatable } from '@n8n/design-system';
15-
import { computed, onMounted, reactive, ref } from 'vue';
15+
import { computed, onMounted, reactive, ref, watch } from 'vue';
1616
import { I18nT } from 'vue-i18n';
1717
1818
const locale = useI18n();
@@ -24,20 +24,37 @@ const documentTitle = useDocumentTitle();
2424
const loadingService = useLoadingService();
2525
2626
const isConnected = ref(false);
27+
const connectionType = ref<'ssh' | 'https'>('ssh');
28+
const httpsUsername = ref('');
29+
const httpsPassword = ref('');
30+
2731
const branchNameOptions = computed(() =>
2832
sourceControlStore.preferences.branches.map((branch) => ({
2933
value: branch,
3034
label: branch,
3135
})),
3236
);
3337
38+
const connectionTypeOptions = [
39+
{ value: 'ssh', label: 'SSH' },
40+
{ value: 'https', label: 'HTTPS' },
41+
];
42+
3443
const onConnect = async () => {
3544
loadingService.startLoading();
3645
loadingService.setLoadingText(locale.baseText('settings.sourceControl.loading.connecting'));
3746
try {
38-
await sourceControlStore.savePreferences({
47+
const connectionData: any = {
3948
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);
4158
await sourceControlStore.getBranches();
4259
isConnected.value = true;
4360
toast.showMessage({
@@ -66,6 +83,8 @@ const onDisconnect = async () => {
6683
loadingService.startLoading();
6784
await sourceControlStore.disconnect(true);
6885
isConnected.value = false;
86+
httpsUsername.value = '';
87+
httpsPassword.value = '';
6988
toast.showMessage({
7089
title: locale.baseText('settings.sourceControl.toast.disconnected.title'),
7190
message: locale.baseText('settings.sourceControl.toast.disconnected.message'),
@@ -111,6 +130,8 @@ const initialize = async () => {
111130
await sourceControlStore.getPreferences();
112131
if (sourceControlStore.preferences.connected) {
113132
isConnected.value = true;
133+
// Load connection type from preferences
134+
connectionType.value = sourceControlStore.preferences.connectionType || 'ssh';
114135
void sourceControlStore.getBranches();
115136
}
116137
};
@@ -124,27 +145,55 @@ onMounted(async () => {
124145
const formValidationStatus = reactive<Record<string, boolean>>({
125146
repoUrl: false,
126147
keyGeneratorType: false,
148+
httpsUsername: false,
149+
httpsPassword: false,
127150
});
128151
129152
function onValidate(key: string, value: boolean) {
130153
formValidationStatus[key] = value;
131154
}
132155
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+
});
144181
145182
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+
});
146196
147-
const validForConnection = computed(() => formValidationStatus.repoUrl);
148197
const branchNameValidationRules: Array<Rule | RuleGroup> = [{ name: 'REQUIRED' }];
149198
150199
async function refreshSshKey() {
@@ -189,6 +238,19 @@ const onSelectSshKeyType = (value: Validatable) => {
189238
}
190239
sourceControlStore.preferences.keyGeneratorType = sshKeyType;
191240
};
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+
});
192254
</script>
193255

194256
<template>
@@ -212,8 +274,26 @@ const onSelectSshKeyType = (value: Validatable) => {
212274
<n8n-heading size="xlarge" tag="h2" class="mb-s">{{
213275
locale.baseText('settings.sourceControl.gitConfig')
214276
}}</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 -->
215293
<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>
217297
<div :class="$style.groupFlex">
218298
<n8n-form-input
219299
id="repoUrl"
@@ -224,7 +304,11 @@ const onSelectSshKeyType = (value: Validatable) => {
224304
validate-on-blur
225305
:validation-rules="repoUrlValidationRules"
226306
: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+
"
228312
@validate="(value: boolean) => onValidate('repoUrl', value)"
229313
/>
230314
<n8n-button
@@ -238,8 +322,56 @@ const onSelectSshKeyType = (value: Validatable) => {
238322
>{{ locale.baseText('settings.sourceControl.button.disconnect') }}</n8n-button
239323
>
240324
</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>
241331
</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+
>
243375
<label>{{ locale.baseText('settings.sourceControl.sshKey') }}</label>
244376
<div :class="{ [$style.sshInput]: !isConnected }">
245377
<n8n-form-input
@@ -287,6 +419,7 @@ const onSelectSshKeyType = (value: Validatable) => {
287419
</I18nT>
288420
</n8n-notice>
289421
</div>
422+
290423
<n8n-button
291424
v-if="!isConnected"
292425
size="large"
@@ -296,6 +429,8 @@ const onSelectSshKeyType = (value: Validatable) => {
296429
@click="onConnect"
297430
>{{ locale.baseText('settings.sourceControl.button.connect') }}</n8n-button
298431
>
432+
433+
<!-- Connected Settings -->
299434
<div v-if="isConnected" data-test-id="source-control-connected-content">
300435
<div :class="$style.group">
301436
<hr />

0 commit comments

Comments
 (0)