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;
|
||||
@@ -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 { Toolbar, IftaLabel, Select, Button, ConfirmPopup, Dialog, useConfirm, useToast, Divider } from 'primevue';
|
||||
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;
|
||||
}
|
||||
|
||||
await props.api?.update_device_instance_state(deviceId.value, selectedInstanceId.value.uuid, disabled);
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -211,62 +217,97 @@ const loadDeviceInfo = async () => {
|
||||
}
|
||||
|
||||
const exportConfig = async () => {
|
||||
if (!deviceId.value || !instanceId.value) {
|
||||
toast.add({ severity: 'error', summary: 'Error', detail: 'No network instance selected', life: 2000 });
|
||||
return;
|
||||
}
|
||||
if (!deviceId.value || !instanceId.value) {
|
||||
toast.add({ severity: 'error', summary: 'Error', detail: 'No network instance selected', life: 2000 });
|
||||
return;
|
||||
}
|
||||
|
||||
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');
|
||||
} 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 });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
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 });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const importConfig = () => {
|
||||
configFile.value.click();
|
||||
configFile.value.click();
|
||||
}
|
||||
|
||||
const handleFileUpload = (event: Event) => {
|
||||
const files = (event.target as HTMLInputElement).files;
|
||||
const file = files ? files[0] : null;
|
||||
if (file) {
|
||||
const files = (event.target as HTMLInputElement).files;
|
||||
const file = files ? files[0] : null;
|
||||
if (!file) return;
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
let str = e.target?.result?.toString();
|
||||
if(str){
|
||||
const config = JSON.parse(str);
|
||||
if(config === null || typeof config !== "object"){
|
||||
throw new Error();
|
||||
}
|
||||
Object.assign(newNetworkConfig.value, config);
|
||||
toast.add({ severity: 'success', summary: 'Import Success', detail: "Config file import success", life: 2000 });
|
||||
reader.onload = async (e) => {
|
||||
try {
|
||||
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;
|
||||
}
|
||||
|
||||
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: ' + error, life: 2000 });
|
||||
}
|
||||
} catch (error) {
|
||||
toast.add({ severity: 'error', summary: 'Error', detail: 'Config file parse error.', life: 2000 });
|
||||
}
|
||||
configFile.value.value = null;
|
||||
configFile.value.value = null;
|
||||
}
|
||||
reader.readAsText(file);
|
||||
}
|
||||
}
|
||||
|
||||
const exportJsonFile = (context: string, name: string) => {
|
||||
let url = window.URL.createObjectURL(new Blob([context], { type: 'application/json' }));
|
||||
let link = document.createElement('a');
|
||||
link.style.display = 'none';
|
||||
link.href = url;
|
||||
link.setAttribute('download', name);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
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;
|
||||
link.setAttribute('download', name);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(url);
|
||||
document.body.removeChild(link);
|
||||
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 () => {
|
||||
@@ -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 ">
|
||||
<Button @click="importConfig" icon="pi pi-file-import" label="Import" iconPos="right" />
|
||||
<Divider />
|
||||
</div>
|
||||
<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 />
|
||||
<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>
|
||||
@@ -329,7 +375,7 @@ onUnmounted(() => {
|
||||
</Status>
|
||||
<Divider />
|
||||
<div class="text-center">
|
||||
<Button @click="updateNetworkState(true)" label="Disable Network" severity="warn" />
|
||||
<Button @click="updateNetworkState(true)" label="Disable Network" severity="warn" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -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())
|
||||
|
||||
Reference in New Issue
Block a user