diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index 8b0a607d60e61..7988eaa838947 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -1,4 +1,10 @@ -import type { CREDENTIAL_EDIT_MODAL_KEY } from './constants'; +import type { + CREDENTIAL_EDIT_MODAL_KEY, + SignInType, + FAKE_DOOR_FEATURES, + TRIGGER_NODE_CREATOR_VIEW, + REGULAR_NODE_CREATOR_VIEW, +} from './constants'; import type { IMenuItem } from 'n8n-design-system'; import type { @@ -35,16 +41,10 @@ import type { IUserSettings, IN8nUISettings, BannerName, + INodeProperties, } from 'n8n-workflow'; -import type { SignInType } from './constants'; -import type { - FAKE_DOOR_FEATURES, - TRIGGER_NODE_CREATOR_VIEW, - REGULAR_NODE_CREATOR_VIEW, -} from './constants'; import type { BulkCommand, Undoable } from '@/models/history'; -import type { PartialBy } from '@/utils/typeHelpers'; -import type { INodeProperties } from 'n8n-workflow'; +import type { PartialBy, TupleToUnion } from '@/utils/typeHelpers'; export * from 'n8n-design-system/types'; @@ -734,11 +734,10 @@ export type ActionsRecord = { [K in ExtractActionKeys]: ActionTypeDescription[]; }; -export interface SimplifiedNodeType - extends Pick< - INodeTypeDescription, - 'displayName' | 'description' | 'name' | 'group' | 'icon' | 'iconUrl' | 'codex' | 'defaults' - > {} +export type SimplifiedNodeType = Pick< + INodeTypeDescription, + 'displayName' | 'description' | 'name' | 'group' | 'icon' | 'iconUrl' | 'codex' | 'defaults' +>; export interface SubcategoryItemProps { description?: string; iconType?: string; @@ -1462,6 +1461,8 @@ export type SamlPreferencesExtractedData = { returnUrl: string; }; +export type SshKeyTypes = ['ed25519', 'rsa']; + export type SourceControlPreferences = { connected: boolean; repositoryUrl: string; @@ -1470,6 +1471,7 @@ export type SourceControlPreferences = { branchReadOnly: boolean; branchColor: string; publicKey?: string; + keyGeneratorType?: TupleToUnion; currentBranch?: string; }; diff --git a/packages/editor-ui/src/__tests__/server/endpoints/sourceControl.ts b/packages/editor-ui/src/__tests__/server/endpoints/sourceControl.ts index 6dc7f2f07cdc3..26d4f93fd241d 100644 --- a/packages/editor-ui/src/__tests__/server/endpoints/sourceControl.ts +++ b/packages/editor-ui/src/__tests__/server/endpoints/sourceControl.ts @@ -14,6 +14,7 @@ export function routesForSourceControl(server: Server) { branchColor: '#1d6acb', connected: false, publicKey: 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHEX+25m', + keyGeneratorType: 'ed25519', }; server.get(`${sourceControlApiRoot}/preferences`, (schema: AppSchema, request: Request) => { diff --git a/packages/editor-ui/src/api/sourceControl.ts b/packages/editor-ui/src/api/sourceControl.ts index 144839b156e8b..13c61ef590b98 100644 --- a/packages/editor-ui/src/api/sourceControl.ts +++ b/packages/editor-ui/src/api/sourceControl.ts @@ -3,9 +3,11 @@ import type { SourceControlAggregatedFile, SourceControlPreferences, SourceControlStatus, + SshKeyTypes, } from '@/Interface'; import { makeRestApiRequest } from '@/utils'; import type { IDataObject } from 'n8n-workflow'; +import type { TupleToUnion } from '@/utils/typeHelpers'; const sourceControlApiRoot = '/source-control'; @@ -70,6 +72,11 @@ export const disconnect = async ( }); }; -export const generateKeyPair = async (context: IRestApiContext): Promise => { - return makeRestApiRequest(context, 'POST', `${sourceControlApiRoot}/generate-key-pair`); +export const generateKeyPair = async ( + context: IRestApiContext, + keyGeneratorType?: TupleToUnion, +): Promise => { + return makeRestApiRequest(context, 'POST', `${sourceControlApiRoot}/generate-key-pair`, { + keyGeneratorType, + }); }; diff --git a/packages/editor-ui/src/stores/sourceControl.store.ts b/packages/editor-ui/src/stores/sourceControl.store.ts index aad181f1d4fa3..d3264ef2cc1cb 100644 --- a/packages/editor-ui/src/stores/sourceControl.store.ts +++ b/packages/editor-ui/src/stores/sourceControl.store.ts @@ -3,25 +3,22 @@ import { defineStore } from 'pinia'; import { EnterpriseEditionFeature } from '@/constants'; import { useSettingsStore } from '@/stores/settings.store'; import { useRootStore } from '@/stores/n8nRoot.store'; -import { useUsersStore } from '@/stores/users.store'; import * as vcApi from '@/api/sourceControl'; -import type { SourceControlPreferences } from '@/Interface'; +import type { SourceControlPreferences, SshKeyTypes } from '@/Interface'; +import type { TupleToUnion } from '@/utils/typeHelpers'; export const useSourceControlStore = defineStore('sourceControl', () => { const rootStore = useRootStore(); const settingsStore = useSettingsStore(); - const usersStore = useUsersStore(); const isEnterpriseSourceControlEnabled = computed(() => settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.SourceControl), ); - const defaultAuthor = computed(() => { - const user = usersStore.currentUser; - return { - name: user?.fullName ?? `${user?.firstName} ${user?.lastName}`.trim(), - email: user?.email ?? '', - }; - }); + + const sshKeyTypes: SshKeyTypes = ['ed25519', 'rsa']; + const sshKeyTypesWithLabel = reactive( + sshKeyTypes.map((value) => ({ value, label: value.toUpperCase() })), + ); const preferences = reactive({ branchName: '', @@ -31,6 +28,7 @@ export const useSourceControlStore = defineStore('sourceControl', () => { branchColor: '#5296D6', connected: false, publicKey: '', + keyGeneratorType: 'ed25519', }); const state = reactive<{ @@ -95,8 +93,8 @@ export const useSourceControlStore = defineStore('sourceControl', () => { setPreferences({ connected: false, branches: [] }); }; - const generateKeyPair = async () => { - await vcApi.generateKeyPair(rootStore.getRestApiContext); + const generateKeyPair = async (keyGeneratorType?: TupleToUnion) => { + await vcApi.generateKeyPair(rootStore.getRestApiContext, keyGeneratorType); const data = await vcApi.getPreferences(rootStore.getRestApiContext); // To be removed once the API is updated preferences.publicKey = data.publicKey; @@ -127,5 +125,6 @@ export const useSourceControlStore = defineStore('sourceControl', () => { disconnect, getStatus, getAggregatedStatus, + sshKeyTypesWithLabel, }; }); diff --git a/packages/editor-ui/src/utils/typeHelpers.ts b/packages/editor-ui/src/utils/typeHelpers.ts index ce7e0b412773a..a655884737a08 100644 --- a/packages/editor-ui/src/utils/typeHelpers.ts +++ b/packages/editor-ui/src/utils/typeHelpers.ts @@ -1 +1,2 @@ export type PartialBy = Omit & Partial>; +export type TupleToUnion = T[number]; diff --git a/packages/editor-ui/src/views/SettingsSourceControl.vue b/packages/editor-ui/src/views/SettingsSourceControl.vue index c6539a83f0e3f..b455b8dfc6171 100644 --- a/packages/editor-ui/src/views/SettingsSourceControl.vue +++ b/packages/editor-ui/src/views/SettingsSourceControl.vue @@ -5,6 +5,8 @@ import { MODAL_CONFIRM } from '@/constants'; import { useUIStore, useSourceControlStore } from '@/stores'; import { useToast, useMessage, useLoadingService, useI18n } from '@/composables'; import CopyInput from '@/components/CopyInput.vue'; +import type { TupleToUnion } from '@/utils/typeHelpers'; +import type { SshKeyTypes } from '@/Interface'; const locale = useI18n(); const sourceControlStore = useSourceControlStore(); @@ -111,6 +113,7 @@ onMounted(async () => { const formValidationStatus = reactive>({ repoUrl: false, + keyGeneratorType: false, }); function onValidate(key: string, value: boolean) { @@ -129,6 +132,8 @@ const repoUrlValidationRules: Array = [ }, ]; +const keyGeneratorTypeValidationRules: Array = [{ name: 'REQUIRED' }]; + const validForConnection = computed(() => formValidationStatus.repoUrl); const branchNameValidationRules: Array = [{ name: 'REQUIRED' }]; @@ -144,7 +149,7 @@ async function refreshSshKey() { ); if (confirmation === MODAL_CONFIRM) { - await sourceControlStore.generateKeyPair(); + await sourceControlStore.generateKeyPair(sourceControlStore.preferences.keyGeneratorType); toast.showMessage({ title: locale.baseText('settings.sourceControl.refreshSshKey.successful.title'), type: 'success', @@ -166,6 +171,13 @@ const refreshBranches = async () => { toast.showError(error, locale.baseText('settings.sourceControl.refreshBranches.error')); } }; + +const onSelectSshKeyType = async (sshKeyType: TupleToUnion) => { + if (sshKeyType === sourceControlStore.preferences.keyGeneratorType) { + return; + } + sourceControlStore.preferences.keyGeneratorType = sshKeyType; +};