添加在线用户查看及踢出用户

This commit is contained in:
lijun 2026-01-18 16:19:57 +08:00
parent fcb81756e2
commit 97534d399a
5 changed files with 384 additions and 0 deletions

View File

@ -86,6 +86,13 @@ export const GetClientDownloadURLHandler = () => {
return request.get("/vpn/clients_url") return request.get("/vpn/clients_url")
} }
export const GetServerOnlineUsers = (serverID:string) => {
return request.get("/vpn/get_client_online_users?server_id=" + serverID)
}
export const KickOutOnlineUser = (Data) => {
return request.post("/vpn/kick_out_user", Data)
}
export const LocalClientConnectHandler = (Data) => { export const LocalClientConnectHandler = (Data) => {
return local_request.post('/vpn/connect', Data) return local_request.post('/vpn/connect', Data)
} }

View File

@ -97,6 +97,12 @@ export const menuData: Menus[] = [
index: '/vpn-tunnel', index: '/vpn-tunnel',
title: '隧道配置', title: '隧道配置',
}, },
{
id: '755',
pid: '75',
index: '/vpn-online-user',
title: '在线用户',
}
], ],
}, },
{ {

View File

@ -164,6 +164,16 @@ const routes: RouteRecordRaw[] = [
}, },
component: () => import(/* webpackChunkName: "system-user" */ '../views/system/vpn-online-connect.vue'), component: () => import(/* webpackChunkName: "system-user" */ '../views/system/vpn-online-connect.vue'),
}, },
{
path: '/vpn-online-user',
name: 'vpn-online-user',
meta: {
title: 'VPN在线用户连接',
permiss: '755',
},
component: () => import(/* webpackChunkName: "system-user" */ '../views/system/vpn-server-online-user.vue'),
},
{ {
path: '/callback', path: '/callback',
name: 'callback', name: 'callback',

View File

@ -65,6 +65,7 @@ export const usePermissStore = defineStore("permiss", {
"752", //VPN地址池管理 "752", //VPN地址池管理
"753", //VPN隧道管理 "753", //VPN隧道管理
"754", //VPN客户端UI "754", //VPN客户端UI
"755", //VPN在线用户连接
], ],
user: ["0", "8", "7", "9", "51" ,"53","55" ,"56", "57", "58", "59", "61", "71", "754"], user: ["0", "8", "7", "9", "51" ,"53","55" ,"56", "57", "58", "59", "61", "71", "754"],
}, },

View File

@ -0,0 +1,360 @@
<template>
<div class="vpn-server-online-user">
<el-row :gutter="20">
<!-- 左侧服务器列表 -->
<el-col :span="8">
<el-card shadow="hover">
<template #header>
<div class="card-header">
<span>服务器列表</span>
</div>
</template>
<div class="server-list">
<div
v-for="server in serverList"
:key="server.server_id"
class="server-item"
:class="{ active: selectedServer?.server_id === server.server_id }"
@click="selectServer(server)"
>
<div class="server-info">
<div class="server-name">
<span class="status-indicator" :class="{ 'online': onlineServers.includes(server.server_id) }"></span>
{{ server.name }}
</div>
<div class="server-ip">{{ server.server_ip }}</div>
</div>
</div>
<el-empty v-if="serverList.length === 0" description="暂无服务器配置" />
</div>
</el-card>
</el-col>
<!-- 右侧在线用户列表 -->
<el-col :span="16">
<el-card shadow="hover" v-if="selectedServer">
<template #header>
<div class="card-header">
<span>{{ selectedServer.name }} - 在线用户 ({{ onlineUsers.length }})</span>
<el-button type="primary" @click="KickOutAllOnlineUser" :loading="loading">
踢出所有用户
</el-button>
<el-button type="primary" @click="refreshOnlineUsers" :loading="loading">
刷新
</el-button>
</div>
</template>
<el-table
:data="onlineUsers"
style="width: 100%"
v-loading="loading"
stripe
border
>
<el-table-column prop="id" label="密钥ID" width="120" />
<el-table-column prop="user_name" label="用户名" width="120" />
<el-table-column prop="private_ipv4" label="内网IPv4" width="140" />
<el-table-column prop="private_ipv6" label="内网IPv6" width="180">
<template #default="scope">
{{ scope.row.private_ipv6 || '-' }}
</template>
</el-table-column>
<el-table-column prop="uuid" label="会话ID" min-width="200" show-overflow-tooltip />
<el-table-column prop="last_update_time" label="最后更新时间" width="180">
<template #default="scope">
{{ formatTime(scope.row.last_update_time) }}
</template>
</el-table-column>
<el-table-column prop="last_update_time" label="操作" width="180" #default="scope">
<el-button type="primary" @click="KickOutSomeUser(scope.row)">踢出</el-button>
</el-table-column>
</el-table>
<el-empty v-if="!loading && onlineUsers.length === 0" description="该服务器暂无在线用户" />
</el-card>
<el-card shadow="hover" v-else>
<el-empty description="请选择要查看的服务器" />
</el-card>
</el-col>
</el-row>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, onUnmounted } from 'vue';
import { ElMessage } from 'element-plus';
import { GetVPNServerConfigHandler, GetVPNServerOnlineListHandler,GetServerOnlineUsers,KickOutOnlineUser } from '@/api/vpn';
import { on } from 'events';
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;
protocol: number;
ip_type: number;
ipv4_address_pool: string;
ipv6_address_pool: string;
dns_server: string;
tunnel: string;
allow_user_id: any[];
encryption: string;
hash: string;
user_max_device: number;
duration_time: number;
ipv4_router: any[];
ipv6_router: any[];
}
interface OnlineUserInfo {
id: number;
user_id: number;
user_name: string;
private_ipv4: string;
private_ipv6: string;
vpn_dp_secret: string;
uuid: string;
last_update_time: number;
}
const serverList = ref<ServerConfig[]>([]);
const selectedServer = ref<ServerConfig | null>(null);
const onlineUsers = ref<OnlineUserInfo[]>([]);
const onlineServers = ref<string[]>([]);
const loading = ref(false);
let timer: number | null = null;
let timer2: number | null = null;
//
const getServerConfigs = async () => {
try {
const response = await GetVPNServerConfigHandler();
serverList.value = response.data;
} catch (error) {
ElMessage.error('获取服务器配置失败');
console.error(error);
}
};
const KickOutAllOnlineUser = async() =>{
let req = {
server_id: selectedServer.value.server_id,
type: 1, //all
session: [{
"user_id": 1,
"session": "kickout all"
}]
}
try{
let resp = await KickOutOnlineUser(req);
if (resp &&resp["code"] == 0){
ElMessage.success("踢出成功");
}else{
ElMessage.error(resp["message"] || "踢出失败");
}
}catch(error){
ElMessage.error("踢出失败");
console.error(error);
}
}
const KickOutSomeUser = async(user:OnlineUserInfo) =>{
let req = {
server_id: selectedServer.value.server_id,
sessions: [{
"user_id": user.user_id,
"session": user.uuid
}]
}
try{
let resp = await KickOutOnlineUser(req);
if (resp &&resp["code"] == 0){
ElMessage.success("踢出成功");
}else{
ElMessage.error(resp["message"] || "踢出失败");
}
}catch(error){
ElMessage.error("踢出失败");
console.error(error);
}
}
// 线
const getOnlineServers = async () => {
try {
const response = await GetVPNServerOnlineListHandler();
if (response.data && Array.isArray(response.data)) {
onlineServers.value = response.data.map((server: any) => server.server_id);
}
} catch (error) {
console.error('获取在线服务器状态失败:', error);
}
};
// 线
const getOnlineUsers = async (serverId: string) => {
if (!serverId) return;
loading.value = true;
try {
const response = await GetServerOnlineUsers(serverId);
if (response["code"] === 0) {
if(response.data){
onlineUsers.value = response.data;
}else{
onlineUsers.value = [];
}
} else {
ElMessage.error(response["message"]);
}
} catch (error) {
console.error('获取在线用户失败:', error);
ElMessage.error('获取在线用户失败');
onlineUsers.value = [];
} finally {
loading.value = false;
}
};
// 线
const refreshOnlineUsers = () => {
if (selectedServer.value) {
getOnlineUsers(selectedServer.value.server_id);
}
};
//
const selectServer = (server: ServerConfig) => {
selectedServer.value = server;
getOnlineUsers(server.server_id);
};
//
const formatTime = (timestamp: number) => {
if (!timestamp) return '-';
const date = new Date(timestamp * 1000);
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
};
//
const startOnlineStatusTimer = () => {
//
getOnlineServers();
// 2
timer = window.setInterval(() => {
getOnlineServers();
}, 10000);
timer2 = window.setInterval(() => {
if (selectedServer.value){
GetServerOnlineUsers(selectedServer.value.server_id);
}
}, 2000);
};
//
const stopOnlineStatusTimer = () => {
if (timer !== null) {
clearInterval(timer);
timer = null;
}
};
//
onMounted(() => {
getServerConfigs();
startOnlineStatusTimer();
});
//
onUnmounted(() => {
stopOnlineStatusTimer();
});
</script>
<style scoped>
.vpn-server-online-user {
padding: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: bold;
}
.server-list {
max-height: 600px;
overflow-y: auto;
}
.server-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px;
margin-bottom: 8px;
border: 1px solid #e4e7ed;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
}
.server-item:hover {
background-color: #f5f7fa;
border-color: #409eff;
}
.server-item.active {
background-color: #ecf5ff;
border-color: #409eff;
}
.server-info {
flex: 1;
}
.server-name {
font-weight: bold;
margin-bottom: 4px;
display: flex;
align-items: center;
}
.status-indicator {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: #909399;
margin-right: 8px;
transition: background-color 0.3s;
}
.status-indicator.online {
background-color: #67c23a;
box-shadow: 0 0 4px rgba(103, 194, 58, 0.5);
}
.server-ip {
font-size: 12px;
color: #909399;
}
.el-table {
margin-top: 10px;
}
</style>