refactor: 在线客户端卡片移至管理后台首页,移除 body 退出按钮

- AdminView.vue: 新增 fetchVpnStatus/handleKick,用户信息卡片下方
  插入在线客户端表格(含踢下线按钮),30s 轮询同步刷新;
  删除 body 退出登录按钮及 handleLogout(header 已有退出入口)
- VpnView.vue: 移除在线客户端卡片及 handleKick(已迁移至 AdminView)
This commit is contained in:
2026-07-10 13:25:55 +08:00
parent a170a234b2
commit f94aa3c4ac
2 changed files with 76 additions and 62 deletions
-50
View File
@@ -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(() => {
</div>
</div>
<!-- 在线客户端 -->
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white p-6 pb-4">{{ t('vpn.onlineClients') }}</h3>
<table class="w-full text-sm">
<thead>
<tr class="border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/50">
<th class="px-6 py-3 text-left font-medium text-gray-500 dark:text-gray-400">{{ t('vpn.user') }}</th>
<th class="px-6 py-3 text-left font-medium text-gray-500 dark:text-gray-400">{{ t('vpn.ipv4') }}</th>
<th class="px-6 py-3 text-left font-medium text-gray-500 dark:text-gray-400">{{ t('vpn.ipv6') }}</th>
<th class="px-6 py-3 text-left font-medium text-gray-500 dark:text-gray-400">{{ t('vpn.connectTime') }}</th>
<th class="px-6 py-3 text-left font-medium text-gray-500 dark:text-gray-400">{{ t('common.actions') }}</th>
</tr>
</thead>
<tbody>
<tr v-if="!status?.clients?.length">
<td colspan="5" class="px-6 py-6 text-center text-gray-400">{{ t('vpn.noOnlineClients') }}</td>
</tr>
<tr v-for="(c, i) in status?.clients" :key="i" class="border-b border-gray-100 dark:border-gray-700/50">
<td class="px-6 py-3 text-gray-900 dark:text-white font-medium">{{ c.username }}</td>
<td class="px-6 py-3 text-gray-700 dark:text-gray-300">{{ c.ip }}</td>
<td class="px-6 py-3 text-gray-700 dark:text-gray-300">{{ c.ip6 || '—' }}</td>
<td class="px-6 py-3 text-gray-500 dark:text-gray-400">{{ c.connected_at }}</td>
<td class="px-6 py-3">
<button
class="px-3 py-1 text-xs rounded-md font-medium text-red-700 bg-red-50 hover:bg-red-100 dark:text-red-400 dark:bg-red-900/20 transition-colors"
@click="handleKick(c.user_id, c.username)"
>
{{ t('vpn.kick') }}
</button>
</td>
</tr>
</tbody>
</table>
</div>
<!-- 静态预留 -->
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden">
<div class="flex items-center justify-between p-6 pb-4">