添加路由支持配置用户用户组
This commit is contained in:
parent
32297ff52c
commit
888828b7a1
|
|
@ -20,6 +20,15 @@ export interface OnlineUserInfo {
|
||||||
client_ip: string;
|
client_ip: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface VPNRouter {
|
||||||
|
type: number; // 4, 6, 46
|
||||||
|
ip: string;
|
||||||
|
prefix: number;
|
||||||
|
metric: number;
|
||||||
|
router_type: number; // 0-全局路由, 1-用户路由, 2-用户组路由
|
||||||
|
target_id: number; // 目标ID: 0-全局, >0-用户ID或用户组ID
|
||||||
|
}
|
||||||
|
|
||||||
export interface ServerConfig {
|
export interface ServerConfig {
|
||||||
name: string;
|
name: string;
|
||||||
server_id: string;
|
server_id: string;
|
||||||
|
|
@ -38,11 +47,10 @@ export interface ServerConfig {
|
||||||
allow_user_id: any[];
|
allow_user_id: any[];
|
||||||
encryption: string;
|
encryption: string;
|
||||||
hash: string;
|
hash: string;
|
||||||
no_policy_action:number;
|
no_policy_action: number;
|
||||||
user_max_device: number;
|
user_max_device: number;
|
||||||
duration_time: number;
|
duration_time: number;
|
||||||
ipv4_router: any[];
|
routers: VPNRouter[];
|
||||||
ipv6_router: any[];
|
|
||||||
vpn_status: VPNStatus;
|
vpn_status: VPNStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -229,48 +229,68 @@
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-divider content-position="left">IPv4路由配置</el-divider>
|
<el-divider content-position="left">路由配置</el-divider>
|
||||||
|
|
||||||
<el-form-item label="IPv4路由">
|
<el-form-item label="路由列表">
|
||||||
<div class="router-section">
|
<div class="router-section">
|
||||||
<div
|
<div
|
||||||
v-for="(router, index) in selectedServer.ipv4_router"
|
v-for="(router, index) in selectedServer.routers"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="router-item"
|
class="router-item"
|
||||||
>
|
>
|
||||||
<el-input v-model="router.ip" placeholder="IP地址 (如: 192.168.1.1)" style="flex: 1;" />
|
<el-select v-model="router.type" placeholder="IP类型" style="width: 120px;">
|
||||||
<el-input-number v-model="router.prefix" :min="0" :max="32" placeholder="前缀长度" style="flex: 1; margin-left: 10px;" />
|
<el-option label="IPv4" :value="4" />
|
||||||
<!-- metric -->
|
<el-option label="IPv6" :value="6" />
|
||||||
<el-input-number v-model="router.metric" :min="0" :max="1000000" placeholder="metric" style="flex: 1; margin-left: 10px;" />
|
<el-option label="IPv4/IPv6" :value="46" />
|
||||||
<el-button type="danger" size="small" text @click="removeRouter('ipv4', index)" style="margin-left: 10px;">
|
</el-select>
|
||||||
删除
|
<el-input v-model="router.ip" placeholder="IP地址" style="flex: 1; margin-left: 10px;" />
|
||||||
</el-button>
|
<el-input-number v-model="router.prefix" :min="0" :max="128" placeholder="前缀" style="width: 100px; margin-left: 10px;" />
|
||||||
</div>
|
<el-input-number v-model="router.metric" :min="0" :max="1000000" placeholder="metric" style="width: 100px; margin-left: 10px;" />
|
||||||
<el-button type="primary" size="small" text @click="addRouter('ipv4')">
|
<el-select v-model="router.router_type" placeholder="路由类型" style="width: 130px; margin-left: 10px;" @change="handleRouterTypeChange(router)">
|
||||||
添加IPv4路由
|
<el-option label="全局路由" :value="0" />
|
||||||
</el-button>
|
<el-option label="用户路由" :value="1" />
|
||||||
</div>
|
<el-option label="用户组路由" :value="2" />
|
||||||
</el-form-item>
|
</el-select>
|
||||||
|
<el-select
|
||||||
<el-divider content-position="left">IPv6路由配置</el-divider>
|
v-if="router.router_type === 1"
|
||||||
|
v-model="router.target_id"
|
||||||
<el-form-item label="IPv6路由">
|
placeholder="选择用户"
|
||||||
<div class="router-section">
|
filterable
|
||||||
<div
|
style="width: 150px; margin-left: 10px;"
|
||||||
v-for="(router, index) in selectedServer.ipv6_router"
|
|
||||||
:key="index"
|
|
||||||
class="router-item"
|
|
||||||
>
|
>
|
||||||
<el-input v-model="router.ip" placeholder="IP地址 (如: 2001:db8::1)" style="flex: 1;" />
|
<el-option
|
||||||
<el-input-number v-model="router.prefix" :min="0" :max="128" placeholder="前缀长度" style="flex: 1; margin-left: 10px;" />
|
v-for="user in user_select_opts"
|
||||||
<!-- metric -->
|
:key="user.value"
|
||||||
<el-input-number v-model="router.metric" :min="0" :max="1000000" placeholder="metric" style="flex: 1; margin-left: 10px;" />
|
:label="user.label"
|
||||||
<el-button type="danger" size="small" text @click="removeRouter('ipv6', index)" style="margin-left: 10px;">
|
:value="user.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<el-select
|
||||||
|
v-else-if="router.router_type === 2"
|
||||||
|
v-model="router.target_id"
|
||||||
|
placeholder="选择用户组"
|
||||||
|
filterable
|
||||||
|
style="width: 150px; margin-left: 10px;"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="group in group_select_opts"
|
||||||
|
:key="group.value"
|
||||||
|
:label="group.label"
|
||||||
|
:value="group.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<el-input
|
||||||
|
v-else
|
||||||
|
disabled
|
||||||
|
value="全局"
|
||||||
|
style="width: 150px; margin-left: 10px;"
|
||||||
|
/>
|
||||||
|
<el-button type="danger" size="small" text @click="removeRouter(index)" style="margin-left: 10px;">
|
||||||
删除
|
删除
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<el-button type="primary" size="small" text @click="addRouter('ipv6')">
|
<el-button type="primary" size="small" text @click="addRouter">
|
||||||
添加IPv6路由
|
添加路由
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
@ -288,9 +308,10 @@
|
||||||
<script setup lang="ts" name="vpn-server-config">
|
<script setup lang="ts" name="vpn-server-config">
|
||||||
import { ref, reactive, onMounted, onUnmounted } from 'vue';
|
import { ref, reactive, onMounted, onUnmounted } from 'vue';
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
import {getAllDefaultUsers} from '@/api/user';
|
import { getAllDefaultUsers } from '@/api/user';
|
||||||
import { GetVPNServerConfigHandler, SetVPNServerConfigHandler, DeleteVPNServerHandler, GetVPNAddressPoolHandler, GetVPNTunnelConfigHandler, GetVPNServerOnlineListHandler } from '@/api/vpn';
|
import { GetVPNServerConfigHandler, SetVPNServerConfigHandler, DeleteVPNServerHandler, GetVPNAddressPoolHandler, GetVPNTunnelConfigHandler, GetVPNServerOnlineListHandler } from '@/api/vpn';
|
||||||
import { ServerConfig } from '@/types/vpn';
|
import { ServerConfig, VPNRouter } from '@/types/vpn';
|
||||||
|
|
||||||
interface UserID {
|
interface UserID {
|
||||||
id: number;
|
id: number;
|
||||||
}
|
}
|
||||||
|
|
@ -339,21 +360,16 @@ interface TunnelRequestAndResponse {
|
||||||
config: TunnelConfig;
|
config: TunnelConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface VPNRouter {
|
|
||||||
type: number;
|
|
||||||
ip: string;
|
|
||||||
prefix: number;
|
|
||||||
metric: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const serverList = ref<ServerConfig[]>([]);
|
const serverList = ref<ServerConfig[]>([]);
|
||||||
const selectedServer = ref<ServerConfig | null>(null);
|
const selectedServer = ref<ServerConfig | null>(null);
|
||||||
const addressPools = ref<AddressPoolRequest[]>([]);
|
const addressPools = ref<AddressPoolRequest[]>([]);
|
||||||
const tunnelConfigs = ref<TunnelRequestAndResponse[]>([]);
|
const tunnelConfigs = ref<TunnelRequestAndResponse[]>([]);
|
||||||
const user_select_opts = ref<Array<{value: number, label: string}>>([]);
|
const user_select_opts = ref<Array<{ value: number, label: string }>>([]);
|
||||||
|
const group_select_opts = ref<Array<{ value: number, label: string }>>([]);
|
||||||
const user_select_ids = ref<number[]>([]);
|
const user_select_ids = ref<number[]>([]);
|
||||||
const onlineServers = ref<string[]>([]);
|
const onlineServers = ref<string[]>([]);
|
||||||
let timer: number | null = null;
|
let timer: number | null = null;
|
||||||
|
|
||||||
// 获取服务器配置列表
|
// 获取服务器配置列表
|
||||||
const getServerConfigs = async () => {
|
const getServerConfigs = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -395,10 +411,8 @@ const getOnlineServers = async () => {
|
||||||
let online_server: ServerConfig[] = [];
|
let online_server: ServerConfig[] = [];
|
||||||
online_server = response.data;
|
online_server = response.data;
|
||||||
onlineServers.value = [];
|
onlineServers.value = [];
|
||||||
for(
|
for (let server of online_server) {
|
||||||
let server of online_server
|
if (server.vpn_status?.status === 2) {
|
||||||
){
|
|
||||||
if(server.vpn_status?.status === 2){
|
|
||||||
onlineServers.value.push(server.server_id);
|
onlineServers.value.push(server.server_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -431,21 +445,38 @@ const selectServer = (server: ServerConfig) => {
|
||||||
selectedServer.value = JSON.parse(JSON.stringify(server)); // 深拷贝避免直接修改
|
selectedServer.value = JSON.parse(JSON.stringify(server)); // 深拷贝避免直接修改
|
||||||
// 确保allow_user_id是用户ID数组
|
// 确保allow_user_id是用户ID数组
|
||||||
if (server.allow_user_id && server.allow_user_id.length > 0) {
|
if (server.allow_user_id && server.allow_user_id.length > 0) {
|
||||||
/**
|
|
||||||
* Extracts the id property from an item object
|
|
||||||
* @param {Object} item - The item object containing an id property
|
|
||||||
* @returns {*} The value of the item's id property
|
|
||||||
*/
|
|
||||||
user_select_ids.value = server.allow_user_id.map(item => item.id);
|
user_select_ids.value = server.allow_user_id.map(item => item.id);
|
||||||
} else {
|
} else {
|
||||||
selectedServer.value.allow_user_id = [];
|
selectedServer.value.allow_user_id = [];
|
||||||
}
|
}
|
||||||
// 确保路由数组存在
|
// 确保路由数组存在
|
||||||
if (!selectedServer.value.ipv4_router) {
|
if (!selectedServer.value.routers) {
|
||||||
selectedServer.value.ipv4_router = [];
|
selectedServer.value.routers = [];
|
||||||
|
}
|
||||||
|
// 兼容旧数据格式 - 将ipv4_router和ipv6_router转换为新格式
|
||||||
|
if ((selectedServer.value as any).ipv4_router && Array.isArray((selectedServer.value as any).ipv4_router)) {
|
||||||
|
for (const router of (selectedServer.value as any).ipv4_router) {
|
||||||
|
selectedServer.value.routers.push({
|
||||||
|
type: 4,
|
||||||
|
ip: router.ip || '',
|
||||||
|
prefix: router.prefix || 24,
|
||||||
|
metric: router.metric || 35,
|
||||||
|
router_type: 0,
|
||||||
|
target_id: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((selectedServer.value as any).ipv6_router && Array.isArray((selectedServer.value as any).ipv6_router)) {
|
||||||
|
for (const router of (selectedServer.value as any).ipv6_router) {
|
||||||
|
selectedServer.value.routers.push({
|
||||||
|
type: 6,
|
||||||
|
ip: router.ip || '',
|
||||||
|
prefix: router.prefix || 64,
|
||||||
|
metric: router.metric || 35,
|
||||||
|
router_type: 0,
|
||||||
|
target_id: 0
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (!selectedServer.value.ipv6_router) {
|
|
||||||
selectedServer.value.ipv6_router = [];
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -480,37 +511,31 @@ const deleteServer = async (serverId: string) => {
|
||||||
const saveConfig = async () => {
|
const saveConfig = async () => {
|
||||||
if (!selectedServer.value) return;
|
if (!selectedServer.value) return;
|
||||||
|
|
||||||
// 验证IPv4路由配置
|
// 验证路由配置
|
||||||
for (let i = 0; i < selectedServer.value.ipv4_router.length; i++) {
|
for (let i = 0; i < selectedServer.value.routers.length; i++) {
|
||||||
const router = selectedServer.value.ipv4_router[i];
|
const router = selectedServer.value.routers[i];
|
||||||
if (!router.ip || router.ip.trim() === '') {
|
if (!router.ip || router.ip.trim() === '') {
|
||||||
ElMessage.error(`IPv4路由第${i + 1}行:IP地址不能为空`);
|
ElMessage.error(`路由第${i + 1}行:IP地址不能为空`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!isValidIPv4Address(router.ip)) {
|
if (router.type === 4 && !isValidIPv4Address(router.ip)) {
|
||||||
ElMessage.error(`IPv4路由第${i + 1}行:IP地址格式不正确`);
|
ElMessage.error(`路由第${i + 1}行:IPv4地址格式不正确`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!router.prefix || router.prefix < 1 || router.prefix > 32) {
|
if (router.type === 6 && !isValidIPv6Address(router.ip)) {
|
||||||
ElMessage.error(`IPv4路由第${i + 1}行:前缀长度必须在1-32之间`);
|
ElMessage.error(`路由第${i + 1}行:IPv6地址格式不正确`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
const maxPrefix = router.type === 4 ? 32 : 128;
|
||||||
|
if (!router.prefix || router.prefix < 0 || router.prefix > maxPrefix) {
|
||||||
// 验证IPv6路由配置
|
ElMessage.error(`路由第${i + 1}行:前缀长度必须在0-${maxPrefix}之间`);
|
||||||
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;
|
return;
|
||||||
}
|
}
|
||||||
if (!isValidIPv6Address(router.ip)) {
|
if (router.router_type === 1 || router.router_type === 2) {
|
||||||
ElMessage.error(`IPv6路由第${i + 1}行:IP地址格式不正确`);
|
if (!router.target_id || router.target_id <= 0) {
|
||||||
|
ElMessage.error(`路由第${i + 1}行:请选择目标用户或用户组`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!router.prefix || router.prefix < 1 || router.prefix > 128) {
|
|
||||||
ElMessage.error(`IPv6路由第${i + 1}行:前缀长度必须在1-128之间`);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -524,7 +549,7 @@ const saveConfig = async () => {
|
||||||
server_ip: selectedServer.value.server_ip,
|
server_ip: selectedServer.value.server_ip,
|
||||||
server_info: selectedServer.value.server_info,
|
server_info: selectedServer.value.server_info,
|
||||||
config: configData
|
config: configData
|
||||||
}
|
};
|
||||||
try {
|
try {
|
||||||
let resp = await SetVPNServerConfigHandler(req);
|
let resp = await SetVPNServerConfigHandler(req);
|
||||||
if (resp["code"] !== 0) {
|
if (resp["code"] !== 0) {
|
||||||
|
|
@ -532,7 +557,7 @@ const saveConfig = async () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ElMessage.success('配置保存成功');
|
ElMessage.success('配置保存成功');
|
||||||
//获取最新数据
|
// 获取最新数据
|
||||||
getServerConfigs();
|
getServerConfigs();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ElMessage.error('保存配置失败');
|
ElMessage.error('保存配置失败');
|
||||||
|
|
@ -541,39 +566,35 @@ const saveConfig = async () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// 添加路由
|
// 添加路由
|
||||||
const addRouter = (type: 'ipv4' | 'ipv6') => {
|
const addRouter = () => {
|
||||||
if (!selectedServer.value) return;
|
if (!selectedServer.value) return;
|
||||||
|
|
||||||
|
if (!selectedServer.value.routers) {
|
||||||
|
selectedServer.value.routers = [];
|
||||||
|
}
|
||||||
|
|
||||||
const router: VPNRouter = {
|
const router: VPNRouter = {
|
||||||
type: type === 'ipv4' ? 4 : 6,
|
type: 4,
|
||||||
ip: '',
|
ip: '',
|
||||||
prefix: type === 'ipv4' ? 24 : 64,
|
prefix: 24,
|
||||||
metric: 35
|
metric: 35,
|
||||||
|
router_type: 0,
|
||||||
|
target_id: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!selectedServer.value.ipv4_router) {
|
selectedServer.value.routers.push(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) => {
|
const removeRouter = (index: number) => {
|
||||||
if (!selectedServer.value) return;
|
if (!selectedServer.value) return;
|
||||||
|
selectedServer.value.routers.splice(index, 1);
|
||||||
|
};
|
||||||
|
|
||||||
if (type === 'ipv4') {
|
// 路由类型变化处理
|
||||||
selectedServer.value.ipv4_router.splice(index, 1);
|
const handleRouterTypeChange = (router: VPNRouter) => {
|
||||||
} else {
|
// 重置目标ID
|
||||||
selectedServer.value.ipv6_router.splice(index, 1);
|
router.target_id = 0;
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 验证IPv4地址格式
|
// 验证IPv4地址格式
|
||||||
|
|
@ -599,16 +620,21 @@ const GetAllDefaultUsers = async () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
user_select_opts.value.length = 0;
|
user_select_opts.value.length = 0;
|
||||||
|
group_select_opts.value.length = 0;
|
||||||
for (let i = 0; i < response.data.length; i++) {
|
for (let i = 0; i < response.data.length; i++) {
|
||||||
let user = response.data[i];
|
let item = response.data[i];
|
||||||
if (user.type === 0) {
|
if (item.type === 0) {
|
||||||
user_select_opts.value.push({
|
user_select_opts.value.push({
|
||||||
value: user.id,
|
value: item.id,
|
||||||
label: user.name
|
label: item.name
|
||||||
|
});
|
||||||
|
} else if (item.type === 1) {
|
||||||
|
group_select_opts.value.push({
|
||||||
|
value: item.id,
|
||||||
|
label: item.name
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//console.log("user_select_opts:", user_select_opts.value);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取默认用户失败:', error);
|
console.error('获取默认用户失败:', error);
|
||||||
}
|
}
|
||||||
|
|
@ -717,6 +743,8 @@ onUnmounted(() => {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.router-item:last-child {
|
.router-item:last-child {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue