mirror of
https://mirror.suhoan.cn/https://github.com/EasyTier/EasyTier.git
synced 2025-12-15 06:07:23 +08:00
Add conversion method from TomlConfigLoader to NetworkConfig to enhance configuration experience (#990)
* add method to create NetworkConfig from TomlConfigLoader * allow web export/import toml config file and gui edit toml config * Extract the configuration file dialog into a separate component and allow direct editing of the configuration file on the web
This commit is contained in:
103
easytier-web/frontend-lib/src/components/ConfigEditDialog.vue
Normal file
103
easytier-web/frontend-lib/src/components/ConfigEditDialog.vue
Normal file
@@ -0,0 +1,103 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { NetworkConfig } from '../types/network';
|
||||
import { Divider, Button, Dialog, Textarea } from 'primevue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps({
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
generateConfig: {
|
||||
type: Object as () => (config: NetworkConfig) => Promise<string>,
|
||||
required: true,
|
||||
},
|
||||
saveConfig: {
|
||||
type: Object as () => (config: string) => Promise<void>,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const curNetwork = defineModel('curNetwork', {
|
||||
type: Object as () => NetworkConfig | undefined,
|
||||
required: true,
|
||||
})
|
||||
|
||||
const visible = defineModel('visible', {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
})
|
||||
watch([visible, curNetwork], async ([newVisible, newCurNetwork]) => {
|
||||
if (!newVisible) {
|
||||
tomlConfig.value = '';
|
||||
return;
|
||||
}
|
||||
if (!newCurNetwork) {
|
||||
tomlConfig.value = '';
|
||||
return;
|
||||
}
|
||||
const config = newCurNetwork;
|
||||
try {
|
||||
errorMessage.value = '';
|
||||
tomlConfig.value = await props.generateConfig(config);
|
||||
} catch (e) {
|
||||
errorMessage.value = 'Failed to generate config: ' + (e instanceof Error ? e.message : String(e));
|
||||
tomlConfig.value = '';
|
||||
}
|
||||
})
|
||||
onMounted(async () => {
|
||||
if (!visible.value) {
|
||||
return;
|
||||
}
|
||||
if (!curNetwork.value) {
|
||||
tomlConfig.value = '';
|
||||
return;
|
||||
}
|
||||
const config = curNetwork.value;
|
||||
try {
|
||||
tomlConfig.value = await props.generateConfig(config);
|
||||
errorMessage.value = '';
|
||||
} catch (e) {
|
||||
errorMessage.value = 'Failed to generate config: ' + (e instanceof Error ? e.message : String(e));
|
||||
tomlConfig.value = '';
|
||||
}
|
||||
});
|
||||
|
||||
const handleConfigSave = async () => {
|
||||
if (props.readonly) return;
|
||||
try {
|
||||
await props.saveConfig(tomlConfig.value);
|
||||
visible.value = false;
|
||||
} catch (e) {
|
||||
errorMessage.value = 'Failed to save config: ' + (e instanceof Error ? e.message : String(e));
|
||||
}
|
||||
};
|
||||
|
||||
const tomlConfig = ref<string>('')
|
||||
const tomlConfigRows = ref<number>(1);
|
||||
const errorMessage = ref<string>('');
|
||||
|
||||
watch(tomlConfig, (newValue) => {
|
||||
tomlConfigRows.value = newValue.split('\n').length;
|
||||
errorMessage.value = '';
|
||||
});
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<Dialog v-model:visible="visible" modal :header="t('config_file')" :style="{ width: '70%' }">
|
||||
<pre v-if="errorMessage"
|
||||
class="mb-2 p-2 rounded text-sm overflow-auto bg-red-100 text-red-700 max-h-40">{{ errorMessage }}</pre>
|
||||
<div class="flex w-full" style="max-height: 60vh; overflow-y: auto;">
|
||||
<Textarea v-model="tomlConfig" class="w-full h-full font-mono flex flex-col resize-none" :rows="tomlConfigRows"
|
||||
spellcheck="false" :readonly="props.readonly"></Textarea>
|
||||
</div>
|
||||
<Divider />
|
||||
<div class="flex gap-2 justify-end">
|
||||
<Button v-if="!props.readonly" type="button" :label="t('save')" @click="handleConfigSave" />
|
||||
<Button type="button" :label="t('close')" @click="visible = false" />
|
||||
</div>
|
||||
</Dialog>
|
||||
</template>
|
||||
@@ -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';
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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: 当前节点信息
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<ParseConfigResponse> {
|
||||
try {
|
||||
const response = await this.client.post<any, ParseConfigResponse>('/parse-config', config);
|
||||
return response;
|
||||
} catch (error) {
|
||||
if (error instanceof AxiosError) {
|
||||
return { error: error.response?.data };
|
||||
}
|
||||
return { error: 'Unknown error: ' + error };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ApiClient;
|
||||
Reference in New Issue
Block a user