优化Web管理页状态和参数说明

This commit is contained in:
cxh 2026-05-09 12:51:17 +08:00
parent a6ee43d6af
commit 5f88b2198e

View File

@ -324,8 +324,13 @@ const char* index_html()
.warn { color:#9a3412; }
.ok { color:#166534; }
.muted { color:#667785; }
.help { margin-top:4px; font-size:12px; line-height:1.35; color:#667785; }
.status-grid { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:10px; margin-bottom:12px; }
.status-box { border:1px solid #e1e7ed; border-radius:6px; padding:10px; background:#fbfcfd; }
.status-box strong { display:block; margin-bottom:4px; color:#243746; }
.url-cell { max-width:360px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
#toast { min-height:24px; margin-left:6px; }
@media (max-width: 860px) { .grid, .grid.two { grid-template-columns:1fr; } table { display:block; overflow-x:auto; } }
@media (max-width: 860px) { .grid, .grid.two, .status-grid { grid-template-columns:1fr; } table { display:block; overflow-x:auto; } }
</style>
</head>
<body>
@ -419,6 +424,35 @@ async function api(path, options) {
function setToast(text, cls='muted') { const t=$('toast'); t.className=cls; t.textContent=text; }
function helpFor(id, text) {
const el = $(id);
if (!el || !el.parentElement || el.parentElement.querySelector('.help')) return;
const div = document.createElement('div');
div.className = 'help';
div.textContent = text;
el.parentElement.appendChild(div);
}
function installHelp() {
helpFor('srs_root', 'SRS安装根目录 srs/binconfhtml ');
helpFor('record_config', 'video服务读取这个SRS录像配置来解析录像目录HTTP端口和切片时长');
helpFor('public_interface', ' enP2p33s0IP会回退到127.0.0.1');
helpFor('stream_app', 'RTMP路径里的app名 camera rtmp://host/app/stream。');
helpFor('live_enabled', ' live SRS ');
helpFor('record_enabled', ' record SRS SRS写录像');
helpFor('record_path', 'SRS DVR保存mp4切片的根目录');
helpFor('dvr_duration', '');
helpFor('retention_days', 'video服务扫描清理');
helpFor('usage_threshold', '使0.990%');
helpFor('live_rtmp_port', 'video服务把实时流推到这个RTMP端口');
helpFor('live_http_api_port', 'SRS WHEP/WebRTC播放接口端口MQTT返回的实时播放URL使用它');
helpFor('live_http_server_port', 'SRS自带静态页面和HTTP-FLV调试入口');
helpFor('live_rtc_port', 'WebRTC UDP媒体端口');
helpFor('record_rtmp_port', 'video服务把录像流推到这个RTMP端口');
helpFor('record_http_api_port', 'record实例的SRS HTTP API端口');
helpFor('record_http_server_port', 'HTTP访问端口URL会走这个HTTP server');
}
function fillConfig() {
const m = config.mqtt_server || {};
const s = config.srs || {};
@ -497,12 +531,42 @@ async function loadConfig() { config = await api('/api/config'); fillConfig(); }
async function loadStatus() {
const st = await api('/api/status');
const disk = st.disk || {};
$('status').innerHTML = renderStatus(st, disk);
return;
$('status').innerHTML = `
<div>IP<span class="pill">${st.public_ip || 'unknown'}</span></div>
<div>${disk.ok ? `<span class="ok">${disk.used_percent.toFixed(1)}% used</span>` : `<span class="warn">${disk.error || 'unavailable'}</span>`}</div>
<div>${(st.services||[]).map(s => `${s.name}: <span class="${s.active==='active'?'ok':'warn'}">${s.active}</span>`).join(' | ')}</div>
<div>${(st.channels||[]).map(c => `${c.name} ${c.device_exists?'':'()'} ${c.running?'':''}`).join('')}</div>`;
}
function escapeHtml(v) {
return String(v ?? '').replace(/[&<>"']/g, ch => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[ch]));
}
function renderStatus(st, disk) {
const services = (st.services || []).map(s => `${escapeHtml(s.name)}: <span class="${s.active==='active'?'ok':'warn'}">${escapeHtml(s.active)}</span>`).join('<br>');
const diskText = disk.ok ? `<span class="ok">${Number(disk.used_percent || 0).toFixed(1)}% used</span><div class="muted">${escapeHtml(disk.path)}</div>` : `<span class="warn">${escapeHtml(disk.error || 'unavailable')}</span>`;
const channelRows = (st.channels || []).map(c => {
const running = c.running ? '<span class="ok">运行</span>' : '<span class="warn">停止</span>';
const enabled = c.enabled ? '<span class="ok">启用</span>' : '<span class="muted">禁用</span>';
const exists = c.device_exists ? '<span class="ok">存在</span>' : '<span class="warn">不存在</span>';
const reason = c.reason || (c.enabled ? '' : '');
const url = c.url ? `<span title="${escapeHtml(c.url)}">${escapeHtml(c.url)}</span>` : '<span class="muted">无</span>';
return `<tr><td>${escapeHtml(c.name)}</td><td>${escapeHtml(c.device)}</td><td>${enabled}</td><td>${exists}</td><td>${running}</td><td>${escapeHtml(reason)}</td><td class="url-cell">${url}</td></tr>`;
}).join('');
return `
<div class="status-grid">
<div class="status-box"><strong>对外IP</strong><span class="pill">${escapeHtml(st.public_ip || 'unknown')}</span></div>
<div class="status-box"><strong>录像磁盘</strong>${diskText}</div>
<div class="status-box"><strong>服务</strong>${services || '<span class="muted">无</span>'}</div>
</div>
<table class="status-table">
<thead><tr><th></th><th></th><th></th><th></th><th></th><th></th><th></th></tr></thead>
<tbody>${channelRows || '<tr><td colspan="7" class="muted">无通道</td></tr>'}</tbody>
</table>`;
}
async function saveConfig() {
try {
collectConfig();
@ -510,7 +574,7 @@ async function saveConfig() {
setToast(res.message || '', res.ok ? 'ok' : 'warn');
} catch (e) { setToast(e.message, 'warn'); }
}
async function loadAll() { try { await loadConfig(); await loadStatus(); setToast(''); } catch(e) { setToast(e.message, 'warn'); } }
async function loadAll() { try { await loadConfig(); installHelp(); await loadStatus(); setToast(''); } catch(e) { setToast(e.message, 'warn'); } }
loadAll();
</script>
</body>