🎨基本功能完善
This commit is contained in:
parent
6a37c8d902
commit
66982f3a7b
@ -47,6 +47,7 @@
|
|||||||
"url": "https://github.com/xiaoxian521"
|
"url": "https://github.com/xiaoxian521"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@element-plus/icons-vue": "^2.3.1",
|
||||||
"@pureadmin/descriptions": "^1.2.1",
|
"@pureadmin/descriptions": "^1.2.1",
|
||||||
"@pureadmin/table": "^3.1.2",
|
"@pureadmin/table": "^3.1.2",
|
||||||
"@pureadmin/utils": "^2.4.7",
|
"@pureadmin/utils": "^2.4.7",
|
||||||
@ -64,6 +65,7 @@
|
|||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"pinyin-pro": "^3.20.4",
|
"pinyin-pro": "^3.20.4",
|
||||||
|
"plus-pro-components": "^0.1.4",
|
||||||
"qs": "^6.12.1",
|
"qs": "^6.12.1",
|
||||||
"responsive-storage": "^2.2.0",
|
"responsive-storage": "^2.2.0",
|
||||||
"sortablejs": "^1.15.2",
|
"sortablejs": "^1.15.2",
|
||||||
|
19
pnpm-lock.yaml
generated
19
pnpm-lock.yaml
generated
@ -5,6 +5,9 @@ settings:
|
|||||||
excludeLinksFromLockfile: false
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@element-plus/icons-vue':
|
||||||
|
specifier: ^2.3.1
|
||||||
|
version: 2.3.1(vue@3.4.27)
|
||||||
'@pureadmin/descriptions':
|
'@pureadmin/descriptions':
|
||||||
specifier: ^1.2.1
|
specifier: ^1.2.1
|
||||||
version: 1.2.1(echarts@5.5.0)(element-plus@2.7.1)(typescript@5.4.5)
|
version: 1.2.1(echarts@5.5.0)(element-plus@2.7.1)(typescript@5.4.5)
|
||||||
@ -56,6 +59,9 @@ dependencies:
|
|||||||
pinyin-pro:
|
pinyin-pro:
|
||||||
specifier: ^3.20.4
|
specifier: ^3.20.4
|
||||||
version: 3.20.4
|
version: 3.20.4
|
||||||
|
plus-pro-components:
|
||||||
|
specifier: ^0.1.4
|
||||||
|
version: 0.1.4(element-plus@2.7.1)(vue@3.4.27)
|
||||||
qs:
|
qs:
|
||||||
specifier: ^6.12.1
|
specifier: ^6.12.1
|
||||||
version: 6.12.1
|
version: 6.12.1
|
||||||
@ -4623,6 +4629,19 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
/plus-pro-components@0.1.4(element-plus@2.7.1)(vue@3.4.27):
|
||||||
|
resolution: {integrity: sha512-lglcrqYQqYM20GgxN+Hg9te/DfNHeERTBSP3EKzWAw6mSl3TU2LYVJIvPgzxAFtoRcg8f9Ybv9vVgtFHSH3JVg==}
|
||||||
|
peerDependencies:
|
||||||
|
element-plus: ^2.3.4
|
||||||
|
vue: ^3.2.0
|
||||||
|
dependencies:
|
||||||
|
'@element-plus/icons-vue': 2.3.1(vue@3.4.27)
|
||||||
|
element-plus: 2.7.1(vue@3.4.27)
|
||||||
|
lodash-es: 4.17.21
|
||||||
|
sortablejs: 1.15.2
|
||||||
|
vue: 3.4.27(typescript@5.4.5)
|
||||||
|
dev: false
|
||||||
|
|
||||||
/popmotion@11.0.5:
|
/popmotion@11.0.5:
|
||||||
resolution: {integrity: sha512-la8gPM1WYeFznb/JqF4GiTkRRPZsfaj2+kCxqQgr2MJylMmIKUwBfWW8Wa5fml/8gmtlD5yI01MP1QCZPWmppA==}
|
resolution: {integrity: sha512-la8gPM1WYeFznb/JqF4GiTkRRPZsfaj2+kCxqQgr2MJylMmIKUwBfWW8Wa5fml/8gmtlD5yI01MP1QCZPWmppA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
29
src/api/clients.ts
Normal file
29
src/api/clients.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { http } from "@/utils/http";
|
||||||
|
import { baseUri } from "@/api/utils";
|
||||||
|
|
||||||
|
// 获取服务端信息
|
||||||
|
export const getClients = (params?: object) => {
|
||||||
|
return http.request<any>("get", baseUri("/client/list"), { params });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 下载客户端配置文件
|
||||||
|
export const downloadClient = (id: string) => {
|
||||||
|
return http.request<any>("post", baseUri("/client/download/" + id), null, {
|
||||||
|
responseType: "arraybuffer"
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取客户端配置二维码
|
||||||
|
export const clientQrCode = (id: string) => {
|
||||||
|
return http.request<any>("post", baseUri("/client/generate-qrcode/" + id));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 新增/更新客户端信息
|
||||||
|
export const saveClient = (data?: object) => {
|
||||||
|
return http.request<any>("post", baseUri("/client/save"), { data });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除客户端
|
||||||
|
export const deleteClient = (id: string) => {
|
||||||
|
return http.request<any>("delete", baseUri("/client/" + id));
|
||||||
|
};
|
27
src/api/server.ts
Normal file
27
src/api/server.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { http } from "@/utils/http";
|
||||||
|
import { baseUri } from "@/api/utils";
|
||||||
|
|
||||||
|
// 获取服务端信息
|
||||||
|
export const getServer = () => {
|
||||||
|
return http.request<any>("get", baseUri("/server"));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 更新服务端信息
|
||||||
|
export const updateServer = (data?: object) => {
|
||||||
|
return http.request<any>("post", baseUri("/server"), { data });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取全局配置
|
||||||
|
export const getGlobalConfig = () => {
|
||||||
|
return http.request<any>("get", baseUri("/setting/server"));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 更新全局配置
|
||||||
|
export const updateGlobalSetting = (data?: object) => {
|
||||||
|
return http.request<any>("post", baseUri("/setting/server-global"), { data });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取当前主机的公网IP
|
||||||
|
export const getPublicIP = () => {
|
||||||
|
return http.request<any>("get", baseUri("/setting/public-ip"));
|
||||||
|
};
|
@ -1,4 +1,4 @@
|
|||||||
// 这里存放本地图标,在 src/layout/list.vue 文件中加载,避免在首启动加载
|
// 这里存放本地图标,在 src/layout/server.vue 文件中加载,避免在首启动加载
|
||||||
import { addIcon } from "@iconify/vue/dist/offline";
|
import { addIcon } from "@iconify/vue/dist/offline";
|
||||||
|
|
||||||
// 本地菜单图标,后端在路由的 icon 中返回对应的图标字符串并且前端在此处使用 addIcon 添加即可渲染菜单图标
|
// 本地菜单图标,后端在路由的 icon 中返回对应的图标字符串并且前端在此处使用 addIcon 添加即可渲染菜单图标
|
||||||
|
@ -5,20 +5,20 @@ export default {
|
|||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "/server/index",
|
path: "/server/config",
|
||||||
name: "Server",
|
name: "Clients",
|
||||||
component: () => import("@/views/server/list.vue"),
|
component: () => import("@/views/server/clients.vue"),
|
||||||
meta: {
|
meta: {
|
||||||
title: "服务端列表",
|
title: "客户端",
|
||||||
showParent: true
|
showParent: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/server/config",
|
path: "/server/index",
|
||||||
name: "ServerConfig",
|
name: "Server",
|
||||||
component: () => import("@/views/server/config.vue"),
|
component: () => import("@/views/server/server.vue"),
|
||||||
meta: {
|
meta: {
|
||||||
title: "服务端配置",
|
title: "服务端",
|
||||||
showParent: true
|
showParent: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
83
src/store/modules/globalSetting.ts
Normal file
83
src/store/modules/globalSetting.ts
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import { defineStore } from "pinia";
|
||||||
|
import type { globalSettingType } from "@/store/types";
|
||||||
|
import { store } from "@/store";
|
||||||
|
import {getGlobalConfig, updateGlobalSetting, updateServer} from "@/api/server";
|
||||||
|
import {state} from "vue-tsc/out/shared";
|
||||||
|
|
||||||
|
export const useGlobalSettingStore = defineStore({
|
||||||
|
id: "pure-globalSetting",
|
||||||
|
state: (): globalSettingType => ({
|
||||||
|
endpointAddress: "",
|
||||||
|
dnsServer: [],
|
||||||
|
MTU: 0,
|
||||||
|
persistentKeepalive: 0,
|
||||||
|
firewallMark: "",
|
||||||
|
table: "",
|
||||||
|
configFilePath: ""
|
||||||
|
}),
|
||||||
|
getters: {
|
||||||
|
getGlobalSetting(state) {
|
||||||
|
return state;
|
||||||
|
},
|
||||||
|
getEndpointAddress(state) {
|
||||||
|
return state.endpointAddress;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
SET_ENDPOINT_ADDRESS(endpointAddress: string) {
|
||||||
|
this.endpointAddress = endpointAddress;
|
||||||
|
},
|
||||||
|
SET_DNS_SERVER(dnsServer: []) {
|
||||||
|
this.dnsServer = dnsServer;
|
||||||
|
},
|
||||||
|
SET_MTU(MTU: number) {
|
||||||
|
this.MTU = MTU;
|
||||||
|
},
|
||||||
|
SET_PERSISTENT_KEEPALIVE(persistentKeepalive: string) {
|
||||||
|
this.persistentKeepalive = persistentKeepalive;
|
||||||
|
},
|
||||||
|
SET_FIREWALL_MARK(firewallMark: string) {
|
||||||
|
this.firewallMark = firewallMark;
|
||||||
|
},
|
||||||
|
SET_TABLE(table: number) {
|
||||||
|
this.table = table;
|
||||||
|
},
|
||||||
|
SET_CONFIG_FILE_PATH(configFilePath: number) {
|
||||||
|
this.configFilePath = configFilePath;
|
||||||
|
},
|
||||||
|
async getGlobalSettingApi() {
|
||||||
|
return new Promise<any>((resolve, reject) => {
|
||||||
|
getGlobalConfig()
|
||||||
|
.then(data => {
|
||||||
|
if (data.code === 200) {
|
||||||
|
this.SET_ENDPOINT_ADDRESS(data.data.endpointAddress);
|
||||||
|
this.SET_DNS_SERVER(data.data.dnsServer);
|
||||||
|
this.SET_MTU(data.data.MTU);
|
||||||
|
this.SET_PERSISTENT_KEEPALIVE(data.data.persistentKeepalive);
|
||||||
|
this.SET_FIREWALL_MARK(data.data.firewallMark);
|
||||||
|
this.SET_TABLE(data.data.table);
|
||||||
|
this.SET_CONFIG_FILE_PATH(data.data.configFilePath);
|
||||||
|
}
|
||||||
|
resolve(data);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async updateGlobalSettingApi(data?: object) {
|
||||||
|
return new Promise<any>((resolve, reject) => {
|
||||||
|
updateGlobalSetting(data)
|
||||||
|
.then(res => {
|
||||||
|
resolve(res);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
export function useGlobalSettingStoreHook() {
|
||||||
|
return useGlobalSettingStore(store);
|
||||||
|
}
|
86
src/store/modules/server.ts
Normal file
86
src/store/modules/server.ts
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import { defineStore } from "pinia";
|
||||||
|
import type { serverType } from "@/store/types";
|
||||||
|
import { store } from "@/store";
|
||||||
|
import {getServer, updateServer} from "@/api/server";
|
||||||
|
import {storageLocal} from "@pureadmin/utils";
|
||||||
|
|
||||||
|
export const useServerStore = defineStore({
|
||||||
|
id: "pure-server",
|
||||||
|
state: (): serverType => ({
|
||||||
|
id: "",
|
||||||
|
ipScope: "",
|
||||||
|
listenPort: 0,
|
||||||
|
privateKey: "",
|
||||||
|
publicKey: "",
|
||||||
|
postUpScript: "",
|
||||||
|
preDownScript: "",
|
||||||
|
postDownScript: ""
|
||||||
|
}),
|
||||||
|
getters: {
|
||||||
|
getServerInfo(state) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
SET_ID(id: string) {
|
||||||
|
this.id = id;
|
||||||
|
},
|
||||||
|
SET_IP_SCOPE(ipScope: string) {
|
||||||
|
this.ipScope = ipScope;
|
||||||
|
},
|
||||||
|
SET_LISTEN_PORT(listenPort: number) {
|
||||||
|
this.listenPort = listenPort;
|
||||||
|
},
|
||||||
|
SET_PRIVATE_KEY(privateKey: string) {
|
||||||
|
this.privateKey = privateKey;
|
||||||
|
},
|
||||||
|
SET_PUBLIC_KEY(publicKey: string) {
|
||||||
|
this.publicKey = publicKey;
|
||||||
|
},
|
||||||
|
SET_POST_UP_SCRIPT(postUpScript: number) {
|
||||||
|
this.postUpScript = postUpScript;
|
||||||
|
},
|
||||||
|
SET_PRE_UP_SCRIPT(preDownScript: number) {
|
||||||
|
this.preDownScript = preDownScript;
|
||||||
|
},
|
||||||
|
SET_POST_DOWN_SCRIPT(postDownScript: number) {
|
||||||
|
this.postDownScript = postDownScript;
|
||||||
|
},
|
||||||
|
async getServerApi() {
|
||||||
|
return new Promise<any>((resolve, reject) => {
|
||||||
|
getServer()
|
||||||
|
.then(data => {
|
||||||
|
if (data.code === 200) {
|
||||||
|
this.SET_ID(data.data.id);
|
||||||
|
this.SET_IP_SCOPE(data.data.ipScope);
|
||||||
|
this.SET_LISTEN_PORT(data.data.listenPort);
|
||||||
|
this.SET_PRIVATE_KEY(data.data.privateKey);
|
||||||
|
this.SET_PUBLIC_KEY(data.data.publicKey);
|
||||||
|
this.SET_POST_UP_SCRIPT(data.data.postUpScript);
|
||||||
|
this.SET_PRE_UP_SCRIPT(data.data.preDownScript);
|
||||||
|
this.SET_POST_DOWN_SCRIPT(data.data.postDownScript);
|
||||||
|
}
|
||||||
|
storageLocal().setItem("server-info",data.data);
|
||||||
|
resolve(data);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async updateServerApi(data?: object) {
|
||||||
|
return new Promise<any>((resolve, reject) => {
|
||||||
|
updateServer(data)
|
||||||
|
.then(res => {
|
||||||
|
resolve(res);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
export function useServerStoreHook() {
|
||||||
|
return useServerStore(store);
|
||||||
|
}
|
@ -39,7 +39,7 @@ export const useUserStore = defineStore({
|
|||||||
this.status = status;
|
this.status = status;
|
||||||
},
|
},
|
||||||
/** 登入 */
|
/** 登入 */
|
||||||
async loginByUsername(data: any) {
|
async loginByUsername(data?: any) {
|
||||||
return new Promise<any>((resolve, reject) => {
|
return new Promise<any>((resolve, reject) => {
|
||||||
login(data)
|
login(data)
|
||||||
.then(data => {
|
.then(data => {
|
||||||
|
@ -45,3 +45,24 @@ export type userType = {
|
|||||||
isAdmin?: number;
|
isAdmin?: number;
|
||||||
status?: number;
|
status?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type serverType = {
|
||||||
|
id?: string;
|
||||||
|
ipScope?: string;
|
||||||
|
listenPort?: number;
|
||||||
|
privateKey?: string;
|
||||||
|
publicKey?: string;
|
||||||
|
postUpScript?: string;
|
||||||
|
preDownScript?: string;
|
||||||
|
postDownScript?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type globalSettingType = {
|
||||||
|
endpointAddress?: string;
|
||||||
|
dnsServer?: [];
|
||||||
|
MTU?: number;
|
||||||
|
persistentKeepalive?: number;
|
||||||
|
firewallMark?: string;
|
||||||
|
table?: string;
|
||||||
|
configFilePath?: string;
|
||||||
|
};
|
||||||
|
@ -11,8 +11,11 @@ import type {
|
|||||||
} from "./types.d";
|
} from "./types.d";
|
||||||
import { stringify } from "qs";
|
import { stringify } from "qs";
|
||||||
import NProgress from "../progress";
|
import NProgress from "../progress";
|
||||||
import { getToken, formatToken } from "@/utils/auth";
|
import { getToken, formatToken, TokenKey, multipleTabsKey } from "@/utils/auth";
|
||||||
import { message } from "@/utils/message";
|
import { message } from "@/utils/message";
|
||||||
|
import { storageLocal } from "@pureadmin/utils";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
import router from "@/router";
|
||||||
|
|
||||||
// 相关配置请参考:www.axios-js.com/zh-cn/docs/#axios-request-config-1
|
// 相关配置请参考:www.axios-js.com/zh-cn/docs/#axios-request-config-1
|
||||||
const defaultConfig: AxiosRequestConfig = {
|
const defaultConfig: AxiosRequestConfig = {
|
||||||
@ -82,6 +85,16 @@ class PureHttp {
|
|||||||
const now = new Date().getTime() / 1000;
|
const now = new Date().getTime() / 1000;
|
||||||
const expired = parseInt(data.expireAt) - now <= 0;
|
const expired = parseInt(data.expireAt) - now <= 0;
|
||||||
if (expired) {
|
if (expired) {
|
||||||
|
message("登陆已过期", { type: "error" });
|
||||||
|
router.replace({
|
||||||
|
path: "/login",
|
||||||
|
query: {
|
||||||
|
redirect: router.currentRoute.value.fullPath
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Cookies.remove(TokenKey);
|
||||||
|
Cookies.remove(multipleTabsKey);
|
||||||
|
storageLocal().clear();
|
||||||
resolve(PureHttp.retryOriginalRequest(config));
|
resolve(PureHttp.retryOriginalRequest(config));
|
||||||
} else {
|
} else {
|
||||||
config.headers["Authorization"] = data.token;
|
config.headers["Authorization"] = data.token;
|
||||||
|
@ -30,7 +30,7 @@ interface MessageParams {
|
|||||||
onClose?: Function | null;
|
onClose?: Function | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 用法非常简单,参考 src/views/components/message/list.vue 文件 */
|
/** 用法非常简单,参考 src/views/components/message/server.vue 文件 */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `Message` 消息提示函数
|
* `Message` 消息提示函数
|
||||||
|
442
src/views/server/clients.vue
Normal file
442
src/views/server/clients.vue
Normal file
@ -0,0 +1,442 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
deleteClient,
|
||||||
|
downloadClient,
|
||||||
|
getClients,
|
||||||
|
saveClient
|
||||||
|
} from "@/api/clients";
|
||||||
|
import { h, reactive, ref } from "vue";
|
||||||
|
import { addDialog } from "@/components/ReDialog/index";
|
||||||
|
import qrCodeForms from "./component/qrCode.vue";
|
||||||
|
import editClientForms from "./component/detail.vue";
|
||||||
|
import { type PlusColumn, PlusSearch } from "plus-pro-components";
|
||||||
|
import "plus-pro-components/es/components/search/style/css";
|
||||||
|
import { storageLocal } from "@pureadmin/utils";
|
||||||
|
import { ArrowDown } from "@element-plus/icons-vue";
|
||||||
|
import { ElMessageBox } from "element-plus";
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
// name 作为一种规范最好必须写上并且和路由的name保持一致
|
||||||
|
name: "Clients"
|
||||||
|
});
|
||||||
|
|
||||||
|
const editClientFormRef = ref();
|
||||||
|
|
||||||
|
const clientSearchForm = ref({
|
||||||
|
name: "",
|
||||||
|
email: "",
|
||||||
|
ip: "",
|
||||||
|
createUser: "",
|
||||||
|
enabled: undefined,
|
||||||
|
current: 1,
|
||||||
|
size: 9
|
||||||
|
});
|
||||||
|
|
||||||
|
const clientSearchProps = {
|
||||||
|
gutter: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
const clientSearchFormColumns: PlusColumn[] = [
|
||||||
|
{
|
||||||
|
label: "名称",
|
||||||
|
prop: "name",
|
||||||
|
valueType: "copy",
|
||||||
|
fieldProps: {
|
||||||
|
placeholder: "请输入"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "邮箱",
|
||||||
|
prop: "email",
|
||||||
|
valueType: "copy",
|
||||||
|
fieldProps: {
|
||||||
|
placeholder: "请输入"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "IP",
|
||||||
|
prop: "ip",
|
||||||
|
valueType: "copy",
|
||||||
|
fieldProps: {
|
||||||
|
placeholder: "请输入"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "创建人",
|
||||||
|
prop: "createUser",
|
||||||
|
valueType: "copy",
|
||||||
|
fieldProps: {
|
||||||
|
placeholder: "请输入"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "状态",
|
||||||
|
prop: "enabled",
|
||||||
|
valueType: "select",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: "启用",
|
||||||
|
value: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "禁用",
|
||||||
|
value: 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
fieldProps: {
|
||||||
|
placeholder: "请选择"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const clientsList = reactive({
|
||||||
|
data: [],
|
||||||
|
total: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
const getClientsApi = (data?: object) => {
|
||||||
|
getClients(data).then(clients => {
|
||||||
|
if (clients.code === 200) {
|
||||||
|
clientsList.data = clients.data.records;
|
||||||
|
clientsList.total = clients.data.total;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 下载客户端配置文件
|
||||||
|
const downloadClientConfig = (id: string, clientName: string) => {
|
||||||
|
downloadClient(id).then(response => {
|
||||||
|
if (response) {
|
||||||
|
const blob = new Blob([response], {
|
||||||
|
type: "text/plain"
|
||||||
|
});
|
||||||
|
const link = document.createElement("a"); // 创建a标签
|
||||||
|
link.download = clientName + ".conf"; // a标签添加属性
|
||||||
|
link.style.display = "none";
|
||||||
|
link.href = URL.createObjectURL(blob);
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click(); // 执行下载
|
||||||
|
URL.revokeObjectURL(link.href); // 释放url
|
||||||
|
document.body.removeChild(link); // 释放标签
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 打开二维码模态框
|
||||||
|
const openQrCodeDialog = (clientName: string, id: string) => {
|
||||||
|
addDialog({
|
||||||
|
width: "20%",
|
||||||
|
title: clientName,
|
||||||
|
contentRenderer: () => h(qrCodeForms),
|
||||||
|
props: {
|
||||||
|
formInline: {
|
||||||
|
id: id
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hideFooter: true
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 打开新增客户端弹窗
|
||||||
|
const openAddClientDialog = () => {
|
||||||
|
const serverInfo = storageLocal().getItem("server-info");
|
||||||
|
addDialog({
|
||||||
|
width: "40%",
|
||||||
|
title: "新增",
|
||||||
|
contentRenderer: () => h(editClientForms, { ref: editClientFormRef }),
|
||||||
|
props: {
|
||||||
|
formInline: {
|
||||||
|
id: "",
|
||||||
|
serverId: serverInfo.id,
|
||||||
|
name: "",
|
||||||
|
email: "",
|
||||||
|
subnetRange: "",
|
||||||
|
ipAllocation: "",
|
||||||
|
allowedIPS: "",
|
||||||
|
extraAllowedIPS: "",
|
||||||
|
endpoint: "",
|
||||||
|
useServerDNS: 0,
|
||||||
|
enableAfterCreation: 0,
|
||||||
|
keys: null,
|
||||||
|
enabled: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeSure: (done, { options }) => {
|
||||||
|
const FormRef = editClientFormRef.value.getDetailFormRef();
|
||||||
|
FormRef.validate(valid => {
|
||||||
|
if (!valid) return;
|
||||||
|
saveClient(options.props.formInline).then(res => {
|
||||||
|
if (res.code === 200) {
|
||||||
|
done();
|
||||||
|
getClientsApi(clientSearchForm.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 打开编辑客户端信息模态框
|
||||||
|
const openEditClientDialog = (client?: any) => {
|
||||||
|
const serverInfo = storageLocal().getItem("server-info");
|
||||||
|
addDialog({
|
||||||
|
width: "40%",
|
||||||
|
title: client.name,
|
||||||
|
contentRenderer: () => h(editClientForms, { ref: editClientFormRef }),
|
||||||
|
props: {
|
||||||
|
formInline: {
|
||||||
|
id: client.id,
|
||||||
|
serverId: serverInfo.id,
|
||||||
|
name: client.name,
|
||||||
|
email: client.email,
|
||||||
|
subnetRange: client.subnetRange,
|
||||||
|
ipAllocation: client.ipAllocation,
|
||||||
|
allowedIPS: client.allowedIPS,
|
||||||
|
extraAllowedIPS: client.extraAllowedIPS,
|
||||||
|
endpoint: client.endpoint,
|
||||||
|
useServerDNS: client.useServerDNS,
|
||||||
|
enableAfterCreation: client.enableAfterCreation,
|
||||||
|
keys: client.keys,
|
||||||
|
enabled: Number(client.enabled)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeSure: (done, { options }) => {
|
||||||
|
const FormRef = editClientFormRef.value.getDetailFormRef();
|
||||||
|
FormRef.validate(valid => {
|
||||||
|
if (!valid) return;
|
||||||
|
saveClient(options.props.formInline).then(res => {
|
||||||
|
if (res.code === 200) {
|
||||||
|
done();
|
||||||
|
getClientsApi(clientSearchForm.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 打开删除弹窗
|
||||||
|
const openDeleteMessageBox = (clientName: string, clientId: string) => {
|
||||||
|
ElMessageBox.confirm("是否删除:" + clientName, "删除", {
|
||||||
|
distinguishCancelAndClose: true,
|
||||||
|
confirmButtonText: "确认",
|
||||||
|
cancelButtonText: "取消",
|
||||||
|
type: "warning",
|
||||||
|
center: true
|
||||||
|
}).then(() => {
|
||||||
|
deleteClient(clientId).then(res => {
|
||||||
|
if (res.code === 200) {
|
||||||
|
getClientsApi(clientSearchForm.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 搜索
|
||||||
|
const searchHandler = (value: object) => {
|
||||||
|
getClientsApi(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 重置搜索表单
|
||||||
|
const resetSearchHandler = (value: object) => {
|
||||||
|
getClientsApi({
|
||||||
|
current: 1,
|
||||||
|
size: 9
|
||||||
|
});
|
||||||
|
clientSearchForm.value.current = 1;
|
||||||
|
clientSearchForm.value.size = 9;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 定义分页相关事件
|
||||||
|
const pageChange = (page: number, size: number) => {
|
||||||
|
clientSearchForm.value.size = size;
|
||||||
|
clientSearchForm.value.current = page;
|
||||||
|
getClientsApi(clientSearchForm.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 超长省略号显示
|
||||||
|
const ellipsis = (str: string) => {
|
||||||
|
if (!str) return "";
|
||||||
|
if (str.length >= 10) {
|
||||||
|
return str.slice(0, 10) + "...";
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
};
|
||||||
|
|
||||||
|
getClientsApi(clientSearchForm.value);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="search-header" style="padding-bottom: 5px">
|
||||||
|
<el-card>
|
||||||
|
<PlusSearch
|
||||||
|
v-model="clientSearchForm"
|
||||||
|
resetText="重置"
|
||||||
|
searchText="搜索"
|
||||||
|
:hasUnfold="false"
|
||||||
|
:columns="clientSearchFormColumns"
|
||||||
|
:rowProps="clientSearchProps"
|
||||||
|
label-width="80"
|
||||||
|
label-position="right"
|
||||||
|
@search="searchHandler"
|
||||||
|
@reset="resetSearchHandler"
|
||||||
|
/>
|
||||||
|
</el-card>
|
||||||
|
<div style="margin-top: 5px">
|
||||||
|
<el-card>
|
||||||
|
<el-button type="primary" @click="openAddClientDialog"
|
||||||
|
>新增客户端</el-button
|
||||||
|
>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<el-card body-style="padding: inherit" shadow="hover">
|
||||||
|
<div class="flex flex-wrap gap-4">
|
||||||
|
<el-card
|
||||||
|
v-for="val in clientsList.data"
|
||||||
|
style="width: 540px"
|
||||||
|
shadow="hover"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<el-tooltip :content="val.name" placement="top">
|
||||||
|
<el-tag size="large">
|
||||||
|
{{ ellipsis(val.name) }}
|
||||||
|
</el-tag>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-dropdown
|
||||||
|
trigger="click"
|
||||||
|
type="primary"
|
||||||
|
split-button
|
||||||
|
style="float: right"
|
||||||
|
>
|
||||||
|
更多
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item>
|
||||||
|
<el-button
|
||||||
|
type="warning"
|
||||||
|
@click="downloadClientConfig(val.id, val.name)"
|
||||||
|
>下载</el-button
|
||||||
|
>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item>
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
@click="openDeleteMessageBox(val.name, val.id)"
|
||||||
|
>删除</el-button
|
||||||
|
>
|
||||||
|
</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
<el-button
|
||||||
|
type="success"
|
||||||
|
style="float: right; margin-right: 5px"
|
||||||
|
@click="openQrCodeDialog(val.name, val.id)"
|
||||||
|
>二维码</el-button
|
||||||
|
>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
style="float: right; margin-right: 5px"
|
||||||
|
@click="openEditClientDialog(val)"
|
||||||
|
>
|
||||||
|
详情
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-form label-position="top">
|
||||||
|
<el-form-item prop="name" label="名称">
|
||||||
|
<el-input v-model="val.name" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="email" label="邮箱">
|
||||||
|
<el-input v-model="val.email" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="ipAllocation" label="客户端IP">
|
||||||
|
<el-select
|
||||||
|
v-model="val.ipAllocation"
|
||||||
|
:clearable="true"
|
||||||
|
:reserve-keyword="false"
|
||||||
|
suffix-icon=""
|
||||||
|
tag-type="primary"
|
||||||
|
popper-class="options-class"
|
||||||
|
placeholder=""
|
||||||
|
multiple
|
||||||
|
filterable
|
||||||
|
allow-create
|
||||||
|
default-first-option
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="allowedIPS" label="允许的IP范围">
|
||||||
|
<el-select
|
||||||
|
v-model="val.allowedIPS"
|
||||||
|
:clearable="true"
|
||||||
|
:reserve-keyword="false"
|
||||||
|
suffix-icon=""
|
||||||
|
tag-type="danger"
|
||||||
|
popper-class="options-class"
|
||||||
|
placeholder=""
|
||||||
|
multiple
|
||||||
|
filterable
|
||||||
|
allow-create
|
||||||
|
default-first-option
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="创建人">
|
||||||
|
<el-tag effect="dark" type="primary">{{
|
||||||
|
val.createUser
|
||||||
|
}}</el-tag>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="客户端状态">
|
||||||
|
<el-tag v-if="val.enabled" effect="dark" type="success"
|
||||||
|
>启用</el-tag
|
||||||
|
>
|
||||||
|
<el-tag v-else effect="dark" type="warning">禁用</el-tag>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item class="timeItem">
|
||||||
|
<p>创建时间: {{ val.createdAt }}</p>
|
||||||
|
<p>更新时间: {{ val.updatedAt }}</p>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
<div class="paginate" style="background-color: #ffffff; margin-top: 5px">
|
||||||
|
<el-card>
|
||||||
|
<el-pagination
|
||||||
|
small
|
||||||
|
background
|
||||||
|
layout="total,prev,pager,next"
|
||||||
|
:page-size="clientSearchForm.size"
|
||||||
|
:total="clientsList.total"
|
||||||
|
@change="pageChange"
|
||||||
|
/>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.timeItem {
|
||||||
|
.el-form-item__content {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.options-class {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style scoped>
|
||||||
|
.example-showcase .el-dropdown + .el-dropdown {
|
||||||
|
margin-left: 15px;
|
||||||
|
}
|
||||||
|
.example-showcase .el-dropdown-link {
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
</style>
|
159
src/views/server/component/detail.vue
Normal file
159
src/views/server/component/detail.vue
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { FormInstance } from "element-plus";
|
||||||
|
import { storageLocal } from "@pureadmin/utils";
|
||||||
|
import { userKey } from "@/utils/auth";
|
||||||
|
import {clientFormRules} from "@/views/server/component/rules";
|
||||||
|
|
||||||
|
// 声明 props 类型
|
||||||
|
export interface DetailFormProps {
|
||||||
|
formInline: {
|
||||||
|
id: string;
|
||||||
|
serverId: string;
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
subnetRange: string;
|
||||||
|
ipAllocation: [];
|
||||||
|
allowedIPS: [];
|
||||||
|
extraAllowedIPS: [];
|
||||||
|
endpoint: string;
|
||||||
|
useServerDNS: number;
|
||||||
|
enableAfterCreation: number;
|
||||||
|
keys: {
|
||||||
|
privateKey: string;
|
||||||
|
publicKey: string;
|
||||||
|
presharedKey: string;
|
||||||
|
};
|
||||||
|
enabled: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 声明 props 默认值
|
||||||
|
// 推荐阅读:https://cn.vuejs.org/guide/typescript/composition-api.html#typing-component-props
|
||||||
|
const props = withDefaults(defineProps<DetailFormProps>(), {
|
||||||
|
formInline: () => ({
|
||||||
|
id: "",
|
||||||
|
serverId: "",
|
||||||
|
name: "",
|
||||||
|
email: "",
|
||||||
|
subnetRange: "",
|
||||||
|
ipAllocation: [],
|
||||||
|
allowedIPS: [],
|
||||||
|
extraAllowedIPS: [],
|
||||||
|
endpoint: "",
|
||||||
|
useServerDNS: 0,
|
||||||
|
enableAfterCreation: 0,
|
||||||
|
keys: {
|
||||||
|
privateKey: "",
|
||||||
|
publicKey: "",
|
||||||
|
presharedKey: ""
|
||||||
|
},
|
||||||
|
enabled: 1
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// vue 规定所有的 prop 都遵循着单向绑定原则,直接修改 prop 时,Vue 会抛出警告。此处的写法仅仅是为了消除警告。
|
||||||
|
// 因为对一个 reactive 对象执行 ref,返回 Ref 对象的 value 值仍为传入的 reactive 对象,
|
||||||
|
// 即 newFormInline === props.formInline 为 true,所以此处代码的实际效果,仍是直接修改 props.formInline。
|
||||||
|
// 但该写法仅适用于 props.formInline 是一个对象类型的情况,原始类型需抛出事件
|
||||||
|
// 推荐阅读:https://cn.vuejs.org/guide/components/props.html#one-way-data-flow
|
||||||
|
const detailForm = ref(props.formInline);
|
||||||
|
const detailFormRef = ref<FormInstance>();
|
||||||
|
|
||||||
|
function getDetailFormRef() {
|
||||||
|
return detailFormRef.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ getDetailFormRef });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-form
|
||||||
|
ref="detailFormRef"
|
||||||
|
:model="detailForm"
|
||||||
|
:rules="clientFormRules"
|
||||||
|
label-width="20%"
|
||||||
|
label-position="right"
|
||||||
|
>
|
||||||
|
<el-form-item prop="name" label="名称">
|
||||||
|
<el-input v-model="detailForm.name" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="email" label="邮箱">
|
||||||
|
<el-input v-model="detailForm.email" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="subnetRange" label="子网范围">
|
||||||
|
<el-input v-model="detailForm.subnetRange" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="ipAllocation" label="客户端IP">
|
||||||
|
<el-select
|
||||||
|
v-model="detailForm.ipAllocation"
|
||||||
|
:clearable="true"
|
||||||
|
:reserve-keyword="false"
|
||||||
|
suffix-icon=""
|
||||||
|
tag-type="warning"
|
||||||
|
popper-class="options-class"
|
||||||
|
placeholder=""
|
||||||
|
multiple
|
||||||
|
filterable
|
||||||
|
allow-create
|
||||||
|
default-first-option
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="allowedIPS" label="允许访问的IP段">
|
||||||
|
<el-select
|
||||||
|
v-model="detailForm.allowedIPS"
|
||||||
|
:clearable="true"
|
||||||
|
:reserve-keyword="false"
|
||||||
|
suffix-icon=""
|
||||||
|
tag-type="warning"
|
||||||
|
popper-class="options-class"
|
||||||
|
placeholder=""
|
||||||
|
multiple
|
||||||
|
filterable
|
||||||
|
allow-create
|
||||||
|
default-first-option
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="extraAllowedIPS" label="其他允许访问的IP段">
|
||||||
|
<el-select
|
||||||
|
v-model="detailForm.extraAllowedIPS"
|
||||||
|
:clearable="true"
|
||||||
|
:reserve-keyword="false"
|
||||||
|
suffix-icon=""
|
||||||
|
tag-type="warning"
|
||||||
|
popper-class="options-class"
|
||||||
|
placeholder=""
|
||||||
|
multiple
|
||||||
|
filterable
|
||||||
|
allow-create
|
||||||
|
default-first-option
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="endpoint" label="链接端点">
|
||||||
|
<el-input v-model="detailForm.endpoint" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="useServerDNS" label="是否使用服务端DNS">
|
||||||
|
<el-radio-group v-model="detailForm.useServerDNS">
|
||||||
|
<el-radio :value="1">是</el-radio>
|
||||||
|
<el-radio :value="0">否</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="enableAfterCreation" label="确认后创建">
|
||||||
|
<el-radio-group v-model="detailForm.enableAfterCreation">
|
||||||
|
<el-radio :value="1">是</el-radio>
|
||||||
|
<el-radio :value="0">否</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="enabled" label="状态">
|
||||||
|
<el-radio-group v-model="detailForm.enabled">
|
||||||
|
<el-radio :value="1">启用</el-radio>
|
||||||
|
<el-radio :value="0">禁用</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
<style lang="scss">
|
||||||
|
.options-class {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
45
src/views/server/component/qrCode.vue
Normal file
45
src/views/server/component/qrCode.vue
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { FormInstance } from "element-plus";
|
||||||
|
import { storageLocal } from "@pureadmin/utils";
|
||||||
|
import { userKey } from "@/utils/auth";
|
||||||
|
import {clientQrCode} from "@/api/clients";
|
||||||
|
|
||||||
|
// 声明 props 类型
|
||||||
|
export interface DetailFormProps {
|
||||||
|
formInline: {
|
||||||
|
id: string;
|
||||||
|
qrCodeStr: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 声明 props 默认值
|
||||||
|
// 推荐阅读:https://cn.vuejs.org/guide/typescript/composition-api.html#typing-component-props
|
||||||
|
const props = withDefaults(defineProps<DetailFormProps>(), {
|
||||||
|
formInline: () => ({
|
||||||
|
id: "",
|
||||||
|
qrCodeStr: ""
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const qrCodeData = ref(props.formInline);
|
||||||
|
|
||||||
|
const getQrCode = (id: string) => {
|
||||||
|
clientQrCode(id).then(res => {
|
||||||
|
if (res.code === 200) {
|
||||||
|
qrCodeData.value.qrCodeStr = res.data.qrCode;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
getQrCode(props.formInline.id);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-card body-style="padding: inherit" shadow="hover">
|
||||||
|
<el-image
|
||||||
|
:src="qrCodeData.qrCodeStr"
|
||||||
|
:preview-teleported="true"
|
||||||
|
style="width: 100%; height: 100%"
|
||||||
|
/>
|
||||||
|
</el-card>
|
||||||
|
</template>
|
36
src/views/server/component/rules.ts
Normal file
36
src/views/server/component/rules.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { reactive } from "vue";
|
||||||
|
import type { FormRules } from "element-plus";
|
||||||
|
|
||||||
|
/** 登录校验 */
|
||||||
|
const clientFormRules = reactive(<FormRules>{
|
||||||
|
name: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: "名称不能为空",
|
||||||
|
trigger: "blur"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
ipAllocation: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: "客户端IP不能为空",
|
||||||
|
trigger: "blur"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
allowedIPS: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: "允许链接IP不能为空",
|
||||||
|
trigger: "blur"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
enabled: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: "状态不能为空",
|
||||||
|
trigger: "blur"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
export { clientFormRules };
|
@ -1,10 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
defineOptions({
|
|
||||||
// name 作为一种规范最好必须写上并且和路由的name保持一致
|
|
||||||
name: "ServerConfig"
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>服务端配置页面</div>
|
|
||||||
</template>
|
|
@ -1,10 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
defineOptions({
|
|
||||||
// name 作为一种规范最好必须写上并且和路由的name保持一致
|
|
||||||
name: "Server"
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>服务端列表</div>
|
|
||||||
</template>
|
|
251
src/views/server/server.vue
Normal file
251
src/views/server/server.vue
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
<script setup lang="tsx">
|
||||||
|
import { reactive, ref } from "vue";
|
||||||
|
import type { FormInstance } from "element-plus";
|
||||||
|
import { useServerStoreHook } from "@/store/modules/server";
|
||||||
|
import { serverFormRules } from "@/views/server/utils/rules";
|
||||||
|
import { useGlobalSettingStoreHook } from "@/store/modules/globalSetting";
|
||||||
|
import { getPublicIP } from "@/api/server";
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
// name 作为一种规范最好必须写上并且和路由的name保持一致
|
||||||
|
name: "Server"
|
||||||
|
});
|
||||||
|
|
||||||
|
const serverFormRef = ref<FormInstance>();
|
||||||
|
|
||||||
|
let serverForm = reactive({
|
||||||
|
id: "",
|
||||||
|
ipScope: "",
|
||||||
|
listenPort: 0,
|
||||||
|
privateKey: "",
|
||||||
|
publicKey: "",
|
||||||
|
postUpScript: "",
|
||||||
|
preDownScript: "",
|
||||||
|
postDownScript: ""
|
||||||
|
});
|
||||||
|
|
||||||
|
const configFormRef = ref<FormInstance>();
|
||||||
|
let configForm = reactive({
|
||||||
|
endpointAddress: "",
|
||||||
|
dnsServer: [],
|
||||||
|
MTU: 0,
|
||||||
|
persistentKeepalive: 0,
|
||||||
|
firewallMark: "",
|
||||||
|
table: "",
|
||||||
|
configFilePath: ""
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取服务端信息接口
|
||||||
|
const getServerApi = () => {
|
||||||
|
useServerStoreHook()
|
||||||
|
.getServerApi()
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === 200) {
|
||||||
|
const data = useServerStoreHook().getServerInfo;
|
||||||
|
serverForm.id = data.id || "";
|
||||||
|
serverForm.ipScope = data.ipScope || "";
|
||||||
|
serverForm.listenPort = data.listenPort || 0;
|
||||||
|
serverForm.privateKey = data.privateKey || "";
|
||||||
|
serverForm.publicKey = data.publicKey || "";
|
||||||
|
serverForm.postUpScript = data.postUpScript || "";
|
||||||
|
serverForm.preDownScript = data.preDownScript || "";
|
||||||
|
serverForm.postDownScript = data.postDownScript || "";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 更新服务端信息
|
||||||
|
const updateServerApi = (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate((valid, fields) => {
|
||||||
|
if (!valid) return fields;
|
||||||
|
useServerStoreHook()
|
||||||
|
.updateServerApi(serverForm)
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === 200) {
|
||||||
|
getServerApi();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取全局配置
|
||||||
|
const getGlobalSettingApi = () => {
|
||||||
|
useGlobalSettingStoreHook()
|
||||||
|
.getGlobalSettingApi()
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === 200) {
|
||||||
|
const data = useGlobalSettingStoreHook().getGlobalSetting;
|
||||||
|
configForm.endpointAddress = data.endpointAddress || "";
|
||||||
|
configForm.dnsServer = data.dnsServer || [];
|
||||||
|
configForm.MTU = data.MTU || 0;
|
||||||
|
configForm.persistentKeepalive = data.persistentKeepalive || 0;
|
||||||
|
configForm.firewallMark = data.firewallMark || "";
|
||||||
|
configForm.table = data.table || "";
|
||||||
|
configForm.configFilePath = data.configFilePath || "";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 更新全局配置
|
||||||
|
const updateGlobalSettingApi = (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate((valid, fields) => {
|
||||||
|
if (!valid) return fields;
|
||||||
|
useGlobalSettingStoreHook()
|
||||||
|
.updateGlobalSettingApi(configForm)
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === 200) {
|
||||||
|
getGlobalSettingApi();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取当前主机的公网IP
|
||||||
|
const getPublicIPApi = () => {
|
||||||
|
getPublicIP().then(res => {
|
||||||
|
if (res.code === 200) {
|
||||||
|
useGlobalSettingStoreHook().SET_ENDPOINT_ADDRESS(res.data.IP);
|
||||||
|
configForm.endpointAddress =
|
||||||
|
useGlobalSettingStoreHook().getEndpointAddress;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
getServerApi();
|
||||||
|
getGlobalSettingApi();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-row :gutter="10">
|
||||||
|
<el-col :span="10" style="padding-left: 50px">
|
||||||
|
<el-card class="left-card" style="max-width: 800px">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>服务端信息</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="server-form">
|
||||||
|
<el-form
|
||||||
|
ref="serverFormRef"
|
||||||
|
:model="serverForm"
|
||||||
|
:rules="serverFormRules"
|
||||||
|
label-position="top"
|
||||||
|
>
|
||||||
|
<el-form-item prop="ipScope" label="子网IP段">
|
||||||
|
<el-input v-model="serverForm.ipScope" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="listenPort" label="监听端口">
|
||||||
|
<el-input-number
|
||||||
|
v-model="serverForm.listenPort"
|
||||||
|
:min="49152"
|
||||||
|
:max="65535"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="privateKey" label="私钥">
|
||||||
|
<el-input v-model="serverForm.privateKey" disabled />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="publicKey" label="公钥">
|
||||||
|
<el-input v-model="serverForm.publicKey" disabled />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="postUpScript" label="postUpScript">
|
||||||
|
<el-input v-model="serverForm.postUpScript" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="postDownScript" label="postDownScript">
|
||||||
|
<el-input v-model="serverForm.preDownScript" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="postDownScript" label="postDownScript">
|
||||||
|
<el-input v-model="serverForm.postDownScript" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="updateServerApi(serverFormRef)"
|
||||||
|
>确认</el-button
|
||||||
|
>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="10" style="padding-left: 50px">
|
||||||
|
<el-card style="max-width: 800px">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>全局配置信息</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-form ref="configFormRef" :model="configForm" label-position="top">
|
||||||
|
<el-form-item prop="endpointAddress" label="endpointAddress">
|
||||||
|
<el-input v-model="configForm.endpointAddress" />
|
||||||
|
<el-button size="small" type="warning" class="getIp" @click="getPublicIPApi">获取IP</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="dnsServer" label="dnsServer">
|
||||||
|
<el-select
|
||||||
|
v-model="configForm.dnsServer"
|
||||||
|
:clearable="true"
|
||||||
|
:reserve-keyword="false"
|
||||||
|
suffix-icon=""
|
||||||
|
tag-type="success"
|
||||||
|
popper-class="options-class"
|
||||||
|
placeholder=""
|
||||||
|
multiple
|
||||||
|
filterable
|
||||||
|
allow-create
|
||||||
|
default-first-option
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="MTU" label="MTU">
|
||||||
|
<el-input v-model="configForm.MTU" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="persistentKeepalive" label="persistentKeepalive">
|
||||||
|
<el-input v-model="configForm.persistentKeepalive" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="firewallMark" label="firewallMark">
|
||||||
|
<el-input v-model="configForm.firewallMark" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="table" label="table">
|
||||||
|
<el-input v-model="configForm.table" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="configFilePath" label="configFilePath">
|
||||||
|
<el-input v-model="configForm.configFilePath" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
@click="updateGlobalSettingApi(configFormRef)"
|
||||||
|
>确认</el-button
|
||||||
|
>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</template>
|
||||||
|
<style lang="scss">
|
||||||
|
.options-class {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.el-row {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
.el-row:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.el-col {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-content {
|
||||||
|
border-radius: 4px;
|
||||||
|
min-height: 36px;
|
||||||
|
}
|
||||||
|
.options-class {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.getIp{
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
</style>
|
22
src/views/server/utils/rules.ts
Normal file
22
src/views/server/utils/rules.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { reactive } from "vue";
|
||||||
|
import type { FormRules } from "element-plus";
|
||||||
|
|
||||||
|
/** 登录校验 */
|
||||||
|
const serverFormRules = reactive(<FormRules>{
|
||||||
|
ipScope: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: "不能为空",
|
||||||
|
trigger: "blur"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
listenPort: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: "不能为空",
|
||||||
|
trigger: "blur"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
export { serverFormRules };
|
@ -290,6 +290,7 @@ userList(); // 用户列表接口
|
|||||||
|
|
||||||
.user-list-table-header {
|
.user-list-table-header {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.div-page {
|
.div-page {
|
||||||
|
@ -1,7 +1,15 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import {useServerStoreHook} from "@/store/modules/server";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "Welcome"
|
name: "Welcome"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const initServerInfo = () => {
|
||||||
|
useServerStoreHook().getServerApi();
|
||||||
|
};
|
||||||
|
|
||||||
|
initServerInfo();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
Loading…
Reference in New Issue
Block a user