From 166d710536293b2a36b31bf674de2b9366a888bf Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Mon, 14 Sep 2026 11:15:15 +0800 Subject: [PATCH] fix(connect): handle personal credential failures --- i18n/locales/en.json | 1 + i18n/locales/es.json | 1 + i18n/locales/ja.json | 1 + i18n/locales/ko.json | 1 + i18n/locales/pt_br.json | 1 + i18n/locales/ru.json | 1 + i18n/locales/vi.json | 1 + i18n/locales/zh.json | 1 + i18n/locales/zh_hant.json | 1 + ui/composables/useAssetAction.ts | 6 +++++- ui/composables/useErrorToast.test.ts | 14 ++++++++++++++ ui/composables/useErrorToast.ts | 13 ++++++++++++- 12 files changed, 40 insertions(+), 2 deletions(-) diff --git a/i18n/locales/en.json b/i18n/locales/en.json index c711580bc..c57506b6e 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -1332,6 +1332,7 @@ "DownloadRdpFailed": "Failed to download RDP file", "ConnectFailed": "Connection failed", "CredentialServiceUnavailable": "The credential service (such as OpenBao) is unavailable. Check the service status and try again.", + "PersonalCredentialNotFound": "The selected personal credential could not be found. Select another credential or enter one manually.", "AclFailed": "The current asset is restricted by ACL policies and cannot be accessed via the client. Please use the Web console to connect.", "MethodDisabled": "This connection method is disabled", "ClientNotFound": "Desktop client not found. Please install or configure the client path.", diff --git a/i18n/locales/es.json b/i18n/locales/es.json index af7977cf2..3599dbbdf 100644 --- a/i18n/locales/es.json +++ b/i18n/locales/es.json @@ -1332,6 +1332,7 @@ "DownloadRdpFailed": "No se pudo descargar el archivo RDP", "ConnectFailed": "Error de conexión", "CredentialServiceUnavailable": "El servicio de credenciales (por ejemplo, OpenBao) no está disponible. Compruebe su estado e inténtelo de nuevo.", + "PersonalCredentialNotFound": "No se encontró la credencial personal seleccionada. Seleccione otra o introdúzcala manualmente.", "AclFailed": "El activo actual está restringido por políticas de ACL y no se puede acceder a él mediante el cliente. Use la consola web para conectarse.", "MethodDisabled": "Este método de conexión está deshabilitado", "ClientNotFound": "No se encontró el cliente de escritorio. Instale o configure su ruta.", diff --git a/i18n/locales/ja.json b/i18n/locales/ja.json index c7fe70eb7..a8e227557 100644 --- a/i18n/locales/ja.json +++ b/i18n/locales/ja.json @@ -1332,6 +1332,7 @@ "DownloadRdpFailed": "RDP ファイルのダウンロードに失敗しました", "ConnectFailed": "接続に失敗しました", "CredentialServiceUnavailable": "認証情報サービス(OpenBao など)を利用できません。サービスの状態を確認してから再試行してください。", + "PersonalCredentialNotFound": "選択した個人認証情報が見つかりません。別の認証情報を選択するか、手動で入力してください。", "AclFailed": "現在のアセットは ACL ポリシーによって制限されているため、クライアントからアクセスできません。Web コンソールから接続してください。", "MethodDisabled": "この接続方法は無効になっています", "ClientNotFound": "デスクトップクライアントが見つかりません。クライアントをインストールするか、パスを設定してください。", diff --git a/i18n/locales/ko.json b/i18n/locales/ko.json index 215d6bca8..07e815306 100644 --- a/i18n/locales/ko.json +++ b/i18n/locales/ko.json @@ -1332,6 +1332,7 @@ "DownloadRdpFailed": "RDP 파일을 다운로드하지 못했습니다", "ConnectFailed": "연결 실패", "CredentialServiceUnavailable": "자격 증명 서비스(예: OpenBao)를 사용할 수 없습니다. 서비스 상태를 확인한 후 다시 시도하세요.", + "PersonalCredentialNotFound": "선택한 개인 자격 증명을 찾을 수 없습니다. 다른 자격 증명을 선택하거나 직접 입력하세요.", "AclFailed": "현재 자산은 ACL 정책으로 제한되어 클라이언트에서 접근할 수 없습니다. 웹 콘솔에서 연결하세요.", "MethodDisabled": "이 연결 방식은 비활성화되어 있습니다", "ClientNotFound": "데스크톱 클라이언트를 찾을 수 없습니다. 클라이언트를 설치하거나 경로를 설정하세요.", diff --git a/i18n/locales/pt_br.json b/i18n/locales/pt_br.json index eae9659f0..2387f16a8 100644 --- a/i18n/locales/pt_br.json +++ b/i18n/locales/pt_br.json @@ -1332,6 +1332,7 @@ "DownloadRdpFailed": "Falha ao baixar o arquivo RDP", "ConnectFailed": "Falha na conexão", "CredentialServiceUnavailable": "O serviço de credenciais (como o OpenBao) não está disponível. Verifique o status do serviço e tente novamente.", + "PersonalCredentialNotFound": "A credencial pessoal selecionada não foi encontrada. Selecione outra ou insira uma manualmente.", "AclFailed": "O ativo atual está restrito por políticas de ACL e não pode ser acessado pelo cliente. Use o console Web para se conectar.", "MethodDisabled": "Este método de conexão está desativado", "ClientNotFound": "Cliente de desktop não encontrado. Instale ou configure o caminho do cliente.", diff --git a/i18n/locales/ru.json b/i18n/locales/ru.json index f59dede98..e07b16abf 100644 --- a/i18n/locales/ru.json +++ b/i18n/locales/ru.json @@ -1332,6 +1332,7 @@ "DownloadRdpFailed": "Не удалось скачать файл RDP", "ConnectFailed": "Ошибка подключения", "CredentialServiceUnavailable": "Сервис учетных данных (например, OpenBao) недоступен. Проверьте его состояние и повторите попытку.", + "PersonalCredentialNotFound": "Выбранные личные учетные данные не найдены. Выберите другие или введите их вручную.", "AclFailed": "Доступ к текущему ресурсу ограничен политиками ACL, поэтому подключение через клиент недоступно. Используйте веб-консоль.", "MethodDisabled": "Этот способ подключения отключён", "ClientNotFound": "Настольный клиент не найден. Установите его или настройте путь к приложению.", diff --git a/i18n/locales/vi.json b/i18n/locales/vi.json index b79de6ead..c16d5ca02 100644 --- a/i18n/locales/vi.json +++ b/i18n/locales/vi.json @@ -1332,6 +1332,7 @@ "DownloadRdpFailed": "Không thể tải tệp RDP", "ConnectFailed": "Kết nối thất bại", "CredentialServiceUnavailable": "Dịch vụ thông tin xác thực (ví dụ: OpenBao) không khả dụng. Hãy kiểm tra trạng thái dịch vụ rồi thử lại.", + "PersonalCredentialNotFound": "Không tìm thấy thông tin xác thực cá nhân đã chọn. Hãy chọn thông tin khác hoặc nhập thủ công.", "AclFailed": "Tài sản hiện tại bị giới hạn bởi chính sách ACL và không thể truy cập bằng máy khách. Hãy dùng bảng điều khiển web để kết nối.", "MethodDisabled": "Phương thức kết nối này đã bị tắt", "ClientNotFound": "Không tìm thấy máy khách máy tính. Vui lòng cài đặt hoặc cấu hình đường dẫn.", diff --git a/i18n/locales/zh.json b/i18n/locales/zh.json index a560ac64e..4641a2780 100644 --- a/i18n/locales/zh.json +++ b/i18n/locales/zh.json @@ -1335,6 +1335,7 @@ "DownloadRdpFailed": "下载 RDP 文件失败", "ConnectFailed": "连接失败", "CredentialServiceUnavailable": "凭据服务(如 OpenBao)不可用,请检查服务状态后重试。", + "PersonalCredentialNotFound": "未找到所选的个人凭据,请重新选择或手动输入。", "AclFailed": "当前资产受到 ACL 访问限制,无法通过客户端连接,请前往 Web 端进行访问", "MethodDisabled": "该连接方式已被管理员禁用", "ClientNotFound": "未找到桌面客户端,请安装或在设置中配置路径", diff --git a/i18n/locales/zh_hant.json b/i18n/locales/zh_hant.json index 6c49b33aa..b9a9f8f52 100644 --- a/i18n/locales/zh_hant.json +++ b/i18n/locales/zh_hant.json @@ -1332,6 +1332,7 @@ "DownloadRdpFailed": "下載 RDP 檔案失敗", "ConnectFailed": "連線失敗", "CredentialServiceUnavailable": "憑據服務(例如 OpenBao)無法使用,請檢查服務狀態後重試。", + "PersonalCredentialNotFound": "找不到所選的個人憑據,請重新選擇或手動輸入。", "AclFailed": "當前資產受到 ACL 訪問限制,無法透過客戶端連線,請前往 Web 端進行訪問", "MethodDisabled": "該連線方式已被管理員停用", "ClientNotFound": "未找到桌面客戶端,請安裝或在設定中配置路徑", diff --git a/ui/composables/useAssetAction.ts b/ui/composables/useAssetAction.ts index 46ac358ef..e56822671 100644 --- a/ui/composables/useAssetAction.ts +++ b/ui/composables/useAssetAction.ts @@ -873,6 +873,10 @@ export const useAssetAction = () => { : undefined; const savePersonalCredential = !!ephemeral?.savePersonalCredential; const useSavedPersonalCredential = isManual && !!personalCredentialId && !savePersonalCredential; + const manualAccountSecretType = _accounts.find((account) => account.alias === "@INPUT")?.secret_type; + const personalCredentialSecretType = personalCredentialId + ? ephemeral?.personalCredentialSecretType || manualAccountSecretType || "password" + : manualAccountSecretType || ephemeral?.personalCredentialSecretType || "password"; const connectionBody: ConnectionBody = { asset: assetId, protocol, @@ -881,7 +885,7 @@ export const useAssetAction = () => { ...(isManual && savePersonalCredential ? { save_personal_credential: true, - input_secret_type: ephemeral.personalCredentialSecretType || "password", + input_secret_type: personalCredentialSecretType, ...(personalCredentialVersion !== undefined ? { personal_credential_version: personalCredentialVersion } : {}) diff --git a/ui/composables/useErrorToast.test.ts b/ui/composables/useErrorToast.test.ts index af92f312f..50b5d9a1a 100644 --- a/ui/composables/useErrorToast.test.ts +++ b/ui/composables/useErrorToast.test.ts @@ -19,4 +19,18 @@ describe("error toast", () => { expect(toastAdd).toHaveBeenCalledTimes(2); expect(toastAdd.mock.calls.map(([toast]) => toast.id)).toEqual(["login-failed", "login-failed"]); }); + + it("localizes a missing personal credential without exposing the API error type", () => { + vi.stubGlobal("useI18n", () => ({ + t: (key: string) => + key === "ConnectError.PersonalCredentialNotFound" ? "未找到所选的个人凭据,请重新选择或手动输入。" : key + })); + const { addErrorToast } = useErrorToast(); + + addErrorToast({ title: "连接失败", description: "ApiRequestError: Personal credential not found" }); + + expect(toastAdd).toHaveBeenCalledWith( + expect.objectContaining({ description: "未找到所选的个人凭据,请重新选择或手动输入。" }) + ); + }); }); diff --git a/ui/composables/useErrorToast.ts b/ui/composables/useErrorToast.ts index 36933d5ca..ba5996bb3 100644 --- a/ui/composables/useErrorToast.ts +++ b/ui/composables/useErrorToast.ts @@ -54,6 +54,14 @@ function resolveErrorDescription(error: unknown) { return normalizeToastText(error); } +function localizeErrorDescription(description: string, translate: (key: string) => string) { + const apiMessage = description.replace(/^ApiRequestError:\s*/i, "").trim(); + if (/^Personal credential not found\.?$/i.test(apiMessage)) { + return translate("ConnectError.PersonalCredentialNotFound"); + } + return description; +} + function hasCopyAction(actions: ErrorToastAction[] = []) { return actions.some((action) => { const label = normalizeToastText(action?.label).toLowerCase(); @@ -67,7 +75,10 @@ export function useErrorToast() { const toast = useToast(); const addErrorToast = (options: ErrorToastOptions) => { - const description = normalizeErrorText(options.description ?? resolveErrorDescription(options.error)); + const description = localizeErrorDescription( + normalizeErrorText(options.description ?? resolveErrorDescription(options.error)), + t + ); const actions = [...(options.actions ?? [])]; const copyText = [options.title, description].filter(Boolean).join("\n");