From a2bf9e33450bdafc0089d7fcdf6056a93258a9dd Mon Sep 17 00:00:00 2001 From: Wanpl-Java <810220955@qq.com> Date: Thu, 11 Jun 2026 14:24:47 +0800 Subject: [PATCH] fix_client_connect_acl_branch_v1.0 --- i18n/locales/en.json | 1 + i18n/locales/zh.json | 1 + ui/components/EditForm/editForm.vue | 12 +++--- ui/composables/useAssetAction.ts | 57 ++++++++++++++--------------- ui/composables/useConnectMethods.ts | 13 ++++++- 5 files changed, 47 insertions(+), 37 deletions(-) diff --git a/i18n/locales/en.json b/i18n/locales/en.json index 52576f14..9a8e3b92 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -173,6 +173,7 @@ "ConnectError": { "ConnectFailed": "Connection failed", "AclFailed": "The current asset is restricted by ACL policies and cannot be accessed via the client. Please use the Web console to connect.", + "ConnectMethodNotAllowed": "This connect method is prohibited by access control policy.", "ClientNotFound": "Desktop client not found. Please install or configure the client path.", "ClientLaunchFailed": "Failed to launch desktop client. Please check installation or permissions.", "ClientExited": "Desktop client exited unexpectedly.", diff --git a/i18n/locales/zh.json b/i18n/locales/zh.json index f6e3ae64..f993984b 100644 --- a/i18n/locales/zh.json +++ b/i18n/locales/zh.json @@ -176,6 +176,7 @@ "ConnectError": { "ConnectFailed": "连接失败", "AclFailed": "当前资产受到 ACL 访问限制,无法通过客户端连接,请前往 Web 端进行访问", + "ConnectMethodNotAllowed": "当前连接方式被访问控制策略禁止,无法连接", "ClientNotFound": "未找到桌面客户端,请安装或在设置中配置路径", "ClientLaunchFailed": "启动桌面客户端失败,请检查安装或权限", "ClientExited": "桌面客户端异常退出", diff --git a/ui/components/EditForm/editForm.vue b/ui/components/EditForm/editForm.vue index 68e274af..0769b4fb 100644 --- a/ui/components/EditForm/editForm.vue +++ b/ui/components/EditForm/editForm.vue @@ -27,7 +27,7 @@ const emits = defineEmits<{ }>(); const { t } = useI18n(); -const { getMethodsForProtocol, getDefaultMethodForProtocol } = useConnectMethods(); +const { getMethodsForProtocol, getDefaultMethodForProtocol, fetchConnectMethods } = useConnectMethods(); // prettier-ignore const trailingIcon = "group-data-[state=open]:rotate-180 transition-transform duration-200"; @@ -79,14 +79,16 @@ watch( async (newProtocol) => { if (!newProtocol) return; + await fetchConnectMethods({ force: true }); const methods = await getMethodsForProtocol(newProtocol); availableConnectMethods.value = methods; - if (!props.connectMethod || !methods.some((m) => m.value === props.connectMethod)) { + const currentValid = props.connectMethod + && methods.some((m) => m.value === props.connectMethod); + if (!currentValid) { + // 无可用方式时须清空,避免残留上一协议的 connectMethod(如 ssh_client) const defaultMethod = await getDefaultMethodForProtocol(newProtocol); - if (defaultMethod) { - emits("update:connectMethod", defaultMethod); - } + emits("update:connectMethod", defaultMethod); } }, { immediate: true } diff --git a/ui/composables/useAssetAction.ts b/ui/composables/useAssetAction.ts index ef5b3487..91e3f91a 100644 --- a/ui/composables/useAssetAction.ts +++ b/ui/composables/useAssetAction.ts @@ -2,6 +2,7 @@ import type { UnlistenFn } from "@tauri-apps/api/event"; import type { ConnectionBody, PermedAccount, PermedProtocol, TokenResponse } from "~/types"; import { useSettingManager } from "~/composables/useSettingManager"; +import { useConnectMethods } from "~/composables/useConnectMethods"; import { useUserInfoStore } from "~/store/modules/userInfo"; let tauriListenersInitialized = false; @@ -56,6 +57,7 @@ export const useAssetAction = () => { const toast = useToast(); const userInfoStore = useUserInfoStore(); const settingManager = useSettingManager(); + const { getMethodsForProtocol, fetchConnectMethods } = useConnectMethods(); // prettier-ignore const { currentSite, currentConnectionInfoMap, currentRdpClientOption } = storeToRefs(userInfoStore); const { charset, rdpResolution, backspaceAsCtrlH, keyboardLayout, rdpClientOption, rdpColorQuality, rdpSmartSize } @@ -193,34 +195,15 @@ export const useAssetAction = () => { }; /** - * @description 根据协议分发连接方法 - * @param protocol + * @description 从服务端 ACL 过滤后的连接方式中解析可用方法 */ - const dispatchConnectMethod = (protocol: string) => { - let method = ""; - - switch (protocol) { - case "ssh": - case "telnet": - method = "ssh_client"; - break; - case "rdp": - method = "mstsc"; - break; - case "sftp": - method = "sftp_client"; - break; - case "vnc": - method = "vnc_client"; - break; - case "http": - method = "chrome"; - break; - default: - method = "db_client"; + const resolveConnectMethod = async (protocol: string, preferred?: string): Promise => { + const methods = await getMethodsForProtocol(protocol); + const normalized = preferred?.trim(); + if (normalized && methods.some((m) => m.value === normalized)) { + return normalized; } - - return method; + return methods[0]?.value || ""; }; const generateConnectOptions = (protocol: string) => { @@ -258,7 +241,7 @@ export const useAssetAction = () => { * @param accounts * @param protocolOverride */ - const handleAssetConnection = ( + const handleAssetConnection = async ( user: string, assetId: string, displayProtocol: string, @@ -320,10 +303,24 @@ export const useAssetAction = () => { return getUserId(accounts!, assetId, user); })(); + await fetchConnectMethods({ force: true }); + // 当前连接显式选择优先;仅在协议一致时复用已保存连接方法,避免跨协议复用错误的客户端 - const connectMethod = ephemeral?.connectMethod?.trim() - || (saved?.protocol === protocol ? saved?.connectMethod?.trim() : "") - || dispatchConnectMethod(protocol); + const preferredMethod = ephemeral?.connectMethod?.trim() + || (saved?.protocol === protocol ? saved?.connectMethod?.trim() : ""); + const connectMethod = await resolveConnectMethod(protocol, preferredMethod); + + if (!connectMethod) { + toast.add({ + title: t("ConnectError.ConnectFailed"), + description: t("ConnectError.ConnectMethodNotAllowed"), + color: "error", + icon: "line-md:close-circle", + progress: true, + duration: 4000 + }); + return; + } userInfoStore.setConnectionInfoForAsset(assetId, { protocol, diff --git a/ui/composables/useConnectMethods.ts b/ui/composables/useConnectMethods.ts index f79620a1..1c46cded 100644 --- a/ui/composables/useConnectMethods.ts +++ b/ui/composables/useConnectMethods.ts @@ -21,8 +21,16 @@ const fetchPromise = new Map>(); export const useConnectMethods = () => { const { currentSite, orgId } = storeToRefs(useUserInfoStore()); - const fetchConnectMethods = async (): Promise => { - const key = `${currentSite.value || ""}:${orgId.value || ""}`; + const getCacheKey = () => `${currentSite.value || ""}:${orgId.value || ""}`; + + const fetchConnectMethods = async (options?: { force?: boolean }): Promise => { + const key = getCacheKey(); + + if (options?.force) { + connectMethodsCache.delete(key); + fetchPromise.delete(key); + } + const cached = connectMethodsCache.get(key); if (cached) { @@ -117,6 +125,7 @@ export const useConnectMethods = () => { const clearCache = () => { connectMethodsCache.clear(); + fetchPromise.clear(); }; return {