添加在线用户查看及踢出用户
This commit is contained in:
parent
fcb81756e2
commit
97534d399a
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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: '在线用户',
|
||||||
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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"],
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
Loading…
Reference in New Issue