添加路由支持配置用户用户组

This commit is contained in:
lj124 2026-05-14 23:01:25 +08:00
parent 32297ff52c
commit 888828b7a1
2 changed files with 174 additions and 138 deletions

View File

@ -20,6 +20,15 @@ export interface OnlineUserInfo {
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 {
name: string;
server_id: string;
@ -38,11 +47,10 @@ export interface ServerConfig {
allow_user_id: any[];
encryption: string;
hash: string;
no_policy_action:number;
no_policy_action: number;
user_max_device: number;
duration_time: number;
ipv4_router: any[];
ipv6_router: any[];
routers: VPNRouter[];
vpn_status: VPNStatus;
}

View File

@ -229,48 +229,68 @@
</el-select>
</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
v-for="(router, index) in selectedServer.ipv4_router"
v-for="(router, index) in selectedServer.routers"
:key="index"
class="router-item"
>
<el-input v-model="router.ip" placeholder="IP地址 (如: 192.168.1.1)" style="flex: 1;" />
<el-input-number v-model="router.prefix" :min="0" :max="32" placeholder="前缀长度" style="flex: 1; margin-left: 10px;" />
<!-- metric -->
<el-input-number v-model="router.metric" :min="0" :max="1000000" placeholder="metric" style="flex: 1; margin-left: 10px;" />
<el-button type="danger" size="small" text @click="removeRouter('ipv4', index)" style="margin-left: 10px;">
<el-select v-model="router.type" placeholder="IP类型" style="width: 120px;">
<el-option label="IPv4" :value="4" />
<el-option label="IPv6" :value="6" />
<el-option label="IPv4/IPv6" :value="46" />
</el-select>
<el-input v-model="router.ip" placeholder="IP地址" style="flex: 1; margin-left: 10px;" />
<el-input-number v-model="router.prefix" :min="0" :max="128" placeholder="前缀" style="width: 100px; margin-left: 10px;" />
<el-input-number v-model="router.metric" :min="0" :max="1000000" placeholder="metric" style="width: 100px; margin-left: 10px;" />
<el-select v-model="router.router_type" placeholder="路由类型" style="width: 130px; margin-left: 10px;" @change="handleRouterTypeChange(router)">
<el-option label="全局路由" :value="0" />
<el-option label="用户路由" :value="1" />
<el-option label="用户组路由" :value="2" />
</el-select>
<el-select
v-if="router.router_type === 1"
v-model="router.target_id"
placeholder="选择用户"
filterable
style="width: 150px; margin-left: 10px;"
>
<el-option
v-for="user in user_select_opts"
:key="user.value"
:label="user.label"
: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>
</div>
<el-button type="primary" size="small" text @click="addRouter('ipv4')">
添加IPv4路由
</el-button>
</div>
</el-form-item>
<el-divider content-position="left">IPv6路由配置</el-divider>
<el-form-item label="IPv6路由">
<div class="router-section">
<div
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-input-number v-model="router.prefix" :min="0" :max="128" placeholder="前缀长度" style="flex: 1; margin-left: 10px;" />
<!-- metric -->
<el-input-number v-model="router.metric" :min="0" :max="1000000" placeholder="metric" style="flex: 1; margin-left: 10px;" />
<el-button type="danger" size="small" text @click="removeRouter('ipv6', index)" style="margin-left: 10px;">
删除
</el-button>
</div>
<el-button type="primary" size="small" text @click="addRouter('ipv6')">
添加IPv6路由
<el-button type="primary" size="small" text @click="addRouter">
添加路由
</el-button>
</div>
</el-form-item>
@ -288,9 +308,10 @@
<script setup lang="ts" name="vpn-server-config">
import { ref, reactive, onMounted, onUnmounted } from 'vue';
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 { ServerConfig } from '@/types/vpn';
import { ServerConfig, VPNRouter } from '@/types/vpn';
interface UserID {
id: number;
}
@ -339,21 +360,16 @@ interface TunnelRequestAndResponse {
config: TunnelConfig;
}
interface VPNRouter {
type: number;
ip: string;
prefix: number;
metric: number;
}
const serverList = ref<ServerConfig[]>([]);
const selectedServer = ref<ServerConfig | null>(null);
const addressPools = ref<AddressPoolRequest[]>([]);
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 onlineServers = ref<string[]>([]);
let timer: number | null = null;
//
const getServerConfigs = async () => {
try {
@ -395,10 +411,8 @@ const getOnlineServers = async () => {
let online_server: ServerConfig[] = [];
online_server = response.data;
onlineServers.value = [];
for(
let server of online_server
){
if(server.vpn_status?.status === 2){
for (let server of online_server) {
if (server.vpn_status?.status === 2) {
onlineServers.value.push(server.server_id);
}
}
@ -431,21 +445,38 @@ const selectServer = (server: ServerConfig) => {
selectedServer.value = JSON.parse(JSON.stringify(server)); //
// allow_user_idID
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 {
selectedServer.value.allow_user_id = [];
}
//
if (!selectedServer.value.ipv4_router) {
selectedServer.value.ipv4_router = [];
if (!selectedServer.value.routers) {
selectedServer.value.routers = [];
}
if (!selectedServer.value.ipv6_router) {
selectedServer.value.ipv6_router = [];
// - ipv4_routeripv6_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
});
}
}
};
@ -480,37 +511,31 @@ 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];
//
for (let i = 0; i < selectedServer.value.routers.length; i++) {
const router = selectedServer.value.routers[i];
if (!router.ip || router.ip.trim() === '') {
ElMessage.error(`IPv4路由第${i + 1}IP地址不能为空`);
ElMessage.error(`路由第${i + 1}IP地址不能为空`);
return;
}
if (!isValidIPv4Address(router.ip)) {
ElMessage.error(`IPv4路由第${i + 1}IP地址格式不正确`);
if (router.type === 4 && !isValidIPv4Address(router.ip)) {
ElMessage.error(`路由第${i + 1}IPv4地址格式不正确`);
return;
}
if (!router.prefix || router.prefix < 1 || router.prefix > 32) {
ElMessage.error(`IPv4路由第${i + 1}行:前缀长度必须在1-32之间`);
if (router.type === 6 && !isValidIPv6Address(router.ip)) {
ElMessage.error(`路由第${i + 1}行:IPv6地址格式不正确`);
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地址不能为空`);
const maxPrefix = router.type === 4 ? 32 : 128;
if (!router.prefix || router.prefix < 0 || router.prefix > maxPrefix) {
ElMessage.error(`路由第${i + 1}前缀长度必须在0-${maxPrefix}之间`);
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;
if (router.router_type === 1 || router.router_type === 2) {
if (!router.target_id || router.target_id <= 0) {
ElMessage.error(`路由第${i + 1}行:请选择目标用户或用户组`);
return;
}
}
}
@ -524,7 +549,7 @@ const saveConfig = async () => {
server_ip: selectedServer.value.server_ip,
server_info: selectedServer.value.server_info,
config: configData
}
};
try {
let resp = await SetVPNServerConfigHandler(req);
if (resp["code"] !== 0) {
@ -532,7 +557,7 @@ const saveConfig = async () => {
return;
}
ElMessage.success('配置保存成功');
//
//
getServerConfigs();
} catch (error) {
ElMessage.error('保存配置失败');
@ -541,39 +566,35 @@ const saveConfig = async () => {
};
//
const addRouter = (type: 'ipv4' | 'ipv6') => {
const addRouter = () => {
if (!selectedServer.value) return;
if (!selectedServer.value.routers) {
selectedServer.value.routers = [];
}
const router: VPNRouter = {
type: type === 'ipv4' ? 4 : 6,
type: 4,
ip: '',
prefix: type === 'ipv4' ? 24 : 64,
metric: 35
prefix: 24,
metric: 35,
router_type: 0,
target_id: 0
};
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);
}
selectedServer.value.routers.push(router);
};
//
const removeRouter = (type: 'ipv4' | 'ipv6', index: number) => {
const removeRouter = (index: number) => {
if (!selectedServer.value) return;
selectedServer.value.routers.splice(index, 1);
};
if (type === 'ipv4') {
selectedServer.value.ipv4_router.splice(index, 1);
} else {
selectedServer.value.ipv6_router.splice(index, 1);
}
//
const handleRouterTypeChange = (router: VPNRouter) => {
// ID
router.target_id = 0;
};
// IPv4
@ -599,16 +620,21 @@ const GetAllDefaultUsers = async () => {
return;
}
user_select_opts.value.length = 0;
group_select_opts.value.length = 0;
for (let i = 0; i < response.data.length; i++) {
let user = response.data[i];
if (user.type === 0) {
let item = response.data[i];
if (item.type === 0) {
user_select_opts.value.push({
value: user.id,
label: user.name
value: item.id,
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) {
console.error('获取默认用户失败:', error);
}
@ -717,6 +743,8 @@ onUnmounted(() => {
display: flex;
align-items: center;
margin-bottom: 10px;
flex-wrap: wrap;
gap: 5px;
}
.router-item:last-child {