Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down
1 change: 1 addition & 0 deletions i18n/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@
"ConnectError": {
"ConnectFailed": "连接失败",
"AclFailed": "当前资产受到 ACL 访问限制,无法通过客户端连接,请前往 Web 端进行访问",
"ConnectMethodNotAllowed": "当前连接方式被访问控制策略禁止,无法连接",
"ClientNotFound": "未找到桌面客户端,请安装或在设置中配置路径",
"ClientLaunchFailed": "启动桌面客户端失败,请检查安装或权限",
"ClientExited": "桌面客户端异常退出",
Expand Down
12 changes: 7 additions & 5 deletions ui/components/EditForm/editForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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 }
Expand Down
57 changes: 27 additions & 30 deletions ui/composables/useAssetAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 }
Expand Down Expand Up @@ -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<string> => {
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) => {
Expand Down Expand Up @@ -258,7 +241,7 @@ export const useAssetAction = () => {
* @param accounts
* @param protocolOverride
*/
const handleAssetConnection = (
const handleAssetConnection = async (
user: string,
assetId: string,
displayProtocol: string,
Expand Down Expand Up @@ -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,
Expand Down
13 changes: 11 additions & 2 deletions ui/composables/useConnectMethods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,16 @@ const fetchPromise = new Map<string, Promise<ConnectMethodsResponse>>();
export const useConnectMethods = () => {
const { currentSite, orgId } = storeToRefs(useUserInfoStore());

const fetchConnectMethods = async (): Promise<ConnectMethodsResponse> => {
const key = `${currentSite.value || ""}:${orgId.value || ""}`;
const getCacheKey = () => `${currentSite.value || ""}:${orgId.value || ""}`;

const fetchConnectMethods = async (options?: { force?: boolean }): Promise<ConnectMethodsResponse> => {
const key = getCacheKey();

if (options?.force) {
connectMethodsCache.delete(key);
fetchPromise.delete(key);
}

const cached = connectMethodsCache.get(key);

if (cached) {
Expand Down Expand Up @@ -117,6 +125,7 @@ export const useConnectMethods = () => {

const clearCache = () => {
connectMethodsCache.clear();
fetchPromise.clear();
};

return {
Expand Down
Loading