refactor(gui): refactor gui to use RemoteClient trait and RemoteManagement component (#1489)

* refactor(gui): refactor gui to use RemoteClient trait and RemoteManagement component
* feat(gui): Add network config saving and refactor RemoteManagement
This commit is contained in:
Mg Pig
2025-10-20 22:07:01 +08:00
committed by GitHub
parent 67ac9b00ff
commit eba9504fc2
27 changed files with 1040 additions and 793 deletions

View File

@@ -1,16 +1,16 @@
<script setup lang="ts">
import { Button, ConfirmPopup, Divider, IftaLabel, Menu, Select, useConfirm, useToast } from 'primevue';
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
import { Button, ConfirmPopup, Divider, IftaLabel, Menu, Message, Select, Tag, useConfirm, useToast } from 'primevue';
import { computed, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import * as Api from '../modules/api';
import { RemoteClient } from '../modules/api';
import * as Utils from '../modules/utils';
import * as NetworkTypes from '../types/network';
import { type MenuItem } from 'primevue/menuitem';
const { t } = useI18n()
const props = defineProps<{
api: RemoteClient;
api: Api.RemoteClient;
newConfigGenerator?: () => NetworkTypes.NetworkConfig;
}>();
@@ -27,15 +27,16 @@ const configFile = ref();
const curNetworkInfo = ref<NetworkTypes.NetworkInstance | null>(null);
const isEditing = ref(false);
// const showCreateNetworkDialog = ref(false);
const showConfigEditDialog = ref(false);
const isCreatingNetwork = ref(false); // Flag to indicate if we're in network creation mode
const editingNetworkConfig = ref<NetworkTypes.NetworkConfig>(NetworkTypes.DEFAULT_NETWORK_CONFIG());
const isEditingNetwork = ref(false); // Flag to indicate if we're in network editing mode
const currentNetworkConfig = ref<NetworkTypes.NetworkConfig | undefined>(undefined);
const listInstanceIdResponse = ref<Api.ListNetworkInstanceIdResponse | undefined>(undefined);
const isRunning = (instanceId: string) => {
return listInstanceIdResponse.value?.running_inst_ids.map(Utils.UuidToStr).includes(instanceId);
}
const instanceIdList = computed(() => {
let insts = new Set<string>();
let t = listInstanceIdResponse.value;
@@ -59,7 +60,7 @@ const selectedInstanceId = computed({
}
});
watch(selectedInstanceId, async (newVal, oldVal) => {
if (newVal?.uuid !== oldVal?.uuid && networkIsDisabled.value) {
if (newVal?.uuid !== oldVal?.uuid && (networkIsDisabled.value || isEditingNetwork.value)) {
await loadCurrentNetworkConfig();
}
});
@@ -144,12 +145,10 @@ const confirmDeleteNetwork = (event: any) => {
const saveAndRunNewNetwork = async () => {
try {
if (isEditing.value) {
await props.api.delete_network(instanceId.value!);
}
let ret = await props.api.run_network(editingNetworkConfig.value);
await props.api.delete_network(instanceId.value!);
let ret = await props.api.run_network(currentNetworkConfig.value!!);
console.debug("saveAndRunNewNetwork", ret);
selectedInstanceId.value = { uuid: editingNetworkConfig.value.instance_id };
selectedInstanceId.value = { uuid: currentNetworkConfig.value!.instance_id };
} catch (e: any) {
console.error(e);
toast.add({ severity: 'error', summary: 'Error', detail: 'Failed to create network, error: ' + JSON.stringify(e.response.data), life: 2000 });
@@ -157,18 +156,26 @@ const saveAndRunNewNetwork = async () => {
}
emits('update');
// showCreateNetworkDialog.value = false;
isCreatingNetwork.value = false; // Exit creation mode after successful network creation
isEditingNetwork.value = false; // Exit creation mode after successful network creation
}
const newNetwork = () => {
editingNetworkConfig.value = props.newConfigGenerator?.() ?? NetworkTypes.DEFAULT_NETWORK_CONFIG();
isEditing.value = false;
// showCreateNetworkDialog.value = true; // Old dialog approach
isCreatingNetwork.value = true; // Switch to creation mode instead
const saveNetworkConfig = async () => {
if (!currentNetworkConfig.value) {
return;
}
await props.api.save_config(currentNetworkConfig.value);
toast.add({ severity: 'success', summary: t("web.common.success"), detail: t("web.device_management.config_saved"), life: 2000 });
}
const newNetwork = async () => {
const newNetworkConfig = props.newConfigGenerator?.() ?? NetworkTypes.DEFAULT_NETWORK_CONFIG();
await props.api.save_config(newNetworkConfig);
selectedInstanceId.value = { uuid: newNetworkConfig.instance_id };
currentNetworkConfig.value = newNetworkConfig;
await loadNetworkInstanceIds();
}
const cancelNetworkCreation = () => {
isCreatingNetwork.value = false;
const cancelEditNetwork = () => {
isEditingNetwork.value = false;
}
const editNetwork = async () => {
@@ -177,14 +184,11 @@ const editNetwork = async () => {
return;
}
isEditing.value = true;
try {
let ret = await props.api.get_network_config(instanceId.value!);
console.debug("editNetwork", ret);
editingNetworkConfig.value = ret;
// showCreateNetworkDialog.value = true; // Old dialog approach
isCreatingNetwork.value = true; // Switch to creation mode instead
currentNetworkConfig.value = ret;
isEditingNetwork.value = true; // Switch to editing mode instead
} catch (e: any) {
console.error(e);
toast.add({ severity: 'error', summary: 'Error', detail: 'Failed to edit network, error: ' + JSON.stringify(e.response.data), life: 2000 });
@@ -194,7 +198,6 @@ const editNetwork = async () => {
const loadNetworkInstanceIds = async () => {
listInstanceIdResponse.value = await props.api.list_network_instance_ids();
console.debug("loadNetworkInstanceIds", listInstanceIdResponse.value);
}
const loadCurrentNetworkInfo = async () => {
@@ -202,13 +205,12 @@ const loadCurrentNetworkInfo = async () => {
return;
}
let ret = await props.api.get_network_info(instanceId.value);
let network_info = ret[instanceId.value];
let network_info = await props.api.get_network_info(instanceId.value);
curNetworkInfo.value = {
instance_id: instanceId.value,
running: network_info.running,
error_msg: network_info.error_msg,
running: network_info?.running ?? false,
error_msg: network_info?.error_msg ?? '',
detail: network_info,
} as NetworkTypes.NetworkInstance;
}
@@ -220,9 +222,8 @@ const exportConfig = async () => {
}
try {
let networkConfig = await props.api.get_network_config(instanceId.value!);
delete networkConfig.instance_id;
let { toml_config: tomlConfig, error } = await props.api.generate_config(networkConfig);
const { instance_id, ...networkConfig } = await props.api.get_network_config(instanceId.value!);
let { toml_config: tomlConfig, error } = await props.api.generate_config(networkConfig as NetworkTypes.NetworkConfig);
if (error) {
throw { response: { data: error } };
}
@@ -255,9 +256,8 @@ const handleFileUpload = (event: Event) => {
const config = resp.config;
if (!config) return;
config.instance_id = editingNetworkConfig.value?.instance_id ?? config?.instance_id;
Object.assign(editingNetworkConfig.value, resp.config);
config.instance_id = currentNetworkConfig.value?.instance_id ?? config?.instance_id;
currentNetworkConfig.value = 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 });
@@ -288,7 +288,7 @@ const generateConfig = async (config: NetworkTypes.NetworkConfig): Promise<strin
return tomlConfig ?? '';
}
const saveConfig = async (tomlConfig: string): Promise<void> => {
const syncTomlConfig = async (tomlConfig: string): Promise<void> => {
let resp = await props.api.parse_config(tomlConfig);
if (resp.error) {
throw resp.error;
@@ -298,11 +298,7 @@ const saveConfig = async (tomlConfig: string): Promise<void> => {
throw new Error("Parsed config is empty");
}
config.instance_id = currentNetworkConfig.value?.instance_id ?? config?.instance_id;
if (networkIsDisabled.value) {
currentNetworkConfig.value = config;
} else {
editingNetworkConfig.value = config;
}
currentNetworkConfig.value = config;
}
// 响应式屏幕宽度
@@ -313,10 +309,11 @@ const updateScreenWidth = () => {
// 菜单引用和菜单项
const menuRef = ref();
const actionMenu = ref([
const actionMenu: Ref<MenuItem[]> = ref([
{
label: t('web.device_management.edit_network'),
icon: 'pi pi-pencil',
visible: () => !(networkIsDisabled.value ?? true),
command: () => editNetwork()
},
{
@@ -370,7 +367,31 @@ onUnmounted(() => {
<IftaLabel class="w-full">
<Select v-model="selectedInstanceId" :options="instanceIdList" optionLabel="uuid" class="w-full"
inputId="dd-inst-id" :placeholder="t('web.device_management.select_network')"
:pt="{ root: { class: 'network-select-container' } }" />
:pt="{ root: { class: 'network-select-container' } }">
<template #value="slotProps">
<div v-if="slotProps.value" class="flex items-center content-center min-w-0">
<div class="mr-4 flex-col min-w-0 flex-1">
<span class="truncate block"> &nbsp; {{ slotProps.value.uuid }}</span>
</div>
<Tag class="my-auto leading-3 shrink-0"
:severity="isRunning(slotProps.value.uuid) ? 'success' : 'info'"
:value="t(isRunning(slotProps.value.uuid) ? 'network_running' : 'network_stopped')" />
</div>
<span v-else>
{{ slotProps.placeholder }}
</span>
</template>
<template #option="slotProps">
<div class="flex items-center content-center min-w-0">
<div class="mr-4 flex-col min-w-0 flex-1">
<span class="truncate block"> &nbsp; {{ slotProps.option.uuid }}</span>
</div>
<Tag class="my-auto leading-3 shrink-0"
:severity="isRunning(slotProps.option.uuid) ? 'success' : 'info'"
:value="t(isRunning(slotProps.option.uuid) ? 'network_running' : 'network_stopped')" />
</div>
</template>
</Select>
<label class="network-label mr-2 font-medium" for="dd-inst-id">{{
t('web.device_management.network') }}</label>
</IftaLabel>
@@ -379,23 +400,23 @@ onUnmounted(() => {
<!-- 简化的按钮区域 - 无论屏幕大小都显示 -->
<div class="flex gap-2 shrink-0 button-container items-center">
<!-- Create/Cancel button based on state -->
<Button v-if="!isCreatingNetwork" @click="newNetwork" icon="pi pi-plus"
<Button v-if="!isEditingNetwork" @click="newNetwork" icon="pi pi-plus"
:label="screenWidth > 640 ? t('web.device_management.create_new') : undefined"
:class="['create-button', screenWidth <= 640 ? 'p-button-icon-only' : '']"
:style="screenWidth <= 640 ? 'width: 3rem !important; height: 3rem !important; font-size: 1.2rem' : ''"
:tooltip="screenWidth <= 640 ? t('web.device_management.create_network') : undefined"
tooltipOptions="{ position: 'bottom' }" severity="primary" />
<Button v-else @click="cancelNetworkCreation" icon="pi pi-times"
:label="screenWidth > 640 ? t('web.device_management.cancel_creation') : undefined"
<Button v-else @click="cancelEditNetwork" icon="pi pi-times"
:label="screenWidth > 640 ? t('web.device_management.cancel_edit') : undefined"
:class="['cancel-button', screenWidth <= 640 ? 'p-button-icon-only' : '']"
:style="screenWidth <= 640 ? 'width: 3rem !important; height: 3rem !important; font-size: 1.2rem' : ''"
:tooltip="screenWidth <= 640 ? t('web.device_management.cancel_creation') : undefined"
:tooltip="screenWidth <= 640 ? t('web.device_management.cancel_edit') : undefined"
tooltipOptions="{ position: 'bottom' }" severity="secondary" />
<!-- More actions menu -->
<Menu ref="menuRef" :model="actionMenu" :popup="true" />
<Button v-if="!isCreatingNetwork && selectedInstanceId" icon="pi pi-ellipsis-v"
<Button v-if="!isEditingNetwork && selectedInstanceId" icon="pi pi-ellipsis-v"
class="p-button-rounded flex items-center justify-center" severity="help"
style="width: 3rem !important; height: 3rem !important; font-size: 1.2rem"
@click="menuRef.toggle($event)" :aria-label="t('web.device_management.more_actions')"
@@ -407,11 +428,10 @@ onUnmounted(() => {
<!-- Main Content Area -->
<div class="network-content bg-surface-0 p-4 rounded-lg shadow-sm">
<!-- Network Creation Form -->
<div v-if="isCreatingNetwork" class="network-creation-container">
<div v-if="isEditingNetwork || networkIsDisabled" class="network-creation-container">
<div class="network-creation-header flex items-center gap-2 mb-3">
<i class="pi pi-plus-circle text-primary text-xl"></i>
<h2 class="text-xl font-medium">{{ isEditing ? t('web.device_management.edit_network') :
t('web.device_management.create_network') }}</h2>
<h2 class="text-xl font-medium">{{ t('web.device_management.edit_network') }}</h2>
</div>
<div class="w-full flex gap-2 flex-wrap justify-start mb-3">
@@ -419,11 +439,13 @@ onUnmounted(() => {
:label="t('web.device_management.edit_as_file')" iconPos="left" severity="secondary" />
<Button @click="importConfig" icon="pi pi-upload" :label="t('web.device_management.import_config')"
iconPos="left" severity="help" />
<Button v-if="networkIsDisabled" @click="saveNetworkConfig" icon="pi pi-save"
:label="t('web.device_management.save_config')" iconPos="left" severity="success" />
</div>
<Divider />
<Config :cur-network="editingNetworkConfig" @run-network="saveAndRunNewNetwork"></Config>
<Config :cur-network="currentNetworkConfig" @run-network="saveAndRunNewNetwork"></Config>
</div>
<!-- Network Status (for running networks) -->
@@ -433,7 +455,10 @@ onUnmounted(() => {
<h2 class="text-xl font-medium">{{ t('web.device_management.network_status') }}</h2>
</div>
<Status v-bind:cur-network-inst="curNetworkInfo" class="mb-4"></Status>
<Status v-if="(curNetworkInfo?.error_msg ?? '') === ''" v-bind:cur-network-inst="curNetworkInfo"
class="mb-4">
</Status>
<Message v-else severity="error" class="mb-4">{{ curNetworkInfo?.error_msg }}</Message>
<div class="text-center mt-4">
<Button @click="updateNetworkState(true)" :label="t('web.device_management.disable_network')"
@@ -441,23 +466,6 @@ onUnmounted(() => {
</div>
</div>
<!-- Network Configuration (for disabled networks) -->
<div v-else-if="networkIsDisabled" class="network-config-container">
<div class="network-config-header flex items-center gap-2 mb-3">
<i class="pi pi-cog text-secondary text-xl"></i>
<h2 class="text-xl font-medium">{{ t('web.device_management.network_configuration') }}</h2>
</div>
<div v-if="currentNetworkConfig" class="mb-4">
<Config :cur-network="currentNetworkConfig" @run-network="updateNetworkState(false)" />
</div>
<div v-else class="network-loading-placeholder text-center py-8">
<i class="pi pi-spin pi-spinner text-3xl text-primary mb-3"></i>
<div class="text-xl text-secondary">{{ t('web.device_management.loading_network_configuration') }}
</div>
</div>
</div>
<!-- Empty State -->
<div v-else class="empty-state flex flex-col items-center py-12">
<i class="pi pi-sitemap text-5xl text-secondary mb-4 opacity-50"></i>
@@ -475,8 +483,8 @@ onUnmounted(() => {
<!-- <ConfigEditDialog v-if="networkIsDisabled" v-model:visible="showCreateNetworkDialog"
:cur-network="currentNetworkConfig" :generate-config="generateConfig" :save-config="saveConfig" /> -->
<ConfigEditDialog v-model:visible="showConfigEditDialog" :cur-network="editingNetworkConfig"
:generate-config="generateConfig" :save-config="saveConfig" />
<ConfigEditDialog v-model:visible="showConfigEditDialog" :cur-network="currentNetworkConfig"
:generate-config="generateConfig" :save-config="syncTomlConfig" />
</div>
</template>
@@ -592,4 +600,5 @@ onUnmounted(() => {
font-size: 0.9rem;
}
}
</style>
</style>

View File

@@ -289,9 +289,11 @@ web:
network: 网络
select_network: 选择网络
create_network: 创建网络
cancel_creation: 取消创建
cancel_edit: 取消编辑
more_actions: 更多操作
edit_as_file: 编辑为文件
save_config: 保存配置
config_saved: 配置已保存
import_config: 导入配置
create_new: 创建新网络
network_status: 网络状态

View File

@@ -289,9 +289,11 @@ web:
network: Network
select_network: Select Network
create_network: Create Network
cancel_creation: Cancel Creation
cancel_edit: Cancel Edit
more_actions: More Actions
edit_as_file: Edit as File
save_config: Save Config
config_saved: Config Saved
import_config: Import Config
create_new: Create New Network
network_status: Network Status

View File

@@ -1,5 +1,5 @@
import { UUID } from './utils';
import { NetworkConfig } from '../types/network';
import { NetworkConfig, NetworkInstanceRunningInfo } from '../types/network';
export interface ValidateConfigResponse {
toml_config: string;
@@ -20,14 +20,21 @@ export interface ParseConfigResponse {
error?: string;
}
export interface CollectNetworkInfoResponse {
info: {
map: Record<string, NetworkInstanceRunningInfo | undefined>;
}
}
export interface RemoteClient {
validate_config(config: any): Promise<ValidateConfigResponse>;
run_network(config: any): Promise<undefined>;
get_network_info(inst_id: string): Promise<any>;
validate_config(config: NetworkConfig): Promise<ValidateConfigResponse>;
run_network(config: NetworkConfig): Promise<undefined>;
get_network_info(inst_id: string): Promise<NetworkInstanceRunningInfo | undefined>;
list_network_instance_ids(): Promise<ListNetworkInstanceIdResponse>;
delete_network(inst_id: string): Promise<undefined>;
update_network_instance_state(inst_id: string, disabled: boolean): Promise<undefined>;
get_network_config(inst_id: string): Promise<any>;
save_config(config: NetworkConfig): Promise<undefined>;
get_network_config(inst_id: string): Promise<NetworkConfig>;
generate_config(config: NetworkConfig): Promise<GenerateConfigResponse>;
parse_config(toml_config: string): Promise<ParseConfigResponse>;
}

View File

@@ -1,7 +1,6 @@
import axios, { AxiosError, AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
import { Md5 } from 'ts-md5'
import { Api, Utils } from 'easytier-frontend-lib';
import { NetworkTypes } from 'easytier-frontend-lib';
import { type Api, type NetworkTypes, Utils } from 'easytier-frontend-lib';
import { Md5 } from 'ts-md5';
export interface ValidateConfigResponse {
toml_config: string;
@@ -188,20 +187,20 @@ class WebRemoteClient implements Api.RemoteClient {
this.machine_id = machine_id;
this.client = client;
}
async validate_config(config: any): Promise<Api.ValidateConfigResponse> {
const response = await this.client.post<any, ValidateConfigResponse>(`/machines/${this.machine_id}/validate-config`, {
async validate_config(config: NetworkTypes.NetworkConfig): Promise<Api.ValidateConfigResponse> {
const response = await this.client.post<NetworkTypes.NetworkConfig, ValidateConfigResponse>(`/machines/${this.machine_id}/validate-config`, {
config: config,
});
return response;
}
async run_network(config: any): Promise<undefined> {
async run_network(config: NetworkTypes.NetworkConfig): Promise<undefined> {
await this.client.post<string>(`/machines/${this.machine_id}/networks`, {
config: config,
});
}
async get_network_info(inst_id: string): Promise<any> {
const response = await this.client.get<any, Record<string, any>>('/machines/' + this.machine_id + '/networks/info/' + inst_id);
return response.info.map;
async get_network_info(inst_id: string): Promise<NetworkTypes.NetworkInstanceRunningInfo | undefined> {
const response = await this.client.get<any, Api.CollectNetworkInfoResponse>('/machines/' + this.machine_id + '/networks/info/' + inst_id);
return response.info.map[inst_id];
}
async list_network_instance_ids(): Promise<Api.ListNetworkInstanceIdResponse> {
const response = await this.client.get<any, ListNetworkInstanceIdResponse>('/machines/' + this.machine_id + '/networks');
@@ -215,8 +214,11 @@ class WebRemoteClient implements Api.RemoteClient {
disabled: disabled,
});
}
async get_network_config(inst_id: string): Promise<any> {
const response = await this.client.get<any, Record<string, any>>('/machines/' + this.machine_id + '/networks/config/' + inst_id);
async save_config(config: NetworkTypes.NetworkConfig): Promise<undefined> {
await this.client.put(`/machines/${this.machine_id}/networks/config/${config.instance_id}`, { config });
}
async get_network_config(inst_id: string): Promise<NetworkTypes.NetworkConfig> {
const response = await this.client.get<any, NetworkTypes.NetworkConfig>('/machines/' + this.machine_id + '/networks/config/' + inst_id);
return response;
}
async generate_config(config: NetworkTypes.NetworkConfig): Promise<Api.GenerateConfigResponse> {

View File

@@ -1,6 +1,6 @@
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0
use easytier::rpc_service::remote_client::PersistentConfig;
use easytier::{launcher::NetworkConfig, rpc_service::remote_client::PersistentConfig};
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
@@ -41,11 +41,11 @@ impl Related<super::users::Entity> for Entity {
impl ActiveModelBehavior for ActiveModel {}
impl PersistentConfig for Model {
impl PersistentConfig<DbErr> for Model {
fn get_network_inst_id(&self) -> &str {
&self.network_instance_id
}
fn get_network_config(&self) -> &str {
&self.network_config
fn get_network_config(&self) -> Result<NetworkConfig, DbErr> {
serde_json::from_str(&self.network_config).map_err(|e| DbErr::Json(e.to_string()))
}
}

View File

@@ -2,7 +2,10 @@
#[allow(unused_imports)]
pub mod entity;
use easytier::rpc_service::remote_client::{ListNetworkProps, Storage};
use easytier::{
launcher::NetworkConfig,
rpc_service::remote_client::{ListNetworkProps, Storage},
};
use entity::user_running_network_configs;
use sea_orm::{
prelude::Expr, sea_query::OnConflict, ColumnTrait as _, DatabaseConnection, DbErr, EntityTrait,
@@ -94,7 +97,7 @@ impl Storage<(UserIdInDb, Uuid), user_running_network_configs::Model, DbErr> for
&self,
(user_id, device_id): (UserIdInDb, Uuid),
network_inst_id: Uuid,
network_config: impl ToString + Send,
network_config: NetworkConfig,
) -> Result<(), DbErr> {
let txn = self.orm_db().begin().await?;
@@ -111,7 +114,9 @@ impl Storage<(UserIdInDb, Uuid), user_running_network_configs::Model, DbErr> for
user_id: sea_orm::Set(user_id),
device_id: sea_orm::Set(device_id.to_string()),
network_instance_id: sea_orm::Set(network_inst_id.to_string()),
network_config: sea_orm::Set(network_config.to_string()),
network_config: sea_orm::Set(
serde_json::to_string(&network_config).map_err(|e| DbErr::Json(e.to_string()))?,
),
disabled: sea_orm::Set(false),
create_time: sea_orm::Set(chrono::Local::now().fixed_offset()),
update_time: sea_orm::Set(chrono::Local::now().fixed_offset()),
@@ -126,16 +131,19 @@ impl Storage<(UserIdInDb, Uuid), user_running_network_configs::Model, DbErr> for
txn.commit().await
}
async fn delete_network_config(
async fn delete_network_configs(
&self,
(user_id, _): (UserIdInDb, Uuid),
network_inst_id: Uuid,
network_inst_ids: &[Uuid],
) -> Result<(), DbErr> {
use entity::user_running_network_configs as urnc;
urnc::Entity::delete_many()
.filter(urnc::Column::UserId.eq(user_id))
.filter(urnc::Column::NetworkInstanceId.eq(network_inst_id.to_string()))
.filter(
urnc::Column::NetworkInstanceId
.is_in(network_inst_ids.iter().map(|id| id.to_string())),
)
.exec(self.orm_db())
.await?;
@@ -220,7 +228,7 @@ impl Storage<(UserIdInDb, Uuid), user_running_network_configs::Model, DbErr> for
#[cfg(test)]
mod tests {
use easytier::rpc_service::remote_client::Storage;
use easytier::{proto::api::manage::NetworkConfig, rpc_service::remote_client::Storage};
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter as _};
use crate::db::{entity::user_running_network_configs, Db, ListNetworkProps};
@@ -229,7 +237,11 @@ mod tests {
async fn test_user_network_config_management() {
let db = Db::memory_db().await;
let user_id = 1;
let network_config = "test_config";
let network_config = NetworkConfig {
network_name: Some("test_config".to_string()),
..Default::default()
};
let network_config_json = serde_json::to_string(&network_config).unwrap();
let inst_id = uuid::Uuid::new_v4();
let device_id = uuid::Uuid::new_v4();
@@ -244,10 +256,14 @@ mod tests {
.unwrap()
.unwrap();
println!("{:?}", result);
assert_eq!(result.network_config, network_config);
assert_eq!(result.network_config, network_config_json);
// overwrite the config
let network_config = "test_config2";
let network_config = NetworkConfig {
network_name: Some("test_config2".to_string()),
..Default::default()
};
let network_config_json = serde_json::to_string(&network_config).unwrap();
db.insert_or_update_user_network_config((user_id, device_id), inst_id, network_config)
.await
.unwrap();
@@ -259,7 +275,7 @@ mod tests {
.unwrap()
.unwrap();
println!("device: {}, {:?}", device_id, result2);
assert_eq!(result2.network_config, network_config);
assert_eq!(result2.network_config, network_config_json);
assert_eq!(result.create_time, result2.create_time);
assert_ne!(result.update_time, result2.update_time);
@@ -272,7 +288,7 @@ mod tests {
1
);
db.delete_network_config((user_id, device_id), inst_id)
db.delete_network_configs((user_id, device_id), &[inst_id])
.await
.unwrap();
let result3 = user_running_network_configs::Entity::find()

View File

@@ -51,6 +51,11 @@ struct ValidateConfigJsonReq {
config: NetworkConfig,
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
struct SaveNetworkJsonReq {
config: NetworkConfig,
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
struct RunNetworkJsonReq {
config: NetworkConfig,
@@ -177,9 +182,9 @@ impl NetworkApi {
Path((machine_id, inst_id)): Path<(uuid::Uuid, uuid::Uuid)>,
) -> Result<(), HttpHandleError> {
client_mgr
.handle_remove_network_instance(
.handle_remove_network_instances(
(Self::get_user_id(&auth_session)?, machine_id),
inst_id,
vec![inst_id],
)
.await
.map_err(convert_error)
@@ -232,6 +237,28 @@ impl NetworkApi {
.map_err(convert_error)
}
async fn handle_save_network_config(
auth_session: AuthSession,
State(client_mgr): AppState,
Path((machine_id, inst_id)): Path<(uuid::Uuid, uuid::Uuid)>,
Json(payload): Json<SaveNetworkJsonReq>,
) -> Result<(), HttpHandleError> {
if payload.config.instance_id() != inst_id.to_string() {
return Err((
StatusCode::BAD_REQUEST,
other_error("Instance ID mismatch".to_string()).into(),
));
}
client_mgr
.handle_save_network_config(
(Self::get_user_id(&auth_session)?, machine_id),
inst_id,
payload.config,
)
.await
.map_err(convert_error)
}
async fn handle_get_network_config(
auth_session: AuthSession,
State(client_mgr): AppState,
@@ -269,7 +296,7 @@ impl NetworkApi {
)
.route(
"/api/v1/machines/:machine-id/networks/config/:inst-id",
get(Self::handle_get_network_config),
get(Self::handle_get_network_config).put(Self::handle_save_network_config),
)
}
}