diff --git a/package.json b/package.json index b371205..0022277 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "url": "https://github.com/xiaoxian521" }, "dependencies": { + "@element-plus/icons-vue": "^2.3.1", "@pureadmin/descriptions": "^1.2.1", "@pureadmin/table": "^3.1.2", "@pureadmin/utils": "^2.4.7", @@ -64,6 +65,7 @@ "path": "^0.12.7", "pinia": "^2.1.7", "pinyin-pro": "^3.20.4", + "plus-pro-components": "^0.1.4", "qs": "^6.12.1", "responsive-storage": "^2.2.0", "sortablejs": "^1.15.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0d0371c..576c625 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,6 +5,9 @@ settings: excludeLinksFromLockfile: false dependencies: + '@element-plus/icons-vue': + specifier: ^2.3.1 + version: 2.3.1(vue@3.4.27) '@pureadmin/descriptions': specifier: ^1.2.1 version: 1.2.1(echarts@5.5.0)(element-plus@2.7.1)(typescript@5.4.5) @@ -56,6 +59,9 @@ dependencies: pinyin-pro: specifier: ^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: specifier: ^6.12.1 version: 6.12.1 @@ -4623,6 +4629,19 @@ packages: dev: false 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: resolution: {integrity: sha512-la8gPM1WYeFznb/JqF4GiTkRRPZsfaj2+kCxqQgr2MJylMmIKUwBfWW8Wa5fml/8gmtlD5yI01MP1QCZPWmppA==} dependencies: diff --git a/src/api/clients.ts b/src/api/clients.ts new file mode 100644 index 0000000..f8902ed --- /dev/null +++ b/src/api/clients.ts @@ -0,0 +1,29 @@ +import { http } from "@/utils/http"; +import { baseUri } from "@/api/utils"; + +// 获取服务端信息 +export const getClients = (params?: object) => { + return http.request("get", baseUri("/client/list"), { params }); +}; + +// 下载客户端配置文件 +export const downloadClient = (id: string) => { + return http.request("post", baseUri("/client/download/" + id), null, { + responseType: "arraybuffer" + }); +}; + +// 获取客户端配置二维码 +export const clientQrCode = (id: string) => { + return http.request("post", baseUri("/client/generate-qrcode/" + id)); +}; + +// 新增/更新客户端信息 +export const saveClient = (data?: object) => { + return http.request("post", baseUri("/client/save"), { data }); +}; + +// 删除客户端 +export const deleteClient = (id: string) => { + return http.request("delete", baseUri("/client/" + id)); +}; diff --git a/src/api/server.ts b/src/api/server.ts new file mode 100644 index 0000000..fe1fcbd --- /dev/null +++ b/src/api/server.ts @@ -0,0 +1,27 @@ +import { http } from "@/utils/http"; +import { baseUri } from "@/api/utils"; + +// 获取服务端信息 +export const getServer = () => { + return http.request("get", baseUri("/server")); +}; + +// 更新服务端信息 +export const updateServer = (data?: object) => { + return http.request("post", baseUri("/server"), { data }); +}; + +// 获取全局配置 +export const getGlobalConfig = () => { + return http.request("get", baseUri("/setting/server")); +}; + +// 更新全局配置 +export const updateGlobalSetting = (data?: object) => { + return http.request("post", baseUri("/setting/server-global"), { data }); +}; + +// 获取当前主机的公网IP +export const getPublicIP = () => { + return http.request("get", baseUri("/setting/public-ip")); +}; diff --git a/src/components/ReIcon/src/offlineIcon.ts b/src/components/ReIcon/src/offlineIcon.ts index 2f63896..ddc3112 100644 --- a/src/components/ReIcon/src/offlineIcon.ts +++ b/src/components/ReIcon/src/offlineIcon.ts @@ -1,4 +1,4 @@ -// 这里存放本地图标,在 src/layout/list.vue 文件中加载,避免在首启动加载 +// 这里存放本地图标,在 src/layout/server.vue 文件中加载,避免在首启动加载 import { addIcon } from "@iconify/vue/dist/offline"; // 本地菜单图标,后端在路由的 icon 中返回对应的图标字符串并且前端在此处使用 addIcon 添加即可渲染菜单图标 diff --git a/src/router/modules/server.ts b/src/router/modules/server.ts index 19f40ee..cb50b5a 100644 --- a/src/router/modules/server.ts +++ b/src/router/modules/server.ts @@ -5,20 +5,20 @@ export default { }, children: [ { - path: "/server/index", - name: "Server", - component: () => import("@/views/server/list.vue"), + path: "/server/config", + name: "Clients", + component: () => import("@/views/server/clients.vue"), meta: { - title: "服务端列表", + title: "客户端", showParent: true } }, { - path: "/server/config", - name: "ServerConfig", - component: () => import("@/views/server/config.vue"), + path: "/server/index", + name: "Server", + component: () => import("@/views/server/server.vue"), meta: { - title: "服务端配置", + title: "服务端", showParent: true } } diff --git a/src/store/modules/globalSetting.ts b/src/store/modules/globalSetting.ts new file mode 100644 index 0000000..b80d57d --- /dev/null +++ b/src/store/modules/globalSetting.ts @@ -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((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((resolve, reject) => { + updateGlobalSetting(data) + .then(res => { + resolve(res); + }) + .catch(error => { + reject(error); + }); + }); + } + } +}); +export function useGlobalSettingStoreHook() { + return useGlobalSettingStore(store); +} diff --git a/src/store/modules/server.ts b/src/store/modules/server.ts new file mode 100644 index 0000000..079fa94 --- /dev/null +++ b/src/store/modules/server.ts @@ -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((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((resolve, reject) => { + updateServer(data) + .then(res => { + resolve(res); + }) + .catch(error => { + reject(error); + }); + }); + } + } +}); +export function useServerStoreHook() { + return useServerStore(store); +} diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts index 86befdd..d948326 100644 --- a/src/store/modules/user.ts +++ b/src/store/modules/user.ts @@ -39,7 +39,7 @@ export const useUserStore = defineStore({ this.status = status; }, /** 登入 */ - async loginByUsername(data: any) { + async loginByUsername(data?: any) { return new Promise((resolve, reject) => { login(data) .then(data => { diff --git a/src/store/types.ts b/src/store/types.ts index d0e39b1..ef39bf2 100644 --- a/src/store/types.ts +++ b/src/store/types.ts @@ -45,3 +45,24 @@ export type userType = { isAdmin?: 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; +}; diff --git a/src/utils/http/index.ts b/src/utils/http/index.ts index c77b554..ab0f1d6 100644 --- a/src/utils/http/index.ts +++ b/src/utils/http/index.ts @@ -11,8 +11,11 @@ import type { } from "./types.d"; import { stringify } from "qs"; import NProgress from "../progress"; -import { getToken, formatToken } from "@/utils/auth"; +import { getToken, formatToken, TokenKey, multipleTabsKey } from "@/utils/auth"; 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 const defaultConfig: AxiosRequestConfig = { @@ -82,6 +85,16 @@ class PureHttp { const now = new Date().getTime() / 1000; const expired = parseInt(data.expireAt) - now <= 0; 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)); } else { config.headers["Authorization"] = data.token; diff --git a/src/utils/message.ts b/src/utils/message.ts index 226febb..6c73627 100644 --- a/src/utils/message.ts +++ b/src/utils/message.ts @@ -30,7 +30,7 @@ interface MessageParams { onClose?: Function | null; } -/** 用法非常简单,参考 src/views/components/message/list.vue 文件 */ +/** 用法非常简单,参考 src/views/components/message/server.vue 文件 */ /** * `Message` 消息提示函数 diff --git a/src/views/server/clients.vue b/src/views/server/clients.vue new file mode 100644 index 0000000..67a2d3b --- /dev/null +++ b/src/views/server/clients.vue @@ -0,0 +1,442 @@ + + + + + + diff --git a/src/views/server/component/detail.vue b/src/views/server/component/detail.vue new file mode 100644 index 0000000..51ea9fe --- /dev/null +++ b/src/views/server/component/detail.vue @@ -0,0 +1,159 @@ + + + + diff --git a/src/views/server/component/qrCode.vue b/src/views/server/component/qrCode.vue new file mode 100644 index 0000000..fdf24de --- /dev/null +++ b/src/views/server/component/qrCode.vue @@ -0,0 +1,45 @@ + + + diff --git a/src/views/server/component/rules.ts b/src/views/server/component/rules.ts new file mode 100644 index 0000000..99575e8 --- /dev/null +++ b/src/views/server/component/rules.ts @@ -0,0 +1,36 @@ +import { reactive } from "vue"; +import type { FormRules } from "element-plus"; + +/** 登录校验 */ +const clientFormRules = reactive({ + 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 }; diff --git a/src/views/server/config.vue b/src/views/server/config.vue deleted file mode 100644 index 8abddb3..0000000 --- a/src/views/server/config.vue +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/src/views/server/list.vue b/src/views/server/list.vue deleted file mode 100644 index c62eabc..0000000 --- a/src/views/server/list.vue +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/src/views/server/server.vue b/src/views/server/server.vue new file mode 100644 index 0000000..c98d443 --- /dev/null +++ b/src/views/server/server.vue @@ -0,0 +1,251 @@ + + + + + diff --git a/src/views/server/utils/rules.ts b/src/views/server/utils/rules.ts new file mode 100644 index 0000000..3a1b963 --- /dev/null +++ b/src/views/server/utils/rules.ts @@ -0,0 +1,22 @@ +import { reactive } from "vue"; +import type { FormRules } from "element-plus"; + +/** 登录校验 */ +const serverFormRules = reactive({ + ipScope: [ + { + required: true, + message: "不能为空", + trigger: "blur" + } + ], + listenPort: [ + { + required: true, + message: "不能为空", + trigger: "blur" + } + ] +}); + +export { serverFormRules }; diff --git a/src/views/user/list.vue b/src/views/user/list.vue index bd1d949..21a0e39 100644 --- a/src/views/user/list.vue +++ b/src/views/user/list.vue @@ -290,6 +290,7 @@ userList(); // 用户列表接口 .user-list-table-header { background-color: #fff; + padding: 20px; } .div-page { diff --git a/src/views/welcome/index.vue b/src/views/welcome/index.vue index 4e13556..49e44a8 100644 --- a/src/views/welcome/index.vue +++ b/src/views/welcome/index.vue @@ -1,7 +1,15 @@