From f94aa3c4ac543191ad0b9ccf429aad15379f547e Mon Sep 17 00:00:00 2001 From: kevin Date: Fri, 10 Jul 2026 13:25:55 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E5=9C=A8=E7=BA=BF=E5=AE=A2?= =?UTF-8?q?=E6=88=B7=E7=AB=AF=E5=8D=A1=E7=89=87=E7=A7=BB=E8=87=B3=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=90=8E=E5=8F=B0=E9=A6=96=E9=A1=B5=EF=BC=8C=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=20body=20=E9=80=80=E5=87=BA=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AdminView.vue: 新增 fetchVpnStatus/handleKick,用户信息卡片下方 插入在线客户端表格(含踢下线按钮),30s 轮询同步刷新; 删除 body 退出登录按钮及 handleLogout(header 已有退出入口) - VpnView.vue: 移除在线客户端卡片及 handleKick(已迁移至 AdminView) --- frontend/src/views/AdminView.vue | 88 +++++++++++++++++++++++++++----- frontend/src/views/VpnView.vue | 50 ------------------ 2 files changed, 76 insertions(+), 62 deletions(-) diff --git a/frontend/src/views/AdminView.vue b/frontend/src/views/AdminView.vue index 4ac42c3..c930316 100644 --- a/frontend/src/views/AdminView.vue +++ b/frontend/src/views/AdminView.vue @@ -19,6 +19,16 @@ const stats = ref([ const userCount = ref(null) let statsTimer: ReturnType | null = null +interface ClientInfo { + user_id: number + username: string + ip: string + ip6?: string + connected_at: string +} +const vpnClients = ref([]) +const kickError = ref('') + function formatUptime(seconds: number): string { if (seconds <= 0) return '0m' const d = Math.floor(seconds / 86400) @@ -61,11 +71,42 @@ async function fetchStats() { } catch {} } +async function fetchVpnStatus() { + try { + const res = await fetch('/api/admin/vpn/status', { + headers: { Authorization: `Bearer ${authStore.token}` }, + }) + if (!res.ok) return + const data = await res.json() + vpnClients.value = data.clients || [] + } catch {} +} + +async function handleKick(userId: number, username: string) { + if (!confirm(t('vpn.confirmKick', { username }))) return + kickError.value = '' + try { + const res = await fetch(`/api/admin/vpn/clients/${userId}`, { + method: 'DELETE', + headers: { Authorization: `Bearer ${authStore.token}` }, + }) + const data = await res.json() + if (!res.ok) throw new Error(data.error || t('vpn.kickFailed')) + await fetchVpnStatus() + } catch (e: any) { + kickError.value = e.message + } +} + onMounted(async () => { await authStore.fetchUser() fetchUserCount() fetchStats() - statsTimer = setInterval(fetchStats, 30000) + fetchVpnStatus() + statsTimer = setInterval(() => { + fetchStats() + fetchVpnStatus() + }, 30000) }) onUnmounted(() => { @@ -75,11 +116,6 @@ onUnmounted(() => { function handleStatClick(route: string) { if (route) router.push(route) } - -function handleLogout() { - authStore.logout() - router.push('/') -} diff --git a/frontend/src/views/VpnView.vue b/frontend/src/views/VpnView.vue index 22636f9..e7f786f 100644 --- a/frontend/src/views/VpnView.vue +++ b/frontend/src/views/VpnView.vue @@ -202,21 +202,6 @@ async function handleDeleteResv(id: number) { } } -async function handleKick(userId: number, username: string) { - if (!confirm(t('vpn.confirmKick', { username }))) return - try { - const res = await fetch(`/api/admin/vpn/clients/${userId}`, { - method: 'DELETE', - headers: authHeader(), - }) - const data = await res.json() - if (!res.ok) throw new Error(data.error || t('vpn.kickFailed')) - await fetchStatus() - } catch (e: any) { - error.value = e.message - } -} - function checkTunCreate(): boolean | null { if (!diag.value) return null return diag.value.tun_create.startsWith('ok') @@ -431,41 +416,6 @@ onMounted(() => { - -
-

{{ t('vpn.onlineClients') }}

- - - - - - - - - - - - - - - - - - - - - - -
{{ t('vpn.user') }}{{ t('vpn.ipv4') }}{{ t('vpn.ipv6') }}{{ t('vpn.connectTime') }}{{ t('common.actions') }}
{{ t('vpn.noOnlineClients') }}
{{ c.username }}{{ c.ip }}{{ c.ip6 || '—' }}{{ c.connected_at }} - -
-
-