diff --git a/packages/backend/src/database/schema.ts b/packages/backend/src/database/schema.ts index b97af0e..d1cc55d 100644 --- a/packages/backend/src/database/schema.ts +++ b/packages/backend/src/database/schema.ts @@ -76,6 +76,7 @@ export const createConnectionsTableSQL = ` CREATE TABLE IF NOT EXISTS connections ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NULL, -- 允许 name 为空 + type TEXT NOT NULL CHECK(type IN ('SSH', 'RDP')) DEFAULT 'SSH', host TEXT NOT NULL, port INTEGER NOT NULL, username TEXT NOT NULL, diff --git a/packages/backend/src/repositories/connection.repository.ts b/packages/backend/src/repositories/connection.repository.ts index 17343af..d10c6b3 100644 --- a/packages/backend/src/repositories/connection.repository.ts +++ b/packages/backend/src/repositories/connection.repository.ts @@ -7,6 +7,7 @@ import { getDbInstance, runDb, getDb as getDbRow, allDb } from '../database/conn interface ConnectionBase { id: number; name: string | null; + type: 'SSH' | 'RDP'; // Add type field host: string; port: number; username: string; @@ -17,15 +18,18 @@ interface ConnectionBase { last_connected_at: number | null; } +// ConnectionWithTagsRow implicitly includes 'type' via ConnectionBase interface ConnectionWithTagsRow extends ConnectionBase { - tag_ids_str: string | null; + tag_ids_str: string | null; } +// ConnectionWithTags implicitly includes 'type' via ConnectionBase export interface ConnectionWithTags extends ConnectionBase { tag_ids: number[]; } // 包含加密字段的完整类型,用于插入/更新 +// FullConnectionData implicitly includes 'type' via ConnectionBase export interface FullConnectionData extends ConnectionBase { encrypted_password?: string | null; encrypted_private_key?: string | null; @@ -34,6 +38,7 @@ export interface FullConnectionData extends ConnectionBase { } +// FullConnectionDbRow implicitly includes 'type' via FullConnectionData interface FullConnectionDbRow extends FullConnectionData { proxy_db_id: number | null; proxy_name: string | null; @@ -53,7 +58,7 @@ interface FullConnectionDbRow extends FullConnectionData { export const findAllConnectionsWithTags = async (): Promise => { const sql = ` SELECT - c.id, c.name, c.host, c.port, c.username, c.auth_method, c.proxy_id, + c.id, c.name, c.type, c.host, c.port, c.username, c.auth_method, c.proxy_id, c.created_at, c.updated_at, c.last_connected_at, GROUP_CONCAT(ct.tag_id) as tag_ids_str FROM connections c @@ -79,7 +84,7 @@ export const findAllConnectionsWithTags = async (): Promise => { const sql = ` SELECT - c.id, c.name, c.host, c.port, c.username, c.auth_method, c.proxy_id, + c.id, c.name, c.type, c.host, c.port, c.username, c.auth_method, c.proxy_id, c.created_at, c.updated_at, c.last_connected_at, GROUP_CONCAT(ct.tag_id) as tag_ids_str FROM connections c @@ -132,13 +137,15 @@ export const findFullConnectionById = async (id: number): Promise): Promise => { const now = Math.floor(Date.now() / 1000); const sql = ` - INSERT INTO connections (name, host, port, username, auth_method, encrypted_password, encrypted_private_key, encrypted_passphrase, proxy_id, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`; + INSERT INTO connections (name, type, host, port, username, auth_method, encrypted_password, encrypted_private_key, encrypted_passphrase, proxy_id, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`; // Add type column and placeholder const params = [ data.name ?? null, + data.type, // Add type parameter data.host, data.port, data.username, data.auth_method, data.encrypted_password ?? null, data.encrypted_private_key ?? null, data.encrypted_passphrase ?? null, data.proxy_id ?? null, @@ -160,6 +167,7 @@ export const createConnection = async (data: Omit>): Promise => { const fieldsToUpdate: { [key: string]: any } = { ...data }; const params: any[] = []; @@ -270,17 +278,18 @@ export const updateConnectionTags = async (connectionId: number, tagIds: number[ */ export const bulkInsertConnections = async ( db: Database, + // Update input type to reflect FullConnectionData now has 'type' connections: Array & { tag_ids?: number[] }> ): Promise<{ connectionId: number, originalData: any }[]> => { - const insertConnSql = `INSERT INTO connections (name, host, port, username, auth_method, encrypted_password, encrypted_private_key, encrypted_passphrase, proxy_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`; + const insertConnSql = `INSERT INTO connections (name, type, host, port, username, auth_method, encrypted_password, encrypted_private_key, encrypted_passphrase, proxy_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`; // Add type column and placeholder const results: { connectionId: number, originalData: any }[] = []; const now = Math.floor(Date.now() / 1000); for (const connData of connections) { const params = [ - connData.name ?? null, connData.host, connData.port, connData.username, connData.auth_method, + connData.name ?? null, connData.type, connData.host, connData.port, connData.username, connData.auth_method, // Add type parameter connData.encrypted_password || null, connData.encrypted_private_key || null, connData.encrypted_passphrase || null, diff --git a/packages/backend/src/services/connection.service.ts b/packages/backend/src/services/connection.service.ts index 802d1db..3234a7a 100644 --- a/packages/backend/src/services/connection.service.ts +++ b/packages/backend/src/services/connection.service.ts @@ -18,57 +18,93 @@ const auditLogService = new AuditLogService(); // 实例化 AuditLogService * 获取所有连接(包含标签) */ export const getAllConnections = async (): Promise => { - return ConnectionRepository.findAllConnectionsWithTags(); + // Repository now returns ConnectionWithTags including 'type' + // Explicit type assertion to ensure compatibility + return ConnectionRepository.findAllConnectionsWithTags() as Promise; }; /** * 根据 ID 获取单个连接(包含标签) */ export const getConnectionById = async (id: number): Promise => { - return ConnectionRepository.findConnectionByIdWithTags(id); + // Repository now returns ConnectionWithTags including 'type' + // Explicit type assertion to ensure compatibility + return ConnectionRepository.findConnectionByIdWithTags(id) as Promise; }; /** * 创建新连接 */ export const createConnection = async (input: CreateConnectionInput): Promise => { - // 1. 验证输入 - if (!input.host || !input.username || !input.auth_method) { - throw new Error('缺少必要的连接信息 (host, username, auth_method)。'); + console.log('[Service:createConnection] Received input:', JSON.stringify(input, null, 2)); // Log input + // 1. 验证输入 (包含 type) + // Convert type to uppercase for validation and consistency + const connectionType = input.type?.toUpperCase() as 'SSH' | 'RDP' | undefined; // Ensure type safety + if (!connectionType || !['SSH', 'RDP'].includes(connectionType)) { + throw new Error('必须提供有效的连接类型 (SSH 或 RDP)。'); } - if (input.auth_method === 'password' && !input.password) { - throw new Error('密码认证方式需要提供 password。'); + if (!input.host || !input.username) { + throw new Error('缺少必要的连接信息 (host, username)。'); } - if (input.auth_method === 'key' && !input.private_key) { - throw new Error('密钥认证方式需要提供 private_key。'); + // Type-specific validation using the uppercase version + if (connectionType === 'SSH') { + if (!input.auth_method || !['password', 'key'].includes(input.auth_method)) { + throw new Error('SSH 连接必须提供有效的认证方式 (password 或 key)。'); + } + if (input.auth_method === 'password' && !input.password) { + throw new Error('SSH 密码认证方式需要提供 password。'); + } + if (input.auth_method === 'key' && !input.private_key) { + throw new Error('SSH 密钥认证方式需要提供 private_key。'); + } + } else if (connectionType === 'RDP') { + if (!input.password) { + throw new Error('RDP 连接需要提供 password。'); + } + // For RDP, we'll ignore auth_method, private_key, passphrase from input if provided } - // 2. 加密凭证 + // 2. 加密凭证 (根据 type) let encryptedPassword = null; let encryptedPrivateKey = null; let encryptedPassphrase = null; + // Default to 'password' for DB compatibility, especially for RDP + let authMethodForDb: 'password' | 'key' = 'password'; - if (input.auth_method === 'password') { - encryptedPassword = encrypt(input.password!); - } else if (input.auth_method === 'key') { - encryptedPrivateKey = encrypt(input.private_key!); - if (input.passphrase) { - encryptedPassphrase = encrypt(input.passphrase); + if (connectionType === 'SSH') { + authMethodForDb = input.auth_method!; // Already validated above + if (input.auth_method === 'password') { + encryptedPassword = encrypt(input.password!); + } else { // key + encryptedPrivateKey = encrypt(input.private_key!); + if (input.passphrase) { + encryptedPassphrase = encrypt(input.passphrase); + } } + } else { // RDP (connectionType is 'RDP') + encryptedPassword = encrypt(input.password!); + // authMethodForDb remains 'password' for RDP to satisfy DB constraint + // Ensure SSH specific fields are null for RDP + encryptedPrivateKey = null; + encryptedPassphrase = null; } // 3. 准备仓库数据 - const connectionData = { - name: input.name || '', // 如果 name 为空或 undefined,则使用空字符串 '' + const defaultPort = input.type === 'RDP' ? 3389 : 22; + // Explicitly type the object being passed to the repository + const connectionData: Omit = { + name: input.name || '', + type: connectionType, // Use the validated uppercase type host: input.host, - port: input.port ?? 22, // 默认端口 + port: input.port ?? defaultPort, // Use type-specific default port username: input.username, - auth_method: input.auth_method, + auth_method: authMethodForDb, // Use determined auth method encrypted_password: encryptedPassword, - encrypted_private_key: encryptedPrivateKey, - encrypted_passphrase: encryptedPassphrase, + encrypted_private_key: encryptedPrivateKey, // Will be null for RDP + encrypted_passphrase: encryptedPassphrase, // Will be null for RDP proxy_id: input.proxy_id ?? null, }; + console.log('[Service:createConnection] Data to be saved:', JSON.stringify(connectionData, null, 2)); // Log data before saving // 4. 在仓库中创建连接记录 const newConnectionId = await ConnectionRepository.createConnection(connectionData); @@ -86,7 +122,7 @@ export const createConnection = async (input: CreateConnectionInput): Promise = {}; + // Explicitly type dataToUpdate to match the repository's expected input + const dataToUpdate: Partial> = {}; let needsCredentialUpdate = false; - let newAuthMethod = input.auth_method || currentFullConnection.auth_method; + // Determine the final type, converting input type to uppercase if provided + const targetType = input.type?.toUpperCase() as 'SSH' | 'RDP' | undefined || currentFullConnection.type; // 更新非凭证字段 - if (input.name !== undefined) dataToUpdate.name = input.name || ''; // 如果 name 是空字符串或 null/undefined,则使用空字符串 '' + if (input.name !== undefined) dataToUpdate.name = input.name || ''; + // Update type if changed, using the uppercase version + if (input.type !== undefined && targetType !== currentFullConnection.type) dataToUpdate.type = targetType; if (input.host !== undefined) dataToUpdate.host = input.host; if (input.port !== undefined) dataToUpdate.port = input.port; if (input.username !== undefined) dataToUpdate.username = input.username; - if (input.proxy_id !== undefined) dataToUpdate.proxy_id = input.proxy_id; // 允许设置为 null + if (input.proxy_id !== undefined) dataToUpdate.proxy_id = input.proxy_id; - // 处理认证方法更改或凭证更新 - if (input.auth_method && input.auth_method !== currentFullConnection.auth_method) { - // 认证方法已更改 - dataToUpdate.auth_method = input.auth_method; - needsCredentialUpdate = true; - if (input.auth_method === 'password') { - if (!input.password) throw new Error('切换到密码认证时需要提供 password。'); - dataToUpdate.encrypted_password = encrypt(input.password); + // 处理认证方法更改或凭证更新 (根据 targetType) + // Use the validated targetType for logic + if (targetType === 'SSH') { + const currentAuthMethod = currentFullConnection.auth_method; + const inputAuthMethod = input.auth_method; + + // Determine the final auth method for SSH + const finalAuthMethod = inputAuthMethod || currentAuthMethod; + if (finalAuthMethod !== currentAuthMethod) { + dataToUpdate.auth_method = finalAuthMethod; // Update auth_method if it changed + } + + if (finalAuthMethod === 'password') { + // If switching to password or updating password + if (input.password !== undefined) { // Check if password was provided in input + if (!input.password && finalAuthMethod !== currentAuthMethod) { + // Switching to password requires a password + throw new Error('切换到密码认证时需要提供 password。'); + } + // Encrypt if password is not empty, otherwise set to null (to clear) + dataToUpdate.encrypted_password = input.password ? encrypt(input.password) : null; + needsCredentialUpdate = true; + } + // When switching to password, clear key fields + if (finalAuthMethod !== currentAuthMethod) { + dataToUpdate.encrypted_private_key = null; + dataToUpdate.encrypted_passphrase = null; + } + } else { // finalAuthMethod is 'key' + let keyUpdated = false; + // If switching to key or updating key + if (input.private_key !== undefined) { + if (!input.private_key && finalAuthMethod !== currentAuthMethod) { + // Switching to key requires a private key + throw new Error('切换到密钥认证时需要提供 private_key。'); + } + // Encrypt if key is not empty, otherwise set to null (to clear) + dataToUpdate.encrypted_private_key = input.private_key ? encrypt(input.private_key) : null; + needsCredentialUpdate = true; + keyUpdated = true; + } + // Update passphrase only if key was updated OR passphrase itself was provided + if (keyUpdated || input.passphrase !== undefined) { + // Encrypt if passphrase is not empty, otherwise set to null (to clear) + dataToUpdate.encrypted_passphrase = input.passphrase ? encrypt(input.passphrase) : null; + needsCredentialUpdate = true; // Consider passphrase change a credential update + } + // When switching to key, clear password field + if (finalAuthMethod !== currentAuthMethod) { + dataToUpdate.encrypted_password = null; + } + } + } else { // targetType is 'RDP' + // RDP only uses password + if (input.password !== undefined) { // Check if password was provided + // Encrypt if password is not empty, otherwise set to null (to clear) + dataToUpdate.encrypted_password = input.password ? encrypt(input.password) : null; + needsCredentialUpdate = true; + } + // Ensure SSH specific fields are nullified if switching to RDP or updating RDP + if (targetType !== currentFullConnection.type || needsCredentialUpdate) { + dataToUpdate.auth_method = 'password'; // RDP uses password auth method in DB dataToUpdate.encrypted_private_key = null; dataToUpdate.encrypted_passphrase = null; - } else { // 密钥 - if (!input.private_key) throw new Error('切换到密钥认证时需要提供 private_key。'); - dataToUpdate.encrypted_private_key = encrypt(input.private_key); - // 仅当密码短语为非空字符串时才加密 - dataToUpdate.encrypted_passphrase = (input.passphrase && input.passphrase.trim() !== '') ? encrypt(input.passphrase) : null; - dataToUpdate.encrypted_password = null; - } - } else { - // 认证方法未更改,检查是否提供了当前方法的凭证 - // 仅当提供了非空字符串时才加密和更新 - if (newAuthMethod === 'password' && input.password && input.password.trim() !== '') { - dataToUpdate.encrypted_password = encrypt(input.password); - needsCredentialUpdate = true; - } else if (newAuthMethod === 'key') { - let passphraseChanged = false; - if (input.private_key && input.private_key.trim() !== '') { - dataToUpdate.encrypted_private_key = encrypt(input.private_key); - // 如果私钥更新,则必须更新(或清除)密码短语 - // 仅当非空时加密,否则设置为 null - dataToUpdate.encrypted_passphrase = (input.passphrase && input.passphrase.trim() !== '') ? encrypt(input.passphrase) : null; - needsCredentialUpdate = true; - passphraseChanged = true; // 如果密钥更改,则将密码短语标记为已处理 - } - // 处理仅更改密码短语(且密钥未更改)的情况 - // 检查 input.passphrase 是否已定义(可能是空字符串以清除) - if (!passphraseChanged && input.passphrase !== undefined) { - // 仅当非空时加密,否则设置为 null - dataToUpdate.encrypted_passphrase = (input.passphrase && input.passphrase.trim() !== '') ? encrypt(input.passphrase) : null; - needsCredentialUpdate = true; // 将此视为凭证更新 - } } } @@ -182,7 +246,12 @@ export const updateConnection = async (id: number, input: UpdateConnectionInput) // 5. 如果进行了任何更改,则记录审计操作 if (hasNonTagChanges || input.tag_ids !== undefined) { - auditLogService.logAction('CONNECTION_UPDATED', { connectionId: id, updatedFields: updatedFieldsForAudit }); + // Add type to audit log if it was updated + const auditDetails: any = { connectionId: id, updatedFields: updatedFieldsForAudit }; + if (dataToUpdate.type) { + auditDetails.newType = dataToUpdate.type; + } + auditLogService.logAction('CONNECTION_UPDATED', auditDetails); } // 6. 获取并返回更新后的连接 diff --git a/packages/backend/src/services/import-export.service.ts b/packages/backend/src/services/import-export.service.ts index 15f174d..55ad270 100644 --- a/packages/backend/src/services/import-export.service.ts +++ b/packages/backend/src/services/import-export.service.ts @@ -7,6 +7,7 @@ import { getDbInstance, runDb, getDb as getDbRow, allDb } from '../database/conn interface ImportedConnectionData { name: string; + type: 'SSH' | 'RDP'; // Add type field host: string; port: number; username: string; @@ -42,10 +43,11 @@ export const exportConnections = async (): Promise => try { const db = await getDbInstance(); + // Ensure ExportRow reflects the updated FullConnectionData (which now includes 'type') type ExportRow = ConnectionRepository.FullConnectionData & { proxy_db_id: number | null; proxy_name: string | null; - proxy_type: 'SOCKS5' | 'HTTP' | null; + proxy_type: 'SOCKS5' | 'HTTP' | null; // Proxy type remains the same proxy_host: string | null; proxy_port: number | null; proxy_username: string | null; @@ -85,7 +87,8 @@ export const exportConnections = async (): Promise => const formattedData: ExportedConnectionData[] = connectionsWithProxies.map(row => { const connection: ExportedConnectionData = { - name: row.name ?? 'Unnamed', + name: row.name ?? 'Unnamed', + type: row.type, // Add type field host: row.host, port: row.port, username: row.username, @@ -154,9 +157,18 @@ export const importConnections = async (fileBuffer: Buffer): Promise props.connectionToEdit, (newVal) => { formError.value = null; // 清除错误 if (newVal) { // 编辑模式:填充表单,但不填充敏感信息 + formData.type = newVal.type; // Correctly set the type for editing formData.name = newVal.name; formData.host = newVal.host; formData.port = newVal.port; @@ -94,6 +97,21 @@ onMounted(() => { tagsStore.fetchTags(); // 获取标签列表 }); +// 监听连接类型变化,动态调整默认端口 +watch(() => formData.type, (newType) => { + // Use uppercase for comparison + if (newType === 'RDP' && formData.port === 22) { + formData.port = 3389; // RDP 默认端口 + } else if (newType === 'SSH' && formData.port === 3389) { + formData.port = 22; // SSH 默认端口 + } + // 重置或调整认证方式等逻辑可以在这里添加 + if (newType === 'RDP') { + // RDP 通常只用密码,可以强制或隐藏 auth_method + // formData.auth_method = 'password'; // Example: Force password for RDP + } +}); + // 处理表单提交 const handleSubmit = async () => { formError.value = null; @@ -110,71 +128,108 @@ const handleSubmit = async () => { return; } - // --- 更新后的验证逻辑 --- - // 1. 添加模式下,密码/密钥是必填的 - if (!isEditMode.value) { - if (formData.auth_method === 'password' && !formData.password) { - formError.value = t('connections.form.errorPasswordRequired'); + // --- 更新后的验证逻辑 (区分 SSH 和 RDP) --- + // Use uppercase for comparison + if (formData.type === 'SSH') { + // SSH Validation + // 1. 添加模式下,密码/密钥是必填的 + if (!isEditMode.value) { + if (formData.auth_method === 'password' && !formData.password) { + formError.value = t('connections.form.errorPasswordRequired'); + return; + } + if (formData.auth_method === 'key' && !formData.private_key) { + formError.value = t('connections.form.errorPrivateKeyRequired'); + return; + } + } + // 2. 编辑模式下,如果切换到密码认证,则密码必填 + else if (isEditMode.value && formData.auth_method === 'password' && !formData.password) { + // 检查原始连接的认证方式,如果原始不是密码,则切换时必须提供密码 + // 注意: props.connectionToEdit 可能没有 type 字段,需要后端配合或前端自行判断 + if (props.connectionToEdit?.auth_method !== 'password') { + formError.value = t('connections.form.errorPasswordRequiredOnSwitch'); + return; + } + // 如果原始就是密码,编辑时密码可以不填(表示不修改) + } + // 3. 编辑模式下,如果切换到密钥认证,则私钥必填 + else if (isEditMode.value && formData.auth_method === 'key' && !formData.private_key) { + // 检查原始连接的认证方式,如果原始不是密钥,则切换时必须提供私钥 + if (props.connectionToEdit?.auth_method !== 'key') { + formError.value = t('connections.form.errorPrivateKeyRequiredOnSwitch'); + return; + } + // 如果原始就是密钥,编辑时私钥可以不填(表示不修改) + } + // Use uppercase for comparison + } else if (formData.type === 'RDP') { + // RDP Validation + // 1. 添加模式下,密码是必填的 + if (!isEditMode.value && !formData.password) { + formError.value = t('connections.form.errorPasswordRequired'); // 可以复用密码必填的翻译 return; } - if (formData.auth_method === 'key' && !formData.private_key) { - formError.value = t('connections.form.errorPrivateKeyRequired'); - return; - } - } - // 2. 编辑模式下,如果切换到密码认证,则密码必填 - else if (isEditMode.value && formData.auth_method === 'password' && !formData.password) { - // 检查原始连接的认证方式,如果原始不是密码,则切换时必须提供密码 - if (props.connectionToEdit?.auth_method !== 'password') { - formError.value = t('connections.form.errorPasswordRequiredOnSwitch'); // 新增翻译键 - return; - } - // 如果原始就是密码,编辑时密码可以不填(表示不修改) - } - // 3. 编辑模式下,如果切换到密钥认证,则私钥必填 - else if (isEditMode.value && formData.auth_method === 'key' && !formData.private_key) { - // 检查原始连接的认证方式,如果原始不是密钥,则切换时必须提供私钥 - if (props.connectionToEdit?.auth_method !== 'key') { - formError.value = t('connections.form.errorPrivateKeyRequiredOnSwitch'); // 新增翻译键 - return; - } - // 如果原始就是密钥,编辑时私钥可以不填(表示不修改) + // 2. 编辑模式下,密码可以不填(表示不修改),除非是从非 RDP 类型切换过来(这个逻辑比较复杂,暂时简化为密码非必填) + // 如果需要更严格的验证(例如从 SSH 编辑为 RDP 时强制要求输入密码),可以在这里添加 } // --- 验证逻辑结束 --- // 构建要发送的数据 (区分添加和编辑) const dataToSend: any = { + type: formData.type, // 发送连接类型 name: formData.name, host: formData.host, port: formData.port, username: formData.username, - auth_method: formData.auth_method, proxy_id: formData.proxy_id || null, tag_ids: formData.tag_ids || [], // 发送 tag_ids + // domain: formData.domain, // 如果添加了 domain 字段 }; - // 处理敏感字段 - if (formData.auth_method === 'password') { - // 仅当用户输入新密码或在编辑模式下明确清空时才发送 + // 处理认证相关字段 (根据类型) + // Use uppercase for comparison + if (formData.type === 'SSH') { + dataToSend.auth_method = formData.auth_method; + if (formData.auth_method === 'password') { + // SSH 密码处理 + if (formData.password) { + dataToSend.password = formData.password; + } else if (isEditMode.value && formData.password === '') { + // 编辑模式下,空密码字符串可能表示清空或不修改,取决于后端实现 + // 假设发送 null 表示清空 (如果后端支持) + // dataToSend.password = null; + // 或者不发送 password 字段表示不修改 + } + } else if (formData.auth_method === 'key') { + // SSH 密钥处理 + if (formData.private_key) { + dataToSend.private_key = formData.private_key; + } + // SSH 密码短语处理 + if (formData.passphrase) { + dataToSend.passphrase = formData.passphrase; + } else if (isEditMode.value && formData.passphrase === '') { + // dataToSend.passphrase = null; // 发送 null 表示清空 + } + } + // Use uppercase for comparison + } else if (formData.type === 'RDP') { + // RDP 密码处理 (通常 RDP 没有 auth_method 选择) if (formData.password) { dataToSend.password = formData.password; } else if (isEditMode.value && formData.password === '') { - dataToSend.password = null; // 发送 null 表示清空密码 (后端需要能处理 null) - } - } else if (formData.auth_method === 'key') { - // 仅当用户输入新私钥时才发送 - if (formData.private_key) { - dataToSend.private_key = formData.private_key; - } - // 仅当用户输入新密码短语或在编辑模式下明确清空时才发送 - if (formData.passphrase) { - dataToSend.passphrase = formData.passphrase; - } else if (isEditMode.value && formData.passphrase === '') { - dataToSend.passphrase = null; // 发送 null 表示清空密码短语 + // 编辑 RDP 时,空密码字符串处理逻辑同上 + // dataToSend.password = null; } + // RDP 不发送 SSH 特有的字段 + delete dataToSend.auth_method; + delete dataToSend.private_key; + delete dataToSend.passphrase; } + let success = false; if (isEditMode.value && props.connectionToEdit) { // 调用更新 action @@ -300,6 +355,16 @@ const testButtonText = computed(() => { + +
+ + +
@@ -317,44 +382,65 @@ const testButtonText = computed(() => {
-

{{ t('connections.form.sectionAuth', '认证方式') }}

+

{{ t('connections.form.sectionAuth', '认证信息') }}

-
-
- - -
+
-
- - -
+ + + + + + + +
@@ -388,7 +474,9 @@ const testButtonText = computed(() => {
-
+ + +
+ +
- - diff --git a/packages/frontend/src/components/ConnectionList.vue b/packages/frontend/src/components/ConnectionList.vue index 1173b2e..7b856a6 100644 --- a/packages/frontend/src/components/ConnectionList.vue +++ b/packages/frontend/src/components/ConnectionList.vue @@ -1,5 +1,5 @@