mirror of
https://github.com/EasyTier/EasyTier.git
synced 2025-09-27 04:56:07 +08:00
Add conversion method from TomlConfigLoader to NetworkConfig to enhance configuration experience (#990)
Some checks failed
EasyTier Core / pre_job (push) Has been cancelled
EasyTier GUI / pre_job (push) Has been cancelled
EasyTier Mobile / pre_job (push) Has been cancelled
EasyTier Test / pre_job (push) Has been cancelled
EasyTier Core / build_web (push) Has been cancelled
EasyTier Core / build (freebsd-13.2-x86_64, 13.2, ubuntu-22.04, x86_64-unknown-freebsd) (push) Has been cancelled
EasyTier Core / build (linux-aarch64, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-arm, ubuntu-22.04, arm-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armhf, ubuntu-22.04, arm-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
Some checks failed
EasyTier Core / pre_job (push) Has been cancelled
EasyTier GUI / pre_job (push) Has been cancelled
EasyTier Mobile / pre_job (push) Has been cancelled
EasyTier Test / pre_job (push) Has been cancelled
EasyTier Core / build_web (push) Has been cancelled
EasyTier Core / build (freebsd-13.2-x86_64, 13.2, ubuntu-22.04, x86_64-unknown-freebsd) (push) Has been cancelled
EasyTier Core / build (linux-aarch64, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-arm, ubuntu-22.04, arm-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armhf, ubuntu-22.04, arm-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
* 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:
@@ -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: 当前节点信息
|
||||
|
@@ -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
|
||||
|
@@ -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<String, String> {
|
||||
Ok(toml.dump())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn generate_network_config(toml_config: String) -> Result<NetworkConfig, String> {
|
||||
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,
|
||||
|
@@ -8,5 +8,6 @@ onBeforeMount(async () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Toast position="bottom-right" />
|
||||
<RouterView />
|
||||
</template>
|
||||
|
2
easytier-gui/src/auto-imports.d.ts
vendored
2
easytier-gui/src/auto-imports.d.ts
vendored
@@ -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<typeof import('pinia')['defineStore']>
|
||||
readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
|
||||
readonly generateMenuItem: UnwrapRef<typeof import('./composables/tray')['generateMenuItem']>
|
||||
readonly generateNetworkConfig: UnwrapRef<typeof import('./composables/network')['generateNetworkConfig']>
|
||||
readonly getActivePinia: UnwrapRef<typeof import('pinia')['getActivePinia']>
|
||||
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
|
||||
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
|
||||
|
@@ -8,6 +8,10 @@ export async function parseNetworkConfig(cfg: NetworkConfig) {
|
||||
return invoke<string>('parse_network_config', { cfg })
|
||||
}
|
||||
|
||||
export async function generateNetworkConfig(tomlConfig: string) {
|
||||
return invoke<NetworkConfig>('generate_network_config', { tomlConfig })
|
||||
}
|
||||
|
||||
export async function runNetworkInstance(cfg: NetworkConfig) {
|
||||
return invoke('run_network_instance', { cfg })
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -269,17 +276,8 @@ function isRunning(id: string) {
|
||||
|
||||
<template>
|
||||
<div id="root" class="flex flex-col">
|
||||
<Dialog v-model:visible="visible" modal header="Config File" :style="{ width: '70%' }">
|
||||
<Panel>
|
||||
<ScrollPanel style="width: 100%; height: 300px">
|
||||
<pre>{{ tomlConfig }}</pre>
|
||||
</ScrollPanel>
|
||||
</Panel>
|
||||
<Divider />
|
||||
<div class="flex gap-2 justify-end">
|
||||
<Button type="button" :label="t('close')" @click="visible = false" />
|
||||
</div>
|
||||
</Dialog>
|
||||
<ConfigEditDialog v-model:visible="visible" :cur-network="curNetworkConfig" :readonly="activeStep !== '1'"
|
||||
:save-config="saveTomlConfig" :generate-config="parseNetworkConfig" />
|
||||
|
||||
<Dialog v-model:visible="aboutVisible" modal :header="t('about.title')" :style="{ width: '70%' }">
|
||||
<About />
|
||||
|
@@ -48,6 +48,12 @@ export const useNetworkStore = defineStore('networkStore', {
|
||||
this.curNetwork = this.networkList[nextCurNetworkIdx]
|
||||
},
|
||||
|
||||
replaceCurNetwork(cfg: NetworkTypes.NetworkConfig) {
|
||||
const curNetworkIdx = this.networkList.indexOf(this.curNetwork)
|
||||
this.networkList[curNetworkIdx] = cfg
|
||||
this.curNetwork = cfg
|
||||
},
|
||||
|
||||
removeNetworkInstance(instanceId: string) {
|
||||
delete this.instances[instanceId]
|
||||
},
|
||||
|
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;
|
@@ -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<Api.ApiClient>(() => new Api.ApiClient(apiHost.value));
|
||||
|
||||
|
||||
const apiHost = ref<string>(getInitialApiHost())
|
||||
const apiHostSuggestions = ref<Array<string>>([])
|
||||
const apiHostSearch = async (event: { query: string }) => {
|
||||
@@ -22,23 +21,46 @@ const apiHostSearch = async (event: { query: string }) => {
|
||||
}
|
||||
|
||||
const newNetworkConfig = ref<NetworkTypes.NetworkConfig>(NetworkTypes.DEFAULT_NETWORK_CONFIG());
|
||||
const toml_config = ref<string>("Press 'Run Network' to generate TOML configuration");
|
||||
const toml_config = ref<string>("");
|
||||
const errorMessage = ref<string>("");
|
||||
|
||||
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));
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -55,8 +77,17 @@ const generateConfig = (config: NetworkTypes.NetworkConfig) => {
|
||||
</div>
|
||||
<Config :cur-network="newNetworkConfig" @run-network="generateConfig" />
|
||||
</div>
|
||||
<div class="sm:w-full md:w-1/2 p-4 bg-gray-100">
|
||||
<pre class="whitespace-pre-wrap">{{ toml_config }}</pre>
|
||||
<div class="sm:w-full md:w-1/2 p-4 flex flex-col h-[calc(100vh-80px)]">
|
||||
<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>
|
||||
<Textarea
|
||||
v-model="toml_config"
|
||||
spellcheck="false"
|
||||
class="w-full flex-grow p-2 bg-gray-100 whitespace-pre-wrap font-mono border-none focus:outline-none resize-none"
|
||||
placeholder="Press 'Run Network' to generate TOML configuration, or paste your TOML configuration here to parse it"
|
||||
></Textarea>
|
||||
<div class="mt-3 flex justify-center">
|
||||
<Button label="Parse Config" icon="pi pi-arrow-left" icon-pos="left" @click="parseConfig" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { Toolbar, IftaLabel, Select, Button, ConfirmPopup, Dialog, useConfirm, useToast, Divider } from 'primevue';
|
||||
import { NetworkTypes, Status, Utils, Api, } from 'easytier-frontend-lib';
|
||||
import { NetworkTypes, Status, Utils, Api, ConfigEditDialog } from 'easytier-frontend-lib';
|
||||
import { watch, computed, onMounted, onUnmounted, ref } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
@@ -33,6 +33,7 @@ const curNetworkInfo = ref<NetworkTypes.NetworkInstance | null>(null);
|
||||
|
||||
const isEditing = ref(false);
|
||||
const showCreateNetworkDialog = ref(false);
|
||||
const showConfigEditDialog = ref(false);
|
||||
const newNetworkConfig = ref<NetworkTypes.NetworkConfig>(NetworkTypes.DEFAULT_NETWORK_CONFIG());
|
||||
|
||||
const listInstanceIdResponse = ref<Api.ListNetworkInstanceIdResponse | undefined>(undefined);
|
||||
@@ -103,7 +104,12 @@ const updateNetworkState = async (disabled: boolean) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (disabled || !disabledNetworkConfig.value) {
|
||||
await props.api?.update_device_instance_state(deviceId.value, selectedInstanceId.value.uuid, disabled);
|
||||
} else if (disabledNetworkConfig.value) {
|
||||
await props.api?.delete_network(deviceId.value, disabledNetworkConfig.value.instance_id);
|
||||
await props.api?.run_network(deviceId.value, disabledNetworkConfig.value);
|
||||
}
|
||||
await loadNetworkInstanceIds();
|
||||
}
|
||||
|
||||
@@ -217,9 +223,15 @@ const exportConfig = async () => {
|
||||
}
|
||||
|
||||
try {
|
||||
let ret = await props.api?.get_network_config(deviceId.value, instanceId.value);
|
||||
delete ret.instance_id;
|
||||
exportJsonFile(JSON.stringify(ret, null, 2),instanceId.value +'.json');
|
||||
let networkConfig = await props.api?.get_network_config(deviceId.value, instanceId.value);
|
||||
delete networkConfig.instance_id;
|
||||
let { toml_config: tomlConfig, error } = await props.api?.generate_config({
|
||||
config: networkConfig
|
||||
});
|
||||
if (error) {
|
||||
throw { response: { data: error } };
|
||||
}
|
||||
exportTomlFile(tomlConfig ?? '', instanceId.value + '.toml');
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
toast.add({ severity: 'error', summary: 'Error', detail: 'Failed to export network config, error: ' + JSON.stringify(e.response.data), life: 2000 });
|
||||
@@ -234,30 +246,34 @@ const importConfig = () => {
|
||||
const handleFileUpload = (event: Event) => {
|
||||
const files = (event.target as HTMLInputElement).files;
|
||||
const file = files ? files[0] : null;
|
||||
if (file) {
|
||||
if (!file) return;
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
reader.onload = async (e) => {
|
||||
try {
|
||||
let str = e.target?.result?.toString();
|
||||
if(str){
|
||||
const config = JSON.parse(str);
|
||||
if(config === null || typeof config !== "object"){
|
||||
throw new Error();
|
||||
let tomlConfig = e.target?.result?.toString();
|
||||
if (!tomlConfig) return;
|
||||
const resp = await props.api?.parse_config({ toml_config: tomlConfig });
|
||||
if (resp.error) {
|
||||
throw resp.error;
|
||||
}
|
||||
Object.assign(newNetworkConfig.value, config);
|
||||
|
||||
const config = resp.config;
|
||||
if (!config) return;
|
||||
|
||||
config.instance_id = newNetworkConfig.value?.instance_id ?? config?.instance_id;
|
||||
|
||||
Object.assign(newNetworkConfig.value, resp.config);
|
||||
toast.add({ severity: 'success', summary: 'Import Success', detail: "Config file import success", life: 2000 });
|
||||
}
|
||||
} catch (error) {
|
||||
toast.add({ severity: 'error', summary: 'Error', detail: 'Config file parse error.', life: 2000 });
|
||||
toast.add({ severity: 'error', summary: 'Error', detail: 'Config file parse error: ' + error, life: 2000 });
|
||||
}
|
||||
configFile.value.value = null;
|
||||
}
|
||||
reader.readAsText(file);
|
||||
}
|
||||
}
|
||||
|
||||
const exportJsonFile = (context: string, name: string) => {
|
||||
let url = window.URL.createObjectURL(new Blob([context], { type: 'application/json' }));
|
||||
const exportTomlFile = (context: string, name: string) => {
|
||||
let url = window.URL.createObjectURL(new Blob([context], { type: 'application/toml' }));
|
||||
let link = document.createElement('a');
|
||||
link.style.display = 'none';
|
||||
link.href = url;
|
||||
@@ -269,6 +285,31 @@ const exportJsonFile = (context: string, name: string) => {
|
||||
window.URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
const generateConfig = async (config: NetworkTypes.NetworkConfig): Promise<string> => {
|
||||
let { toml_config: tomlConfig, error } = await props.api?.generate_config({ config });
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
return tomlConfig ?? '';
|
||||
}
|
||||
|
||||
const saveConfig = async (tomlConfig: string): Promise<void> => {
|
||||
let resp = await props.api?.parse_config({ toml_config: tomlConfig });
|
||||
if (resp.error) {
|
||||
throw resp.error;
|
||||
};
|
||||
const config = resp.config;
|
||||
if (!config) {
|
||||
throw new Error("Parsed config is empty");
|
||||
}
|
||||
config.instance_id = disabledNetworkConfig.value?.instance_id ?? config?.instance_id;
|
||||
if (networkIsDisabled.value) {
|
||||
disabledNetworkConfig.value = config;
|
||||
} else {
|
||||
newNetworkConfig.value = config;
|
||||
}
|
||||
}
|
||||
|
||||
let periodFunc = new Utils.PeriodicTask(async () => {
|
||||
try {
|
||||
await Promise.all([loadNetworkInstanceIds(), loadDeviceInfo()]);
|
||||
@@ -288,18 +329,23 @@ onUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input type="file" @change="handleFileUpload" class="hidden" accept="application/json" ref="configFile"/>
|
||||
<input type="file" @change="handleFileUpload" class="hidden" accept="application/toml" ref="configFile" />
|
||||
<ConfirmPopup></ConfirmPopup>
|
||||
<Dialog v-model:visible="showCreateNetworkDialog" modal :header="!isEditing ? 'Create New Network' : 'Edit Network'"
|
||||
:style="{ width: '55rem' }">
|
||||
<Dialog v-if="!networkIsDisabled" v-model:visible="showCreateNetworkDialog" modal
|
||||
:header="!isEditing ? 'Create New Network' : 'Edit Network'" :style="{ width: '55rem' }">
|
||||
<div class="flex flex-col">
|
||||
<div class="w-11/12 self-center ">
|
||||
<div class="w-11/12 self-center space-x-2">
|
||||
<Button @click="showConfigEditDialog = true" icon="pi pi-pen-to-square" label="Edit File" iconPos="right" />
|
||||
<Button @click="importConfig" icon="pi pi-file-import" label="Import" iconPos="right" />
|
||||
</div>
|
||||
</div>
|
||||
<Divider />
|
||||
</div>
|
||||
</div>
|
||||
<Config :cur-network="newNetworkConfig" @run-network="createNewNetwork"></Config>
|
||||
</Dialog>
|
||||
<ConfigEditDialog v-if="networkIsDisabled" v-model:visible="showCreateNetworkDialog"
|
||||
:cur-network="disabledNetworkConfig" :generate-config="generateConfig" :save-config="saveConfig" />
|
||||
<ConfigEditDialog v-else v-model:visible="showConfigEditDialog" :cur-network="newNetworkConfig"
|
||||
:generate-config="generateConfig" :save-config="saveConfig" />
|
||||
|
||||
<Toolbar>
|
||||
<template #start>
|
||||
|
@@ -11,7 +11,7 @@ use axum::{extract::State, routing::get, Json, Router};
|
||||
use axum_login::tower_sessions::{ExpiredDeletion, SessionManagerLayer};
|
||||
use axum_login::{login_required, AuthManagerLayerBuilder, AuthUser, AuthzBackend};
|
||||
use axum_messages::MessagesManagerLayer;
|
||||
use easytier::common::config::ConfigLoader;
|
||||
use easytier::common::config::{ConfigLoader, TomlConfigLoader};
|
||||
use easytier::common::scoped_task::ScopedTask;
|
||||
use easytier::launcher::NetworkConfig;
|
||||
use easytier::proto::rpc_types;
|
||||
@@ -68,6 +68,17 @@ struct GenerateConfigResponse {
|
||||
toml_config: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
struct ParseConfigRequest {
|
||||
toml_config: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
struct ParseConfigResponse {
|
||||
error: Option<String>,
|
||||
config: Option<NetworkConfig>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Error {
|
||||
message: String,
|
||||
@@ -158,6 +169,25 @@ impl RestfulServer {
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_parse_config(
|
||||
Json(req): Json<ParseConfigRequest>,
|
||||
) -> Result<Json<ParseConfigResponse>, HttpHandleError> {
|
||||
let config = TomlConfigLoader::new_from_str(&req.toml_config)
|
||||
.and_then(|config| NetworkConfig::new_from_config(&config));
|
||||
match config {
|
||||
Ok(c) => Ok(ParseConfigResponse {
|
||||
error: None,
|
||||
config: Some(c),
|
||||
}
|
||||
.into()),
|
||||
Err(e) => Ok(ParseConfigResponse {
|
||||
error: Some(format!("{:?}", e)),
|
||||
config: None,
|
||||
}
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn start(
|
||||
mut self,
|
||||
) -> Result<
|
||||
@@ -216,6 +246,7 @@ impl RestfulServer {
|
||||
"/api/v1/generate-config",
|
||||
post(Self::handle_generate_config),
|
||||
)
|
||||
.route("/api/v1/parse-config", post(Self::handle_parse_config))
|
||||
.layer(MessagesManagerLayer)
|
||||
.layer(auth_layer)
|
||||
.layer(tower_http::cors::CorsLayer::very_permissive())
|
||||
|
@@ -752,4 +752,396 @@ impl NetworkConfig {
|
||||
cfg.set_flags(flags);
|
||||
Ok(cfg)
|
||||
}
|
||||
|
||||
pub fn new_from_config(config: &TomlConfigLoader) -> Result<Self, anyhow::Error> {
|
||||
let default_config = TomlConfigLoader::default();
|
||||
|
||||
let mut result = Self::default();
|
||||
|
||||
result.instance_id = Some(config.get_id().to_string());
|
||||
if config.get_hostname() != default_config.get_hostname() {
|
||||
result.hostname = Some(config.get_hostname());
|
||||
}
|
||||
|
||||
result.dhcp = Some(config.get_dhcp());
|
||||
|
||||
let network_identity = config.get_network_identity();
|
||||
result.network_name = Some(network_identity.network_name.clone());
|
||||
result.network_secret = network_identity.network_secret.clone();
|
||||
|
||||
if let Some(ipv4) = config.get_ipv4() {
|
||||
result.virtual_ipv4 = Some(ipv4.address().to_string());
|
||||
result.network_length = Some(ipv4.network_length() as i32);
|
||||
}
|
||||
|
||||
let peers = config.get_peers();
|
||||
match peers.len() {
|
||||
1 => {
|
||||
result.networking_method = Some(NetworkingMethod::PublicServer as i32);
|
||||
result.public_server_url = Some(peers[0].uri.to_string());
|
||||
}
|
||||
0 => {
|
||||
result.networking_method = Some(NetworkingMethod::Standalone as i32);
|
||||
}
|
||||
_ => {
|
||||
result.networking_method = Some(NetworkingMethod::Manual as i32);
|
||||
result.peer_urls = peers.iter().map(|p| p.uri.to_string()).collect();
|
||||
}
|
||||
}
|
||||
|
||||
result.listener_urls = config
|
||||
.get_listeners()
|
||||
.unwrap_or_else(|| vec![])
|
||||
.iter()
|
||||
.map(|l| l.to_string())
|
||||
.collect();
|
||||
|
||||
result.proxy_cidrs = config
|
||||
.get_proxy_cidrs()
|
||||
.iter()
|
||||
.map(|c| {
|
||||
if let Some(mapped) = c.mapped_cidr {
|
||||
format!("{}->{}", c.cidr, mapped)
|
||||
} else {
|
||||
c.cidr.to_string()
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
if let Some(rpc_portal) = config.get_rpc_portal() {
|
||||
result.rpc_port = Some(rpc_portal.port() as i32);
|
||||
}
|
||||
|
||||
if let Some(whitelist) = config.get_rpc_portal_whitelist() {
|
||||
result.rpc_portal_whitelists = whitelist.iter().map(|w| w.to_string()).collect();
|
||||
}
|
||||
|
||||
if let Some(vpn_config) = config.get_vpn_portal_config() {
|
||||
result.enable_vpn_portal = Some(true);
|
||||
|
||||
let cidr = vpn_config.client_cidr;
|
||||
result.vpn_portal_client_network_addr = Some(cidr.first_address().to_string());
|
||||
result.vpn_portal_client_network_len = Some(cidr.network_length() as i32);
|
||||
|
||||
result.vpn_portal_listen_port = Some(vpn_config.wireguard_listen.port() as i32);
|
||||
}
|
||||
|
||||
if let Some(routes) = config.get_routes() {
|
||||
if !routes.is_empty() {
|
||||
result.enable_manual_routes = Some(true);
|
||||
result.routes = routes.iter().map(|r| r.to_string()).collect();
|
||||
}
|
||||
}
|
||||
|
||||
let exit_nodes = config.get_exit_nodes();
|
||||
if !exit_nodes.is_empty() {
|
||||
result.exit_nodes = exit_nodes.iter().map(|n| n.to_string()).collect();
|
||||
}
|
||||
|
||||
if let Some(socks5_portal) = config.get_socks5_portal() {
|
||||
result.enable_socks5 = Some(true);
|
||||
result.socks5_port = socks5_portal.port().map(|p| p as i32);
|
||||
}
|
||||
|
||||
let mapped_listeners = config.get_mapped_listeners();
|
||||
if !mapped_listeners.is_empty() {
|
||||
result.mapped_listeners = mapped_listeners.iter().map(|l| l.to_string()).collect();
|
||||
}
|
||||
|
||||
let flags = config.get_flags();
|
||||
result.latency_first = Some(flags.latency_first);
|
||||
result.dev_name = Some(flags.dev_name.clone());
|
||||
result.use_smoltcp = Some(flags.use_smoltcp);
|
||||
result.enable_kcp_proxy = Some(flags.enable_kcp_proxy);
|
||||
result.disable_kcp_input = Some(flags.disable_kcp_input);
|
||||
result.enable_quic_proxy = Some(flags.enable_quic_proxy);
|
||||
result.disable_quic_input = Some(flags.disable_quic_input);
|
||||
result.disable_p2p = Some(flags.disable_p2p);
|
||||
result.bind_device = Some(flags.bind_device);
|
||||
result.no_tun = Some(flags.no_tun);
|
||||
result.enable_exit_node = Some(flags.enable_exit_node);
|
||||
result.relay_all_peer_rpc = Some(flags.relay_all_peer_rpc);
|
||||
result.multi_thread = Some(flags.multi_thread);
|
||||
result.proxy_forward_by_system = Some(flags.proxy_forward_by_system);
|
||||
result.disable_encryption = Some(!flags.enable_encryption);
|
||||
result.disable_udp_hole_punching = Some(flags.disable_udp_hole_punching);
|
||||
result.enable_magic_dns = Some(flags.accept_dns);
|
||||
result.mtu = Some(flags.mtu as i32);
|
||||
result.enable_private_mode = Some(flags.private_mode);
|
||||
|
||||
if !flags.relay_network_whitelist.is_empty() && flags.relay_network_whitelist != "*" {
|
||||
result.enable_relay_network_whitelist = Some(true);
|
||||
result.relay_network_whitelist = flags
|
||||
.relay_network_whitelist
|
||||
.split_whitespace()
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::common::config::ConfigLoader;
|
||||
use rand::Rng;
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
fn gen_default_config() -> crate::common::config::TomlConfigLoader {
|
||||
let config = crate::common::config::TomlConfigLoader::default();
|
||||
config.set_id(uuid::Uuid::new_v4());
|
||||
config.set_dhcp(false);
|
||||
config.set_inst_name("default".to_string());
|
||||
config.set_listeners(vec![]);
|
||||
config.set_rpc_portal(std::net::SocketAddr::from(([0, 0, 0, 0], 0)));
|
||||
config
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_network_config_conversion_basic() -> Result<(), anyhow::Error> {
|
||||
let config = gen_default_config();
|
||||
|
||||
let network_config = super::NetworkConfig::new_from_config(&config)?;
|
||||
|
||||
let generated_config = network_config.gen_config()?;
|
||||
|
||||
let config_str = config.dump();
|
||||
let generated_config_str = generated_config.dump();
|
||||
|
||||
assert_eq!(
|
||||
config_str, generated_config_str,
|
||||
"Generated config does not match original config:\nOriginal:\n{}\n\nGenerated:\n{}\nNetwork Config: {}\n",
|
||||
config_str, generated_config_str, serde_json::to_string(&network_config).unwrap()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_network_config_conversion_random() -> Result<(), anyhow::Error> {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
for _ in 0..100 {
|
||||
let config = gen_default_config();
|
||||
|
||||
config.set_id(uuid::Uuid::new_v4());
|
||||
|
||||
config.set_dhcp(rng.gen_bool(0.5));
|
||||
|
||||
if rng.gen_bool(0.7) {
|
||||
let hostname = format!("host-{}", rng.gen::<u16>());
|
||||
config.set_hostname(Some(hostname));
|
||||
}
|
||||
|
||||
config.set_network_identity(crate::common::config::NetworkIdentity::new(
|
||||
format!("network-{}", rng.gen::<u16>()),
|
||||
format!("secret-{}", rng.gen::<u64>()),
|
||||
));
|
||||
config.set_inst_name(config.get_network_identity().network_name.clone());
|
||||
|
||||
if !config.get_dhcp() {
|
||||
let addr = Ipv4Addr::new(
|
||||
rng.gen_range(1..254),
|
||||
rng.gen_range(0..255),
|
||||
rng.gen_range(0..255),
|
||||
rng.gen_range(1..254),
|
||||
);
|
||||
let prefix_len = rng.gen_range(1..31);
|
||||
let ipv4 = format!("{}/{}", addr, prefix_len).parse().unwrap();
|
||||
config.set_ipv4(Some(ipv4));
|
||||
}
|
||||
|
||||
let peer_count = rng.gen_range(0..3);
|
||||
let mut peers = Vec::new();
|
||||
for _ in 0..peer_count {
|
||||
let port = rng.gen_range(10000..60000);
|
||||
let protocol = if rng.gen_bool(0.5) { "tcp" } else { "udp" };
|
||||
let uri = format!("{}://127.0.0.1:{}", protocol, port)
|
||||
.parse()
|
||||
.unwrap();
|
||||
peers.push(crate::common::config::PeerConfig { uri });
|
||||
}
|
||||
config.set_peers(peers);
|
||||
|
||||
if rng.gen_bool(0.7) {
|
||||
let listener_count = rng.gen_range(0..3);
|
||||
let mut listeners = Vec::new();
|
||||
for _ in 0..listener_count {
|
||||
let port = rng.gen_range(10000..60000);
|
||||
let protocol = if rng.gen_bool(0.5) { "tcp" } else { "udp" };
|
||||
listeners.push(format!("{}://0.0.0.0:{}", protocol, port).parse().unwrap());
|
||||
}
|
||||
config.set_listeners(listeners);
|
||||
}
|
||||
|
||||
if rng.gen_bool(0.6) {
|
||||
let proxy_count = rng.gen_range(0..3);
|
||||
for _ in 0..proxy_count {
|
||||
let network = format!(
|
||||
"{}.{}.{}.0/{}",
|
||||
rng.gen_range(1..254),
|
||||
rng.gen_range(0..255),
|
||||
rng.gen_range(0..255),
|
||||
rng.gen_range(24..30)
|
||||
)
|
||||
.parse::<cidr::Ipv4Cidr>()
|
||||
.unwrap();
|
||||
|
||||
let mapped_network = if rng.gen_bool(0.5) {
|
||||
Some(
|
||||
format!(
|
||||
"{}.{}.{}.0/{}",
|
||||
rng.gen_range(1..254),
|
||||
rng.gen_range(0..255),
|
||||
rng.gen_range(0..255),
|
||||
network.network_length()
|
||||
)
|
||||
.parse::<cidr::Ipv4Cidr>()
|
||||
.unwrap(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
config.add_proxy_cidr(network, mapped_network);
|
||||
}
|
||||
}
|
||||
|
||||
if rng.gen_bool(0.8) {
|
||||
let port = rng.gen_range(0..65535);
|
||||
config.set_rpc_portal(std::net::SocketAddr::from(([0, 0, 0, 0], port)));
|
||||
|
||||
if rng.gen_bool(0.6) {
|
||||
let whitelist_count = rng.gen_range(1..3);
|
||||
let mut whitelist = Vec::new();
|
||||
for _ in 0..whitelist_count {
|
||||
let ip = Ipv4Addr::new(
|
||||
rng.gen_range(1..254),
|
||||
rng.gen_range(0..255),
|
||||
rng.gen_range(0..255),
|
||||
rng.gen_range(0..255),
|
||||
);
|
||||
let cidr = format!("{}/32", ip);
|
||||
whitelist.push(cidr.parse().unwrap());
|
||||
}
|
||||
config.set_rpc_portal_whitelist(Some(whitelist));
|
||||
}
|
||||
}
|
||||
|
||||
if rng.gen_bool(0.5) {
|
||||
let vpn_network = format!(
|
||||
"{}.{}.{}.0/{}",
|
||||
rng.gen_range(10..173),
|
||||
rng.gen_range(0..255),
|
||||
rng.gen_range(0..255),
|
||||
rng.gen_range(24..30)
|
||||
);
|
||||
let vpn_port = rng.gen_range(10000..60000);
|
||||
config.set_vpn_portal_config(crate::common::config::VpnPortalConfig {
|
||||
client_cidr: vpn_network.parse().unwrap(),
|
||||
wireguard_listen: format!("0.0.0.0:{}", vpn_port).parse().unwrap(),
|
||||
});
|
||||
}
|
||||
|
||||
if rng.gen_bool(0.6) {
|
||||
let route_count = rng.gen_range(1..3);
|
||||
let mut routes = Vec::new();
|
||||
for _ in 0..route_count {
|
||||
let route = format!(
|
||||
"{}.{}.{}.0/{}",
|
||||
rng.gen_range(1..254),
|
||||
rng.gen_range(0..255),
|
||||
rng.gen_range(0..255),
|
||||
rng.gen_range(24..30)
|
||||
);
|
||||
routes.push(route.parse().unwrap());
|
||||
}
|
||||
config.set_routes(Some(routes));
|
||||
}
|
||||
|
||||
if rng.gen_bool(0.4) {
|
||||
let node_count = rng.gen_range(1..3);
|
||||
let mut nodes = Vec::new();
|
||||
for _ in 0..node_count {
|
||||
let ip = Ipv4Addr::new(
|
||||
rng.gen_range(1..254),
|
||||
rng.gen_range(0..255),
|
||||
rng.gen_range(0..255),
|
||||
rng.gen_range(1..254),
|
||||
);
|
||||
nodes.push(ip);
|
||||
}
|
||||
config.set_exit_nodes(nodes);
|
||||
}
|
||||
|
||||
if rng.gen_bool(0.5) {
|
||||
let socks5_port = rng.gen_range(10000..60000);
|
||||
config.set_socks5_portal(Some(
|
||||
format!("socks5://0.0.0.0:{}", socks5_port).parse().unwrap(),
|
||||
));
|
||||
}
|
||||
|
||||
if rng.gen_bool(0.4) {
|
||||
let count = rng.gen_range(1..3);
|
||||
let mut mapped_listeners = Vec::new();
|
||||
for _ in 0..count {
|
||||
let port = rng.gen_range(10000..60000);
|
||||
mapped_listeners.push(format!("tcp://0.0.0.0:{}", port).parse().unwrap());
|
||||
}
|
||||
config.set_mapped_listeners(Some(mapped_listeners));
|
||||
}
|
||||
|
||||
if rng.gen_bool(0.9) {
|
||||
let mut flags = crate::common::config::gen_default_flags();
|
||||
flags.latency_first = rng.gen_bool(0.5);
|
||||
flags.dev_name = format!("etun{}", rng.gen_range(0..10));
|
||||
flags.use_smoltcp = rng.gen_bool(0.3);
|
||||
flags.enable_kcp_proxy = rng.gen_bool(0.5);
|
||||
flags.disable_kcp_input = rng.gen_bool(0.3);
|
||||
flags.enable_quic_proxy = rng.gen_bool(0.5);
|
||||
flags.disable_quic_input = rng.gen_bool(0.3);
|
||||
flags.disable_p2p = rng.gen_bool(0.2);
|
||||
flags.bind_device = rng.gen_bool(0.3);
|
||||
flags.no_tun = rng.gen_bool(0.1);
|
||||
flags.enable_exit_node = rng.gen_bool(0.4);
|
||||
flags.relay_all_peer_rpc = rng.gen_bool(0.5);
|
||||
flags.multi_thread = rng.gen_bool(0.7);
|
||||
flags.proxy_forward_by_system = rng.gen_bool(0.3);
|
||||
flags.enable_encryption = rng.gen_bool(0.8);
|
||||
flags.disable_udp_hole_punching = rng.gen_bool(0.2);
|
||||
flags.accept_dns = rng.gen_bool(0.6);
|
||||
flags.mtu = rng.gen_range(1200..1500);
|
||||
flags.private_mode = rng.gen_bool(0.3);
|
||||
|
||||
if rng.gen_bool(0.4) {
|
||||
flags.relay_network_whitelist = (0..rng.gen_range(1..3))
|
||||
.map(|_| {
|
||||
format!(
|
||||
"{}.{}.0.0/16",
|
||||
rng.gen_range(10..192),
|
||||
rng.gen_range(0..255)
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
config.set_flags(flags);
|
||||
}
|
||||
|
||||
let network_config = super::NetworkConfig::new_from_config(&config)?;
|
||||
let generated_config = network_config.gen_config()?;
|
||||
generated_config.set_peers(generated_config.get_peers()); // Ensure peers field is not None
|
||||
|
||||
let config_str = config.dump();
|
||||
let generated_config_str = generated_config.dump();
|
||||
|
||||
assert_eq!(
|
||||
config_str, generated_config_str,
|
||||
"Generated config does not match original config:\nOriginal:\n{}\n\nGenerated:\n{}\nNetwork Config: {}\n",
|
||||
config_str, generated_config_str, serde_json::to_string(&network_config).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user