From 78314b1f0b47a71b62ac83ca1dcc79f96877624d Mon Sep 17 00:00:00 2001 From: Baobhan Sith <80159437+Heavrnl@users.noreply.github.com> Date: Fri, 16 May 2025 13:10:04 +0800 Subject: [PATCH] update --- .../frontend/src/stores/connections.store.ts | 7 +- .../frontend/src/views/ConnectionsView.vue | 228 ++++++++++++++++-- packages/frontend/src/views/DashboardView.vue | 2 +- 3 files changed, 219 insertions(+), 18 deletions(-) diff --git a/packages/frontend/src/stores/connections.store.ts b/packages/frontend/src/stores/connections.store.ts index 0cde114..28f5d6d 100644 --- a/packages/frontend/src/stores/connections.store.ts +++ b/packages/frontend/src/stores/connections.store.ts @@ -191,13 +191,14 @@ export const useConnectionsStore = defineStore('connections', { }, // 测试连接 Action - async testConnection(connectionId: number): Promise<{ success: boolean; message?: string }> { + async testConnection(connectionId: number): Promise<{ success: boolean; message?: string; latency?: number }> { // 注意:这里不改变 isLoading 状态,或者可以引入单独的 testing 状态 // this.isLoading = true; // this.error = null; try { - const response = await apiClient.post<{ success: boolean; message: string }>(`/connections/${connectionId}/test`); // 使用 apiClient - return { success: response.data.success, message: response.data.message }; + // 假设后端返回 { success: boolean; message: string; latency?: number } + const response = await apiClient.post<{ success: boolean; message: string; latency?: number }>(`/connections/${connectionId}/test`); // 使用 apiClient + return { success: response.data.success, message: response.data.message, latency: response.data.latency }; } catch (err: any) { console.error(`测试连接 ${connectionId} 失败:`, err); const errorMessage = err.response?.data?.message || err.message || '测试连接时发生未知错误。'; diff --git a/packages/frontend/src/views/ConnectionsView.vue b/packages/frontend/src/views/ConnectionsView.vue index f9d28fe..f8e802a 100644 --- a/packages/frontend/src/views/ConnectionsView.vue +++ b/packages/frontend/src/views/ConnectionsView.vue @@ -269,14 +269,156 @@ const handleBatchEditFormClose = () => { }; // --- End Batch Edit Functions --- +// --- Test Connection Logic --- +interface ConnectionTestState { + status: 'idle' | 'testing' | 'success' | 'error'; + resultText: string; + latency?: number; + latencyColor?: string; +} +const connectionTestStates = ref>(new Map()); +const isTestingAll = ref(false); + +const getLatencyColorString = (latencyMs?: number): string => { + if (latencyMs === undefined) return 'inherit'; // Default or inherit + // These colors should ideally come from theme variables if available + if (latencyMs < 100) return 'var(--color-success, #4CAF50)'; + if (latencyMs < 300) return 'var(--color-warning, #ff9800)'; + return 'var(--color-error, #F44336)'; +}; + +const handleTestSingleConnection = async (conn: ConnectionInfo) => { + if (!conn.id || conn.type !== 'SSH') return; + + connectionTestStates.value.set(conn.id, { + status: 'testing', + resultText: t('connections.test.testingInProgress', '测试中...'), + }); + + try { + // Pass only the ID to testConnection, as per store definition + const result = await connectionsStore.testConnection(conn.id); + + if (result.success) { + const latencyMs = result.latency; + let displayText = ''; // 初始化为空字符串,符合只显示延迟的要求 + let determinedColor; + + if (latencyMs !== undefined) { + displayText = `${latencyMs}ms`; + determinedColor = getLatencyColorString(latencyMs); + } else { + // 测试成功,但没有延迟信息。不显示文本。 + // 颜色应为明确的成功颜色。 + // getLatencyColorString(0) 会返回绿色,代表非常好的情况。 + determinedColor = getLatencyColorString(0); // 或者直接使用 'var(--color-success, #4CAF50)' + } + + connectionTestStates.value.set(conn.id, { + status: 'success', + resultText: displayText, // 将显示 "XXms" 或者为空 + latency: latencyMs, + latencyColor: determinedColor, + }); + } else { + connectionTestStates.value.set(conn.id, { + status: 'error', + resultText: result.message || t('connections.test.unknownError', '未知错误'), + }); + } + } catch (error: any) { + connectionTestStates.value.set(conn.id, { + status: 'error', + resultText: error.message || t('connections.test.unknownError', '未知错误'), + }); + } +}; + +const handleTestAllFilteredConnections = async () => { + if (isTestingAll.value || isLoadingConnections.value) return; + // Ensure conn.id exists for map function and error handling + const sshConnectionsToTest = filteredAndSortedConnections.value.filter(c => c.type === 'SSH' && c.id != null); + if (sshConnectionsToTest.length === 0) { + // Optionally notify user that there are no SSH connections to test + // Consider using uiNotificationsStore from your project for a user-friendly message + return; + } + + isTestingAll.value = true; + const testPromises = sshConnectionsToTest.map(conn => { + // conn.id is guaranteed to exist here due to the filter above. + // We're calling handleTestSingleConnection for each. + // Individual errors within handleTestSingleConnection will update that specific connection's state. + // We also add a .catch here to handle any unexpected errors from handleTestSingleConnection itself + // or if conn.id was somehow null/undefined (though filtered out). + return handleTestSingleConnection(conn).catch(error => { + console.error(`Error testing connection ${conn.id}:`, error); + // Ensure state is updated for this specific connection to show an error + // The 'id' here is from the 'conn' object in the map function's scope. + connectionTestStates.value.set(conn.id!, { // Using non-null assertion as id is checked + status: 'error', + resultText: t('connections.test.unknownErrorDuringBatch', '批量测试中发生错误'), // New i18n key + }); + }); + }); + + try { + await Promise.all(testPromises); + } catch (error) { + // This catch block handles errors if Promise.all itself fails, + // though individual promise rejections are handled above. + console.error("Error during batch testing of connections (Promise.all):", error); + // Optionally, set a general error state or notification for the entire batch operation if needed. + } finally { + isTestingAll.value = false; + } +}; + +const getSingleTestButtonInfo = (connId: number | undefined, connType: string | undefined) => { + const state = connId ? connectionTestStates.value.get(connId) : undefined; + + if (connType !== 'SSH') { + return { + textKey: 'connections.actions.test', + iconClass: 'fas fa-plug', + disabled: true, + loading: false, + title: t('connections.test.onlySshSupportedTest', '仅SSH连接支持测试。') + }; + } + if (!connId) { // Should not happen if connType is SSH and we are in the list + return { textKey: 'connections.actions.test', iconClass: 'fas fa-plug', disabled: true, loading: false, title: '' }; + } + + if (state?.status === 'testing') { + return { textKey: 'connections.test.testingShort', iconClass: 'fas fa-spinner fa-spin', disabled: true, loading: true, title: t('connections.test.testingShort', '测试中') }; + } + if (state?.status === 'success' || state?.status === 'error') { + // 测试完成后,按钮恢复为初始“测试”状态 + return { textKey: 'connections.actions.test', iconClass: 'fas fa-plug', disabled: false, loading: false, title: t('connections.actions.test', '测试') }; + } + // 默认状态也是“测试” + return { textKey: 'connections.actions.test', iconClass: 'fas fa-plug', disabled: false, loading: false, title: t('connections.actions.test', '测试') }; +}; + +const getTruncatedNotes = (notes: string | null | undefined): string => { + if (!notes || notes.trim() === '') return ''; // 返回空字符串,如果没有备注 + const maxLength = 100; + if (notes.length <= maxLength) return notes; + return notes.substring(0, maxLength) + '...'; +}; + +// --- End Test Connection Logic --- +