diff --git a/src/api/vpn.ts b/src/api/vpn.ts index f771ce4..b1f288b 100644 --- a/src/api/vpn.ts +++ b/src/api/vpn.ts @@ -36,6 +36,10 @@ export const SetVPNAddressPoolHandler = (Data) => { return request.post('/vpn/set_vpn_ip_pool', Data) } +export const DeleteVPNAddressPoolHandler = (Data) => { + return request.delete('/vpn/delete_vpn_ip_pool', { data: Data }) +} + /** * 获取VPN隧道配置信息 * @returns {Promise} 包含VPN隧道配置信息的Promise对象 @@ -54,6 +58,10 @@ export const GetVPNServerOnlineListHandler = () => { return request.get('/vpn/get_server_online') } +export const DeleteVPNTunnelHandler = (Data) => { + return request.delete('/vpn/delete_vpn_tunnel', { data: Data }) +} + export const GetVPNServerConfigHandler = () => { return request.get('/vpn/get_vpn_server_config') } diff --git a/src/components/menu.ts b/src/components/menu.ts index 9858a0d..9e258bb 100644 --- a/src/components/menu.ts +++ b/src/components/menu.ts @@ -84,7 +84,19 @@ export const menuData: Menus[] = [ pid: '75', index: '/vpn-server-config', title: 'VPN服务器配置', - } + }, + { + id: '752', + pid: '75', + index: '/vpn-address-pool', + title: '地址池配置', + }, + { + id: '753', + pid: '75', + index: '/vpn-tunnel', + title: '隧道配置', + }, ], }, { diff --git a/src/router/index.ts b/src/router/index.ts index d0198fa..8e88589 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -137,6 +137,24 @@ const routes: RouteRecordRaw[] = [ }, component: () => import(/* webpackChunkName: "system-user" */ '../views/system/vpn-server-config.vue'), }, + { + path: '/vpn-address-pool', + name: 'vpn-address-pool', + meta: { + title: 'VPN地址池管理', + permiss: '752', + }, + component: () => import(/* webpackChunkName: "system-user" */ '../views/system/vpn-address-pool.vue'), + }, + { + path: '/vpn-tunnel', + name: 'vpn-tunnel', + meta: { + title: 'VPN隧道管理', + permiss: '753', + }, + component: () => import(/* webpackChunkName: "system-user" */ '../views/system/vpn-tunnel.vue'), + }, { path: '/callback', name: 'callback', diff --git a/src/store/permiss.ts b/src/store/permiss.ts index fb96208..57118d3 100644 --- a/src/store/permiss.ts +++ b/src/store/permiss.ts @@ -62,6 +62,8 @@ export const usePermissStore = defineStore("permiss", { "74", //模型策略管理 "75", //VPN服务器配置管理 "751", //VPN服务器配置管理 + "752", //VPN地址池管理 + "753", //VPN隧道管理 ], user: ["0", "8", "7", "9", "51" ,"53","55" ,"56", "57", "58", "59", "61", "71", "75"], }, diff --git a/src/views/system/vpn-address-pool.vue b/src/views/system/vpn-address-pool.vue new file mode 100644 index 0000000..c061e43 --- /dev/null +++ b/src/views/system/vpn-address-pool.vue @@ -0,0 +1,560 @@ + + + + + \ No newline at end of file diff --git a/src/views/system/vpn-server-config.vue b/src/views/system/vpn-server-config.vue index 22980e6..1a217d7 100644 --- a/src/views/system/vpn-server-config.vue +++ b/src/views/system/vpn-server-config.vue @@ -64,6 +64,20 @@ + + + + + + + + + + + + + + @@ -215,6 +229,48 @@ /> + + IPv4路由配置 + + +
+
+ + + + 删除 + +
+ + 添加IPv4路由 + +
+
+ + IPv6路由配置 + + +
+
+ + + + 删除 + +
+ + 添加IPv6路由 + +
+
@@ -281,14 +337,17 @@ interface TunnelRequestAndResponse { } interface VPNRouter { - network: string; - gateway: string; + type: number; + ip: string; + prefix: number; } interface ServerConfig { name: string; server_id: string; server_ip: string; + server_ipv6: string; + server_ip_type: number; server_info: string; udp_port: number; tcp_port: number; @@ -380,7 +439,7 @@ const stopOnlineStatusTimer = () => { // 选择服务器 const selectServer = (server: ServerConfig) => { - selectedServer.value = { ...server }; + selectedServer.value = JSON.parse(JSON.stringify(server)); // 深拷贝避免直接修改 // 确保allow_user_id是用户ID数组 if (server.allow_user_id && server.allow_user_id.length > 0) { /** @@ -392,6 +451,13 @@ const selectServer = (server: ServerConfig) => { } else { selectedServer.value.allow_user_id = []; } + // 确保路由数组存在 + if (!selectedServer.value.ipv4_router) { + selectedServer.value.ipv4_router = []; + } + if (!selectedServer.value.ipv6_router) { + selectedServer.value.ipv6_router = []; + } }; // 删除服务器 @@ -425,6 +491,40 @@ const deleteServer = async (serverId: string) => { const saveConfig = async () => { if (!selectedServer.value) return; + // 验证IPv4路由配置 + for (let i = 0; i < selectedServer.value.ipv4_router.length; i++) { + const router = selectedServer.value.ipv4_router[i]; + if (!router.ip || router.ip.trim() === '') { + ElMessage.error(`IPv4路由第${i + 1}行:IP地址不能为空`); + return; + } + if (!isValidIPv4Address(router.ip)) { + ElMessage.error(`IPv4路由第${i + 1}行:IP地址格式不正确`); + return; + } + if (!router.prefix || router.prefix < 1 || router.prefix > 32) { + ElMessage.error(`IPv4路由第${i + 1}行:前缀长度必须在1-32之间`); + return; + } + } + + // 验证IPv6路由配置 + for (let i = 0; i < selectedServer.value.ipv6_router.length; i++) { + const router = selectedServer.value.ipv6_router[i]; + if (!router.ip || router.ip.trim() === '') { + ElMessage.error(`IPv6路由第${i + 1}行:IP地址不能为空`); + return; + } + if (!isValidIPv6Address(router.ip)) { + ElMessage.error(`IPv6路由第${i + 1}行:IP地址格式不正确`); + return; + } + if (!router.prefix || router.prefix < 1 || router.prefix > 128) { + ElMessage.error(`IPv6路由第${i + 1}行:前缀长度必须在1-128之间`); + return; + } + } + // 转换数据格式,确保与后端API兼容 const configData = { ...selectedServer.value }; // 将allow_user_id转换为后端期望的格式 @@ -451,6 +551,56 @@ const saveConfig = async () => { } }; +// 添加路由 +const addRouter = (type: 'ipv4' | 'ipv6') => { + if (!selectedServer.value) return; + + const router: VPNRouter = { + type: type === 'ipv4' ? 4 : 6, + ip: '', + prefix: type === 'ipv4' ? 24 : 64 + }; + + if (!selectedServer.value.ipv4_router) { + selectedServer.value.ipv4_router = []; + } + if (!selectedServer.value.ipv6_router) { + selectedServer.value.ipv6_router = []; + } + + if (type === 'ipv4') { + selectedServer.value.ipv4_router.push(router); + } else { + selectedServer.value.ipv6_router.push(router); + } +}; + +// 删除路由 +const removeRouter = (type: 'ipv4' | 'ipv6', index: number) => { + if (!selectedServer.value) return; + + if (type === 'ipv4') { + selectedServer.value.ipv4_router.splice(index, 1); + } else { + selectedServer.value.ipv6_router.splice(index, 1); + } +}; + +// 验证IPv4地址格式 +const isValidIPv4Address = (ip: string): boolean => { + if (!ip || ip.trim() === '') return false; + const ipv4Regex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; + return ipv4Regex.test(ip); +}; + +// 验证IPv6地址格式 +const isValidIPv6Address = (ip: string): boolean => { + if (!ip || ip.trim() === '') return false; + // 简化的IPv6验证,支持标准IPv6格式 + const ipv6Regex = /^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$|^(?:[0-9a-fA-F]{1,4}:){1,7}:$|^(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}$|^(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}$|^(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}$|^(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}$|^(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}$|^[0-9a-fA-F]{1,4}:(?:(?::[0-9a-fA-F]{1,4}){1,6})$|^:(?:(?::[0-9a-fA-F]{1,4}){1,7}|:)$/; + return ipv6Regex.test(ip); +}; + const GetAllDefaultUsers = async () => { try { const response = await getAllDefaultUsers(); @@ -565,4 +715,21 @@ onUnmounted(() => { .el-form { padding: 20px 0; } + +.router-section { + border: 1px solid #e4e7ed; + border-radius: 4px; + padding: 10px; + background-color: #fafafa; +} + +.router-item { + display: flex; + align-items: center; + margin-bottom: 10px; +} + +.router-item:last-child { + margin-bottom: 0; +} \ No newline at end of file diff --git a/src/views/system/vpn-tunnel.vue b/src/views/system/vpn-tunnel.vue new file mode 100644 index 0000000..dd34b1d --- /dev/null +++ b/src/views/system/vpn-tunnel.vue @@ -0,0 +1,354 @@ + + + + + \ No newline at end of file