整理文件

This commit is contained in:
CN-JS-HuiBai
2026-04-07 17:53:03 +08:00
parent 5b775622e1
commit f83ab6745c
401 changed files with 70 additions and 41568 deletions

View File

@@ -0,0 +1,352 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>在线设备与在线 IP</title>
<style>
:root {
--bg: #f3f0e8;
--card: rgba(255, 252, 246, 0.94);
--line: #d8ccb6;
--text: #2f261a;
--muted: #786b58;
--accent: #1f7a5a;
--accent-soft: #d9efe5;
--warning: #8f4c2d;
--shadow: 0 18px 50px rgba(77, 56, 27, 0.12);
}
* {
box-sizing: border-box;
}
body {
margin: 0;
min-height: 100vh;
font-family: Georgia, "Times New Roman", serif;
color: var(--text);
background:
radial-gradient(circle at top left, rgba(255, 255, 255, 0.75), transparent 34%),
linear-gradient(135deg, #efe4d0 0%, #f9f5ee 45%, #e6dccb 100%);
}
.wrap {
max-width: 1120px;
margin: 0 auto;
padding: 32px 20px 48px;
}
.hero {
display: grid;
gap: 18px;
margin-bottom: 24px;
}
.eyebrow {
letter-spacing: 0.12em;
text-transform: uppercase;
font-size: 12px;
color: var(--muted);
}
h1 {
margin: 0;
font-size: clamp(30px, 5vw, 54px);
line-height: 1;
}
.subtitle {
max-width: 720px;
font-size: 16px;
line-height: 1.7;
color: var(--muted);
}
.summary {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 16px;
margin-bottom: 24px;
}
.card {
background: var(--card);
border: 1px solid rgba(216, 204, 182, 0.8);
border-radius: 24px;
box-shadow: var(--shadow);
backdrop-filter: blur(10px);
}
.stat {
padding: 22px;
}
.stat-label {
font-size: 13px;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.08em;
}
.stat-value {
margin-top: 12px;
font-size: 36px;
font-weight: 700;
}
.stat-note {
margin-top: 10px;
font-size: 14px;
color: var(--muted);
}
.grid {
display: grid;
grid-template-columns: 1.2fr 0.8fr;
gap: 18px;
}
.panel {
padding: 22px;
}
.panel h2 {
margin: 0 0 16px;
font-size: 24px;
}
.panel-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 16px;
}
.badge {
padding: 8px 12px;
border-radius: 999px;
background: var(--accent-soft);
color: var(--accent);
font-size: 13px;
white-space: nowrap;
}
.device-list,
.session-list {
display: grid;
gap: 12px;
}
.row {
padding: 16px;
border-radius: 18px;
background: rgba(255, 255, 255, 0.65);
border: 1px solid rgba(216, 204, 182, 0.7);
}
.row-top {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
}
.row-title {
font-size: 17px;
font-weight: 700;
}
.row-meta {
margin-top: 8px;
color: var(--muted);
font-size: 14px;
line-height: 1.6;
word-break: break-all;
}
.empty {
padding: 28px 20px;
border: 1px dashed var(--line);
border-radius: 18px;
color: var(--muted);
text-align: center;
background: rgba(255, 255, 255, 0.48);
}
.footer-note {
margin-top: 18px;
color: var(--warning);
font-size: 14px;
line-height: 1.7;
}
@media (max-width: 860px) {
.grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="wrap">
<section class="hero">
<div class="eyebrow">Xboard Plugin Dashboard</div>
<h1>在线设备与在线 IP</h1>
<div class="subtitle">
当前页面由 User Online Devices 插件生成,会自动刷新并显示该账号最近上报到 Xboard 的在线 IP 列表。
</div>
</section>
<section class="summary">
<article class="card stat">
<div class="stat-label">账号</div>
<div class="stat-value" id="user-email">{{ $payload['user']['email'] }}</div>
<div class="stat-note">用户 ID: <span id="user-id">{{ $payload['user']['id'] }}</span></div>
</article>
<article class="card stat">
<div class="stat-label">在线设备数</div>
<div class="stat-value" id="online-count">{{ $payload['user']['online_count'] }}</div>
<div class="stat-note">按实时在线 IP 去重统计</div>
</article>
<article class="card stat">
<div class="stat-label">最后活跃</div>
<div class="stat-value" id="last-online">{{ $payload['user']['last_online_at'] ? date('Y-m-d H:i:s', $payload['user']['last_online_at']) : '暂无记录' }}</div>
<div class="stat-note">数据来源于 Xboard 设备状态服务</div>
</article>
</section>
<section class="grid">
<article class="card panel">
<div class="panel-head">
<h2>在线 IP 列表</h2>
<div class="badge"> {{ $refreshInterval }} 秒刷新</div>
</div>
<div class="device-list" id="device-list"></div>
<div class="footer-note">
说明:这里展示的是节点最近上报的在线 IPXboard 本体按 IP 去重,所以“在线设备”本质上是“当前唯一在线 IP 数”。
</div>
</article>
<article class="card panel">
<div class="panel-head">
<h2>登录会话</h2>
<div class="badge" id="session-badge">{{ $payload['meta']['show_active_sessions'] ? '已开启' : '已关闭' }}</div>
</div>
<div class="session-list" id="session-list"></div>
</article>
</section>
</div>
<script>
const snapshotUrl = @json($snapshotUrl);
const refreshInterval = {{ (int) $refreshInterval }};
const initialPayload = @json($payload);
function formatTime(timestamp) {
if (!timestamp) return '暂无记录';
const date = new Date(timestamp * 1000);
const pad = (value) => String(value).padStart(2, '0');
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
}
function createEmpty(message) {
const div = document.createElement('div');
div.className = 'empty';
div.textContent = message;
return div;
}
function renderDevices(devices) {
const root = document.getElementById('device-list');
root.innerHTML = '';
if (!devices.length) {
root.appendChild(createEmpty('当前没有检测到在线设备。'));
return;
}
devices.forEach((device) => {
const row = document.createElement('div');
row.className = 'row';
row.innerHTML = `
<div class="row-top">
<div class="row-title">${device.name}</div>
<div class="badge">在线</div>
</div>
<div class="row-meta">IP: ${device.ip}</div>
`;
root.appendChild(row);
});
}
function renderSessions(sessions, enabled) {
const root = document.getElementById('session-list');
root.innerHTML = '';
if (!enabled) {
root.appendChild(createEmpty('管理员已关闭登录会话展示。'));
return;
}
if (!sessions.length) {
root.appendChild(createEmpty('当前没有可展示的登录会话。'));
return;
}
sessions.forEach((session) => {
const row = document.createElement('div');
row.className = 'row';
row.innerHTML = `
<div class="row-top">
<div class="row-title">${session.device}</div>
<div class="badge">#${session.id}</div>
</div>
<div class="row-meta">
最近使用: ${formatTime(session.last_used_at)}<br>
创建时间: ${formatTime(session.created_at)}<br>
过期时间: ${formatTime(session.expires_at)}
</div>
`;
root.appendChild(row);
});
}
function render(payload) {
document.getElementById('user-email').textContent = payload.user.email;
document.getElementById('user-id').textContent = payload.user.id;
document.getElementById('online-count').textContent = payload.user.online_count;
document.getElementById('last-online').textContent = formatTime(payload.user.last_online_at);
document.getElementById('session-badge').textContent = payload.meta.show_active_sessions ? '已开启' : '已关闭';
renderDevices(payload.online_devices || []);
renderSessions(payload.active_sessions || [], payload.meta.show_active_sessions);
}
async function refresh() {
try {
const response = await fetch(snapshotUrl, {
method: 'GET',
credentials: 'same-origin',
headers: {
'Accept': 'application/json'
}
});
if (!response.ok) return;
const result = await response.json();
if (result && result.data) {
render(result.data);
}
} catch (error) {
console.error('Failed to refresh online devices snapshot.', error);
}
}
render(initialPayload);
window.setInterval(refresh, refreshInterval * 1000);
</script>
</body>
</html>