diff --git a/apps/desktop/src/components/connection/ConnectionDialog.vue b/apps/desktop/src/components/connection/ConnectionDialog.vue index 86526eb3fc..c0492a64f0 100644 --- a/apps/desktop/src/components/connection/ConnectionDialog.vue +++ b/apps/desktop/src/components/connection/ConnectionDialog.vue @@ -20,6 +20,7 @@ import type { NacosAdminConfig, NacosAuthConfig } from "@/types/nacos"; import { CONNECTION_ATTEMPT_CANCELLED_MESSAGE, useConnectionStore } from "@/stores/connectionStore"; import { useTunnelProfileStore } from "@/stores/tunnelProfileStore"; import { detachTunnelProfileLayer, tunnelProfileReferenceLayer, tunnelProfileSummary } from "@/lib/connection/tunnelProfiles"; +import { applySshConfigHostAliasPrefill as prefillSshConfigHostAlias } from "@/lib/connection/sshConfigHosts"; import { REDIS_SCAN_PAGE_SIZE_DEFAULT, REDIS_SCAN_PAGE_SIZE_MIN, REDIS_SCAN_PAGE_SIZE_MAX, REDIS_SCAN_PAGE_SIZE_OPTIONS } from "@/lib/redis/redisKeyPattern"; import { useSettingsStore } from "@/stores/settingsStore"; import { useToast } from "@/composables/useToast"; @@ -3480,24 +3481,8 @@ watch([() => editingId.value, () => open.value], () => { const sshConfigHostAliases = computed(() => sshConfigHosts.value.map((entry) => entry.alias)); -/** - * Prefills user/port/key_path from a matching ~/.ssh/config alias, without - * overwriting values the user already changed away from the form defaults. - * This is a UX preview only — the authoritative resolution happens in the - * Rust backend at connect time (see resolve_ssh_tunnel_config), so imported - * configs that never touched this UI still resolve correctly. - */ function applySshConfigHostAliasPrefill(target: SshTunnelConfig) { - const entry = sshConfigHosts.value.find((candidate) => candidate.alias === target.host); - if (!entry) return; - if (target.user === DEFAULT_SSH_USER && entry.user) target.user = entry.user; - if (target.port === 22 && entry.port) target.port = entry.port; - if (!target.key_path && entry.identity_file) { - target.key_path = entry.identity_file; - if ((!target.auth_method || target.auth_method === "password") && !target.password?.trim()) { - target.auth_method = "key"; - } - } + prefillSshConfigHostAlias(target, sshConfigHosts.value); } async function browseSshKeyPath(target?: SshTunnelConfig | null) { diff --git a/apps/desktop/src/components/connection/TunnelProfileManager.vue b/apps/desktop/src/components/connection/TunnelProfileManager.vue index ec439a82ed..df740ec44e 100644 --- a/apps/desktop/src/components/connection/TunnelProfileManager.vue +++ b/apps/desktop/src/components/connection/TunnelProfileManager.vue @@ -10,7 +10,9 @@ import { Loader2, Plus, Trash2 } from "@lucide/vue"; import { useToast } from "@/composables/useToast"; import { useTunnelProfileStore } from "@/stores/tunnelProfileStore"; import { createTunnelProfile, createTunnelProfileTestGuard, tunnelProfileSummary, type TunnelProfileType } from "@/lib/connection/tunnelProfiles"; -import type { TunnelProfile } from "@/types/database"; +import { applySshConfigHostAliasPrefill as prefillSshConfigHostAlias } from "@/lib/connection/sshConfigHosts"; +import * as api from "@/lib/backend/api"; +import type { SshConfigHostEntry, TunnelProfile } from "@/types/database"; import { translateBackendError } from "@/i18n/backend-errors"; const { t } = useI18n(); @@ -24,6 +26,7 @@ const hasInitializedDraft = ref(false); const isTesting = ref(false); const testResult = ref<{ ok: boolean; message: string } | null>(null); const testGuard = createTunnelProfileTestGuard(); +const sshConfigHosts = ref([]); function cloneProfiles(profiles: TunnelProfile[]): TunnelProfile[] { return JSON.parse(JSON.stringify(profiles)) as TunnelProfile[]; @@ -56,6 +59,23 @@ const selected = computed(() => draft.value.find((profile) => profile.id === sel const selectedSsh = computed(() => (selected.value?.type === "ssh" ? selected.value : null)); const selectedProxy = computed(() => (selected.value?.type === "proxy" ? selected.value : null)); const selectedHttp = computed(() => (selected.value?.type === "http_tunnel" ? selected.value : null)); +const sshConfigHostAliases = computed(() => sshConfigHosts.value.map((entry) => entry.alias)); + +async function loadSshConfigHosts() { + try { + sshConfigHosts.value = await api.listSshConfigHosts(); + } catch { + sshConfigHosts.value = []; + } +} + +function updateSelectedSshHost(value: string | number) { + if (!selectedSsh.value) return; + selectedSsh.value.host = String(value); + prefillSshConfigHostAlias(selectedSsh.value, sshConfigHosts.value); +} + +void loadSshConfigHosts(); function invalidateProfileTest() { testGuard.invalidate(); @@ -190,7 +210,10 @@ async function testSelected() {