Files
meshgo/http/client_detail.html
2026-05-15 22:03:46 +08:00

124 lines
4.6 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>客户端详情 - meshgo</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #f0f2f5; color: #333; }
header { background: #1677ff; color: #fff; padding: 16px 24px; font-size: 18px; font-weight: 600; display: flex; align-items: center; gap: 12px; }
.back-btn { color: #fff; text-decoration: none; font-size: 14px; opacity: 0.8; }
.back-btn:hover { opacity: 1; }
.container { max-width: 1000px; margin: 24px auto; padding: 0 16px; }
.card { background: #fff; border-radius: 8px; padding: 20px; box-shadow: 0 1px 3px rgba(0,0,0,.08); margin-bottom: 16px; }
.card h3 { font-size: 15px; color: #333; margin-bottom: 16px; border-bottom: 1px solid #eee; padding-bottom: 10px; }
.info-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; }
.info-item { }
.info-item .label { font-size: 12px; color: #888; margin-bottom: 4px; }
.info-item .value { font-size: 14px; color: #333; font-weight: 500; }
.sub-list { display: flex; flex-wrap: wrap; gap: 8px; }
.sub-tag { display: inline-block; padding: 4px 12px; background: #e6f4ff; color: #1677ff; border-radius: 4px; font-size: 13px; font-family: monospace; }
.online-dot { display: inline-block; width: 8px; height: 8px; background: #52c41a; border-radius: 50%; margin-right: 6px; }
.status-online { color: #52c41a; }
.status-offline { color: #ff4d4f; }
.loading, .error, .not-found { text-align: center; padding: 48px; color: #888; }
.error { color: #ff4d4f; }
</style>
</head>
<body>
<header>
<a href="/admin/mqtt" class="back-btn">← 返回</a>
客户端详情
</header>
<div class="container">
<div id="app">
<div class="loading">加载中...</div>
</div>
</div>
<script>
const clientId = window.location.pathname.split('/').pop();
function renderClient(client, subs) {
if (!client) {
document.getElementById('app').innerHTML = '<div class="card"><div class="not-found">客户端不存在或已离线</div></div>';
return;
}
const time = new Date(client.connected_at).toLocaleString('zh-CN', { hour12: false });
const subsHtml = subs.length > 0
? subs.map(s => `<span class="sub-tag">${esc(s)}</span>`).join('')
: '<span style="color:#aaa">暂无订阅</span>';
document.getElementById('app').innerHTML = `
<div class="card">
<h3>基本信息</h3>
<div class="info-grid">
<div class="info-item">
<div class="label">状态</div>
<div class="value"><span class="online-dot"></span><span class="status-online">在线</span></div>
</div>
<div class="info-item">
<div class="label">客户端 ID</div>
<div class="value" style="font-family:monospace;font-size:13px">${esc(client.id)}</div>
</div>
<div class="info-item">
<div class="label">IP 地址</div>
<div class="value">${esc(client.remote_addr)}</div>
</div>
<div class="info-item">
<div class="label">用户名</div>
<div class="value">${esc(client.username)}</div>
</div>
<div class="info-item">
<div class="label">连接时间</div>
<div class="value">${time}</div>
</div>
<div class="info-item">
<div class="label">订阅数</div>
<div class="value">${client.subs_count}</div>
</div>
</div>
</div>
<div class="card">
<h3>订阅主题 (${subs.length})</h3>
<div class="sub-list">${subsHtml}</div>
</div>
`;
}
function esc(s) {
if (!s) return '';
return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
}
function load() {
Promise.all([
fetch('/admin/mqtt/api/client/' + encodeURIComponent(clientId)).then(r => r.json()),
fetch('/admin/mqtt/api/client/' + encodeURIComponent(clientId) + '/subs').then(r => r.json())
]).then(([clientRes, subsRes]) => {
if (clientRes.code !== 0) {
document.getElementById('app').innerHTML = '<div class="card"><div class="error">获取客户端信息失败</div></div>';
return;
}
renderClient(clientRes.data, subsRes.data || []);
}).catch(() => {
document.getElementById('app').innerHTML = '<div class="card"><div class="error">网络错误</div></div>';
});
}
load();
</script>
</body>
</html>