Skip to content
Merged
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
2 changes: 2 additions & 0 deletions i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"DirectoryService": "Directory Service",
"Resource": "Resource",
"Database": "Database",
"Web": "Web",
"OfflinePlayer": "Offline Player",
"Favorite": "Favorite"
},
Expand Down Expand Up @@ -94,6 +95,7 @@
"ModifyConnectionInfo": "Connect",
"Description": "Next connections will use these settings by default, right-click to edit",
"OptionalProtocol": "Protocol",
"ConnectMethod": "Connect Method",
"OptionalAccount": "Account"
},
"Layout": {
Expand Down
2 changes: 2 additions & 0 deletions i18n/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"DirectoryService": "目录服务",
"Database": "数据库",
"Device": "设备",
"Web": "Web",
"Other": "其他",
"Favorite": "收藏",
"OfflinePlayer": "离线播放器",
Expand Down Expand Up @@ -94,6 +95,7 @@
"ModifyConnectionInfo": "连接",
"Description": "保存后,下次连接将默认使用这些设置,右击重新设置",
"OptionalProtocol": "协议",
"ConnectMethod": "连接方法",
"OptionalAccount": "账号"
},
"SettingModal": {
Expand Down
Binary file added public/icons/browser.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 38 additions & 0 deletions src-tauri/src/commands/get_connect_methods.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use crate::commands::auth_login::ensure_fresh_token;
use crate::service::connect_methods::ConnectMethodsService;
use log::{error, info};
use serde_json::json;
use tauri::{AppHandle, Emitter};

#[tauri::command]
pub async fn get_connect_methods(app: AppHandle, site: String, bearer_token: String) {
let bearer = match ensure_fresh_token(&app, &site, Some(&bearer_token)).await {
Ok(b) => b,
Err(e) => {
let _ = app.emit(
"get-connect-methods-failure",
json!({ "status": 401, "error": e.to_string() }),
);
return;
}
};
let connect_methods_service = ConnectMethodsService::new(site, bearer);
let connect_methods_data = connect_methods_service.get_connect_methods().await;

if !connect_methods_data.success {
error!("获取 ConnectMethods 数据失败");

let _ = app.emit(
"get-connect-methods-failure",
json!({ "status": connect_methods_data.status }),
);
return;
}

info!("获取 ConnectMethods 数据成功");

let _ = app.emit(
"get-connect-methods-success",
json!({ "status": connect_methods_data.status, "data": connect_methods_data.data }),
);
}
3 changes: 2 additions & 1 deletion src-tauri/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ pub mod auth_login;
pub mod get_asset_detail;
pub mod get_assets;
pub mod get_config;
pub mod get_connect_methods;
pub mod get_setting;
pub mod get_token;
pub mod get_version_message;
pub mod http_callback;
pub mod list_system_fonts;
pub mod logout;
pub mod pull_up;
Expand All @@ -14,4 +16,3 @@ pub mod set_favorite;
pub mod unfavorite;
pub mod update_config;
pub mod window_controls;
pub mod http_callback;
2 changes: 2 additions & 0 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::commands::auth_login::{auth_cancel, auth_login, handle_auth_callback,
use crate::commands::get_asset_detail::get_asset_detail;
use crate::commands::get_assets::get_assets;
use crate::commands::get_config::get_config;
use crate::commands::get_connect_methods::get_connect_methods;
use crate::commands::get_setting::get_setting;
use crate::commands::get_token::get_connect_token;
use crate::commands::get_version_message::get_version_message;
Expand Down Expand Up @@ -195,6 +196,7 @@ pub fn run() {
toggle_maximize_window,
update_config_selection,
init_http_callback_server,
get_connect_methods,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
Expand Down
6 changes: 5 additions & 1 deletion src-tauri/src/service/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub enum Category {
WindowsAd,
Database,
Device,
Web,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone)]
Expand Down Expand Up @@ -43,7 +44,9 @@ impl AssetQuery {
pub fn new(asset_type: Category, org: String) -> Self {
let (r#type, category) = match asset_type {
Category::Database | Category::Device => (None, Some(asset_type)),
Category::Linux | Category::Windows | Category::WindowsAd => (Some(asset_type), None),
Category::Linux | Category::Windows | Category::WindowsAd | Category::Web => {
(Some(asset_type), None)
}
};

Self {
Expand Down Expand Up @@ -115,6 +118,7 @@ impl AssetService {
Category::WindowsAd => (Some(Category::WindowsAd), None),
Category::Database => (None, Some(Category::Database)),
Category::Device => (None, Some(Category::Device)),
Category::Web => (None, Some(Category::Web)),
}
};

Expand Down
20 changes: 20 additions & 0 deletions src-tauri/src/service/connect_methods.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use crate::commands::requests::{get_with_response, ApiResponse};

pub struct ConnectMethodsService {
origin: String,
bearer_token: String,
}

impl ConnectMethodsService {
pub fn new(origin: String, bearer_token: String) -> Self {
Self {
origin,
bearer_token,
}
}

pub async fn get_connect_methods(&self) -> ApiResponse {
let url = format!("{}/api/v1/terminal/components/connect-methods/", self.origin);
get_with_response(&url, &self.bearer_token).await
}
}
1 change: 1 addition & 0 deletions src-tauri/src/service/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ pub(crate) mod token_oauth;
pub(crate) mod user;
pub(crate) mod http_callback;
pub(crate) mod version;
pub(crate) mod connect_methods;
1 change: 1 addition & 0 deletions ui/components/BasePage/basePage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ const handleConnectAsset = async (asset: AssetItem) => {
manualPassword: saved!.manualPassword || "",
dynamicPassword: saved!.dynamicPassword || "",
rememberSecret: !!saved!.rememberSecret,
connectMethod: saved!.connectMethod || "",
availableProtocols: saved!.availableProtocols || []
});
}
Expand Down
3 changes: 2 additions & 1 deletion ui/components/Card/AssetIcon/assetIcon.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ const imageProps = computed(() => {
mongodb: "/icons/mongodb.png",
dameng: "/icons/dameng.png",
clickhouse: "/icons/clickhouse.png",
windows_ad: "/icons/windows.png"
windows_ad: "/icons/windows.png",
website: "/icons/browser.png"
};

const src = iconMap[props.type] || "";
Expand Down
4 changes: 4 additions & 0 deletions ui/components/ConnectionEditor/connectionEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const draftManualUsername = ref<string>("");
const draftManualPassword = ref<string>("");
const draftDynamicPassword = ref<string>("");
const draftRememberSecret = ref<boolean>(false);
const draftConnectMethod = ref<string>("");

let pendingResolve: ((info: any) => void) | null = null;
let pendingReject: ((reason?: any) => void) | null = null;
Expand Down Expand Up @@ -63,6 +64,7 @@ const initDraft = (asset: AssetItem) => {
draftManualPassword.value = saved?.manualPassword || "";
draftDynamicPassword.value = saved?.dynamicPassword || "";
draftRememberSecret.value = saved?.rememberSecret || false;
draftConnectMethod.value = saved?.connectMethod || "";
};

/**
Expand Down Expand Up @@ -109,6 +111,7 @@ const buildConnectionInfo = () => {
manualPassword: draftManualPassword.value || "",
dynamicPassword: draftDynamicPassword.value || "",
rememberSecret: !!draftRememberSecret.value,
connectMethod: draftConnectMethod.value || "",
availableProtocols: normalizeProtocols()
};
};
Expand Down Expand Up @@ -208,6 +211,7 @@ defineExpose({ open: openModal, close });
v-model:manual-password="draftManualPassword"
v-model:dynamic-password="draftDynamicPassword"
v-model:remember-secret="draftRememberSecret"
v-model:connect-method="draftConnectMethod"
:accounts="currentAsset.permedAccounts || []"
:protocols="currentAsset.permedProtocols || []"
/>
Expand Down
47 changes: 47 additions & 0 deletions ui/components/EditForm/editForm.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script setup lang="ts">
import type { SelectMenuItem } from "@nuxt/ui";
import type { PermedAccount, PermedProtocol } from "~/types/index";
import { useConnectMethods } from "~/composables/useConnectMethods";

const props = defineProps<{
account: string
Expand All @@ -11,6 +12,7 @@ const props = defineProps<{
manualPassword?: string
dynamicPassword?: string
rememberSecret?: boolean
connectMethod?: string
}>();

const emits = defineEmits<{
Expand All @@ -20,14 +22,23 @@ const emits = defineEmits<{
(e: "update:manualPassword", v: string): void
(e: "update:dynamicPassword", v: string): void
(e: "update:rememberSecret", v: boolean): void
(e: "update:connectMethod", v: string): void
}>();

const { t } = useI18n();
const { getMethodsForProtocol, getDefaultMethodForProtocol, getMethodDisplayName } = useConnectMethods();
// prettier-ignore
const trailingIcon = "group-data-[state=open]:rotate-180 transition-transform duration-200";

const showManualInputArea = ref(false);
const showDynamicUserArea = ref(false);
const availableConnectMethods = ref<any[]>([]);
const connectMethodItems = computed<SelectMenuItem[]>(() => {
return availableConnectMethods.value.map((method) => ({
label: method.label || method.value,
value: method.value
}));
});

const localManualUsername = computed<string>({
get: () => props.manualUsername || "",
Expand All @@ -49,6 +60,11 @@ const localRememberSecret = computed<boolean>({
set: (v: boolean) => emits("update:rememberSecret", !!v)
});

const localConnectMethod = computed<string>({
get: () => props.connectMethod || "",
set: (v: string) => emits("update:connectMethod", v ?? "")
});

watch(
() => props.account,
(newVal) => {
Expand All @@ -57,6 +73,24 @@ watch(
{ immediate: true }
);

watch(
() => props.protocol,
async (newProtocol) => {
if (!newProtocol) return;

const methods = await getMethodsForProtocol(newProtocol);
availableConnectMethods.value = methods;

if (!props.connectMethod || !methods.find((m) => m.value === props.connectMethod)) {
const defaultMethod = await getDefaultMethodForProtocol(newProtocol);
if (defaultMethod) {
emits("update:connectMethod", defaultMethod);
}
}
},
{ immediate: true }
);

const protocolItems = computed(() => props.protocols.map((p: PermedProtocol) => p.name));

const accountItems = computed(() => {
Expand Down Expand Up @@ -133,6 +167,19 @@ function handleSpecialAccount(v: string) {
/>
</UFormField>

<UFormField :label="t('EditModal.ConnectMethod')" size="md">
<USelect
v-model="localConnectMethod"
:items="connectMethodItems"
:ui="{
trailingIcon
}"
icon="lucide:zap"
class="w-full"
:disabled="connectMethodItems.length === 0"
/>
</UFormField>

<UFormField :label="t('EditModal.OptionalAccount')" size="md">
<USelectMenu
v-model="selectedAccount"
Expand Down
6 changes: 6 additions & 0 deletions ui/components/SideBar/sideBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ const sideBarItems = computed<NavigationMenuItem[]>(() => {
icon: "mingcute:device-line",
to: localePath("device"),
disabled: isLoading.value
},
{
label: t("Menu.Web"),
icon: "mingcute:web-line",
to: localePath("web"),
disabled: isLoading.value
},
{
label: t("Menu.Favorite"),
Expand Down
29 changes: 23 additions & 6 deletions ui/composables/useAssetAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,25 +212,38 @@ export const useAssetAction = () => {
case "vnc":
method = "vnc_client";
break;
case "http":
method = "chrome";
break;
default:
method = "db_client";
}

return method;
};

const generateConnectOptions = () => {
const generateConnectOptions = (protocol: string) => {
const prefs = resolveGraphicsPreferences();

return {
const options = {
charset: prefs.resolvedCharset,
is_backspace_as_ctrl_h: prefs.resolvedBackspace,
backspaceAsCtrlH: prefs.resolvedBackspace,
resolution: prefs.resolvedResolution,
rdp_resolution: prefs.resolvedResolution,
keyboard_layout: prefs.resolvedKeyboardLayout,
rdp_client_option: prefs.resolvedClientOptions,
rdp_color_quality: prefs.resolvedColorQuality,
rdp_smart_size: prefs.resolvedSmartSize
rdp_smart_size: prefs.resolvedSmartSize,
token_reusable: false,
disableautohash: false,
};
const specificOptions = protocol === "http" ? {
appletConnectMethod: "client",
reusable: false,
} : {};
return {
...options,
...specificOptions
};
};

Expand All @@ -253,6 +266,7 @@ export const useAssetAction = () => {
manualUsername?: string;
manualPassword?: string;
dynamicPassword?: string;
connectMethod?: string;
}
) => {
const saved = currentConnectionInfoMap.value[assetId];
Expand Down Expand Up @@ -297,15 +311,18 @@ export const useAssetAction = () => {
return getUserId(accounts!, assetId, user);
})();

// 优先使用保存的连接方法,其次使用临时连接方法,最后使用 dispatchConnectMethod 作为后备
const connectMethod = saved?.connectMethod ?? ephemeral?.connectMethod ?? dispatchConnectMethod(protocol);

nextTick(() => {
getConnectToken({
asset: assetId,
protocol,
input_username,
input_secret,
account: accountForToken,
connect_method: dispatchConnectMethod(protocol),
connect_options: generateConnectOptions()
connect_method: connectMethod,
connect_options: generateConnectOptions(protocol)
});
});
};
Expand Down
Loading