diff --git a/easytier-gui/locales/cn.yml b/easytier-gui/locales/cn.yml index 66f5de3..86c6270 100644 --- a/easytier-gui/locales/cn.yml +++ b/easytier-gui/locales/cn.yml @@ -50,7 +50,11 @@ dev_name_placeholder: 注意:当多个网络同时使用相同的TUN接口名 off_text: 点击关闭 on_text: 点击开启 show_config: 显示配置 +edit_config: 编辑配置文件 close: 关闭 +save: 保存 +config_saved: 配置已保存 + use_latency_first: 延迟优先模式 my_node_info: 当前节点信息 diff --git a/easytier-gui/locales/en.yml b/easytier-gui/locales/en.yml index 94d8178..b7cc324 100644 --- a/easytier-gui/locales/en.yml +++ b/easytier-gui/locales/en.yml @@ -51,7 +51,10 @@ dev_name_placeholder: 'Note: When multiple networks use the same TUN interface n off_text: Press to disable on_text: Press to enable show_config: Show Config +edit_config: Edit Config File close: Close +save: Save +config_saved: Configuration saved my_node_info: My Node Info peer_count: Connected upload: Upload diff --git a/easytier-gui/src-tauri/src/lib.rs b/easytier-gui/src-tauri/src/lib.rs index d42faeb..b0a06e8 100644 --- a/easytier-gui/src-tauri/src/lib.rs +++ b/easytier-gui/src-tauri/src/lib.rs @@ -4,11 +4,9 @@ use std::collections::BTreeMap; use easytier::{ - common::config::{ - ConfigLoader, FileLoggerConfig, LoggingConfigBuilder, - }, - launcher::{ConfigSource, NetworkConfig, NetworkInstanceRunningInfo}, + common::config::{ConfigLoader, FileLoggerConfig, LoggingConfigBuilder, TomlConfigLoader}, instance_manager::NetworkInstanceManager, + launcher::{ConfigSource, NetworkConfig, NetworkInstanceRunningInfo}, utils::{self, NewFilterSender}, }; @@ -44,6 +42,13 @@ fn parse_network_config(cfg: NetworkConfig) -> Result { Ok(toml.dump()) } +#[tauri::command] +fn generate_network_config(toml_config: String) -> Result { + let config = TomlConfigLoader::new_from_str(&toml_config).map_err(|e| e.to_string())?; + let cfg = NetworkConfig::new_from_config(&config).map_err(|e| e.to_string())?; + Ok(cfg) +} + #[tauri::command] fn run_network_instance(cfg: NetworkConfig) -> Result<(), String> { let instance_id = cfg.instance_id().to_string(); @@ -226,6 +231,7 @@ pub fn run() { }) .invoke_handler(tauri::generate_handler![ parse_network_config, + generate_network_config, run_network_instance, retain_network_instance, collect_network_infos, diff --git a/easytier-gui/src/App.vue b/easytier-gui/src/App.vue index 818fba8..7088dce 100644 --- a/easytier-gui/src/App.vue +++ b/easytier-gui/src/App.vue @@ -8,5 +8,6 @@ onBeforeMount(async () => { diff --git a/easytier-gui/src/auto-imports.d.ts b/easytier-gui/src/auto-imports.d.ts index c4a6782..18e781e 100644 --- a/easytier-gui/src/auto-imports.d.ts +++ b/easytier-gui/src/auto-imports.d.ts @@ -23,6 +23,7 @@ declare global { const effectScope: typeof import('vue')['effectScope'] const event2human: typeof import('./composables/utils')['event2human'] const generateMenuItem: typeof import('./composables/tray')['generateMenuItem'] + const generateNetworkConfig: typeof import('./composables/network')['generateNetworkConfig'] const getActivePinia: typeof import('pinia')['getActivePinia'] const getCurrentInstance: typeof import('vue')['getCurrentInstance'] const getCurrentScope: typeof import('vue')['getCurrentScope'] @@ -134,6 +135,7 @@ declare module 'vue' { readonly defineStore: UnwrapRef readonly effectScope: UnwrapRef readonly generateMenuItem: UnwrapRef + readonly generateNetworkConfig: UnwrapRef readonly getActivePinia: UnwrapRef readonly getCurrentInstance: UnwrapRef readonly getCurrentScope: UnwrapRef diff --git a/easytier-gui/src/composables/network.ts b/easytier-gui/src/composables/network.ts index d603695..e0cc994 100644 --- a/easytier-gui/src/composables/network.ts +++ b/easytier-gui/src/composables/network.ts @@ -8,6 +8,10 @@ export async function parseNetworkConfig(cfg: NetworkConfig) { return invoke('parse_network_config', { cfg }) } +export async function generateNetworkConfig(tomlConfig: string) { + return invoke('generate_network_config', { tomlConfig }) +} + export async function runNetworkInstance(cfg: NetworkConfig) { return invoke('run_network_instance', { cfg }) } diff --git a/easytier-gui/src/pages/index.vue b/easytier-gui/src/pages/index.vue index 0ba8b26..80fafa5 100644 --- a/easytier-gui/src/pages/index.vue +++ b/easytier-gui/src/pages/index.vue @@ -8,7 +8,7 @@ import { exit } from '@tauri-apps/plugin-process' import { open } from '@tauri-apps/plugin-shell' import TieredMenu from 'primevue/tieredmenu' import { useToast } from 'primevue/usetoast' -import { NetworkTypes, Config, Status, Utils, I18nUtils } from 'easytier-frontend-lib' +import { NetworkTypes, Config, Status, Utils, I18nUtils, ConfigEditDialog } from 'easytier-frontend-lib' import { isAutostart, setLoggingLevel } from '~/composables/network' import { useTray } from '~/composables/tray' @@ -23,7 +23,7 @@ useTray(true) const items = ref([ { - label: () => t('show_config'), + label: () => activeStep.value == "2" ? t('show_config') : t('edit_config'), icon: 'pi pi-file-edit', command: async () => { try { @@ -262,6 +262,13 @@ onMounted(async () => { function isRunning(id: string) { return networkStore.networkInstanceIds.includes(id) } + +async function saveTomlConfig(tomlConfig: string) { + const config = await generateNetworkConfig(tomlConfig) + networkStore.replaceCurNetwork(config); + toast.add({ severity: 'success', detail: t('config_saved'), life: 3000 }) + visible.value = false +} + diff --git a/easytier-web/frontend-lib/src/components/index.ts b/easytier-web/frontend-lib/src/components/index.ts index 2628025..9d26d14 100644 --- a/easytier-web/frontend-lib/src/components/index.ts +++ b/easytier-web/frontend-lib/src/components/index.ts @@ -1,2 +1,3 @@ export { default as Config } from './Config.vue'; export { default as Status } from './Status.vue'; +export { default as ConfigEditDialog } from './ConfigEditDialog.vue'; diff --git a/easytier-web/frontend-lib/src/easytier-frontend-lib.ts b/easytier-web/frontend-lib/src/easytier-frontend-lib.ts index a6ed27c..4a69aeb 100644 --- a/easytier-web/frontend-lib/src/easytier-frontend-lib.ts +++ b/easytier-web/frontend-lib/src/easytier-frontend-lib.ts @@ -1,7 +1,7 @@ import './style.css' import type { App } from 'vue'; -import { Config, Status } from "./components"; +import { Config, Status, ConfigEditDialog } from "./components"; import Aura from '@primevue/themes/aura' import PrimeVue from 'primevue/config' @@ -41,10 +41,11 @@ export default { }); app.component('Config', Config); + app.component('ConfigEditDialog', ConfigEditDialog); app.component('Status', Status); app.component('HumanEvent', HumanEvent); app.directive('tooltip', vTooltip as any); } }; -export { Config, Status, I18nUtils, NetworkTypes, Api, Utils }; +export { Config, ConfigEditDialog, Status, I18nUtils, NetworkTypes, Api, Utils }; diff --git a/easytier-web/frontend-lib/src/locales/cn.yaml b/easytier-web/frontend-lib/src/locales/cn.yaml index 492bea1..bb19881 100644 --- a/easytier-web/frontend-lib/src/locales/cn.yaml +++ b/easytier-web/frontend-lib/src/locales/cn.yaml @@ -51,7 +51,11 @@ dev_name_placeholder: 注意:当多个网络同时使用相同的TUN接口名 off_text: 点击关闭 on_text: 点击开启 show_config: 显示配置 +edit_config: 编辑配置文件 +config_file: 配置文件 close: 关闭 +save: 保存 +config_saved: 配置已保存 use_latency_first: 延迟优先模式 my_node_info: 当前节点信息 diff --git a/easytier-web/frontend-lib/src/locales/en.yaml b/easytier-web/frontend-lib/src/locales/en.yaml index bfef6e5..e89efb7 100644 --- a/easytier-web/frontend-lib/src/locales/en.yaml +++ b/easytier-web/frontend-lib/src/locales/en.yaml @@ -52,7 +52,11 @@ dev_name_placeholder: 'Note: When multiple networks use the same TUN interface n off_text: Press to disable on_text: Press to enable show_config: Show Config +edit_config: Edit Config File +config_file: Config File close: Close +save: Save +config_saved: Configuration saved my_node_info: My Node Info peer_count: Connected upload: Upload diff --git a/easytier-web/frontend-lib/src/modules/api.ts b/easytier-web/frontend-lib/src/modules/api.ts index 699a79a..5a0a1fb 100644 --- a/easytier-web/frontend-lib/src/modules/api.ts +++ b/easytier-web/frontend-lib/src/modules/api.ts @@ -47,6 +47,15 @@ export interface GenerateConfigResponse { error?: string; } +export interface ParseConfigRequest { + toml_config: string; +} + +export interface ParseConfigResponse { + config?: NetworkConfig; + error?: string; +} + export class ApiClient { private client: AxiosInstance; private authFailedCb: Function | undefined; @@ -215,6 +224,18 @@ export class ApiClient { return { error: 'Unknown error: ' + error }; } } + + public async parse_config(config: ParseConfigRequest): Promise { + try { + const response = await this.client.post('/parse-config', config); + return response; + } catch (error) { + if (error instanceof AxiosError) { + return { error: error.response?.data }; + } + return { error: 'Unknown error: ' + error }; + } + } } export default ApiClient; \ No newline at end of file diff --git a/easytier-web/frontend/src/components/ConfigGenerator.vue b/easytier-web/frontend/src/components/ConfigGenerator.vue index e9a8f7c..dbfdaae 100644 --- a/easytier-web/frontend/src/components/ConfigGenerator.vue +++ b/easytier-web/frontend/src/components/ConfigGenerator.vue @@ -2,12 +2,11 @@ import { NetworkTypes } from 'easytier-frontend-lib'; import {computed, ref} from 'vue'; import { Api } from 'easytier-frontend-lib' -import {AutoComplete, Divider} from "primevue"; +import {AutoComplete, Divider, Button, Textarea} from "primevue"; import {getInitialApiHost, cleanAndLoadApiHosts, saveApiHost} from "../modules/api-host" const api = computed(() => new Api.ApiClient(apiHost.value)); - const apiHost = ref(getInitialApiHost()) const apiHostSuggestions = ref>([]) const apiHostSearch = async (event: { query: string }) => { @@ -22,23 +21,46 @@ const apiHostSearch = async (event: { query: string }) => { } const newNetworkConfig = ref(NetworkTypes.DEFAULT_NETWORK_CONFIG()); -const toml_config = ref("Press 'Run Network' to generate TOML configuration"); +const toml_config = ref(""); +const errorMessage = ref(""); const generateConfig = (config: NetworkTypes.NetworkConfig) => { saveApiHost(apiHost.value) + errorMessage.value = ""; api.value?.generate_config({ config: config }).then((res) => { if (res.error) { - toml_config.value = res.error; + errorMessage.value = "Generation failed: " + res.error; } else if (res.toml_config) { toml_config.value = res.toml_config; } else { - toml_config.value = "Api server returned an unexpected response"; + errorMessage.value = "Api server returned an unexpected response"; } + }).catch(err => { + errorMessage.value = "Generate request failed: " + (err instanceof Error ? err.message : String(err)); }); }; +const parseConfig = async () => { + try { + errorMessage.value = ""; + const res = await api.value?.parse_config({ + toml_config: toml_config.value + }); + + if (res.error) { + errorMessage.value = "Parse failed: " + res.error; + } else if (res.config) { + newNetworkConfig.value = res.config; + } else { + errorMessage.value = "API returned an unexpected response"; + } + } catch (e) { + errorMessage.value = "Parse request failed: " + (e instanceof Error ? e.message : String(e)); + } +}; +