137 lines
4.8 KiB
HTML
137 lines
4.8 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
|
<meta http-equiv="Pragma" content="no-cache">
|
|
<meta http-equiv="Expires" content="0">
|
|
<title>RTU Web Manager</title>
|
|
<link rel="stylesheet" href="css/style.css">
|
|
</head>
|
|
<body>
|
|
|
|
<!-- Mobile Nav Toggle -->
|
|
<button id="nav-toggle" onclick="toggleNav()">
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 12h18M3 6h18M3 18h18"/></svg>
|
|
</button>
|
|
|
|
<nav id="nav-sidebar">
|
|
<div class="nav-header">
|
|
<div class="nav-logo">
|
|
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="var(--accent)" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/></svg>
|
|
<span id="nav-title">RTU Manager</span>
|
|
</div>
|
|
<div class="nav-status">
|
|
<span class="nav-status-dot disconnected" id="nav-status-dot"></span>
|
|
<span id="nav-status-text">Disconnected</span>
|
|
</div>
|
|
</div>
|
|
<div id="nav-links"></div>
|
|
|
|
<div class="nav-lang">
|
|
<select class="lang-select" id="lang-select" onchange="I18n.setLang(this.value)">
|
|
<option value="zh">中文</option>
|
|
<option value="en">English</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="nav-footer">
|
|
<span id="nav-version">RTU Web v3.0</span>
|
|
<span id="nav-time"></span>
|
|
</div>
|
|
</nav>
|
|
|
|
<div id="main-content">
|
|
<div id="page-header">
|
|
<span id="page-title">Dashboard</span>
|
|
<span id="page-subtitle"></span>
|
|
</div>
|
|
<div id="page-container"></div>
|
|
</div>
|
|
|
|
<div class="toast-container" id="toast-container"></div>
|
|
|
|
<script src="js/i18n.js"></script>
|
|
<script src="js/ws-client.js"></script>
|
|
<script src="js/signal-store.js"></script>
|
|
<script src="js/ui.js"></script>
|
|
<script src="js/router.js"></script>
|
|
<script src="js/pages/login.js"></script>
|
|
<script src="js/pages/dashboard.js"></script>
|
|
<script src="js/pages/signals.js"></script>
|
|
<script src="js/pages/param.js"></script>
|
|
<script src="js/pages/datacenter.js"></script>
|
|
<script src="js/pages/plc_debug.js"></script>
|
|
<script src="js/pages/plc_config.js"></script>
|
|
<script src="js/pages/monitor.js"></script>
|
|
<script src="js/pages/terminal.js"></script>
|
|
|
|
<script>
|
|
(function() {
|
|
I18n.init();
|
|
SignalStore.restore();
|
|
document.getElementById('lang-select').value = I18n.current;
|
|
document.getElementById('nav-version').textContent = I18n.t('app.version');
|
|
document.getElementById('nav-title').textContent = I18n.t('app.title');
|
|
buildNav();
|
|
|
|
WsClient.onMsg(function(data, binary) {
|
|
if (binary) {
|
|
var text = new TextDecoder().decode(binary);
|
|
/* 命令输出走 terminal 的 termOnBinary 处理 */
|
|
if (typeof termOnBinary === 'function') { termOnBinary(text); }
|
|
MonitorRing.push('↓', '[binary ' + binary.byteLength + 'B]');
|
|
return;
|
|
}
|
|
if (!data || typeof data !== 'object') return;
|
|
MonitorRing.push('↓', JSON.stringify(data).substring(0, 200));
|
|
|
|
if (data.out || data.in || data.yk || data.ao || data.param) {
|
|
SignalStore.update(data);
|
|
var sigPages = ['#out','#in','#yk','#ao','#param','#datacenter','#plc_debug'];
|
|
if (sigPages.indexOf(Router.currentHash) >= 0) Router.dispatch(Router.currentHash);
|
|
}
|
|
/* ---- 命令列表:渲染到终端面板 ---- */
|
|
if (data.type === 'cmd_list' && data.cmds) {
|
|
if (typeof renderCmdPanels === 'function') { renderCmdPanels(data.cmds); }
|
|
}
|
|
if (data.type === 'async_output') {
|
|
toast('[' + (data.module||'async') + '] ' + (data.msg||data.data||''), data.success?'success':'error');
|
|
}
|
|
/* ---- 文件请求响应 ---- */
|
|
if (data.type === 'file_content' || data.type === 'file_saved') {
|
|
if (typeof PlcConfigPage !== 'undefined' && PlcConfigPage.onData) {
|
|
PlcConfigPage.onData(data);
|
|
}
|
|
}
|
|
if (data.type === 'cmd_rsp') {
|
|
var out = document.getElementById('term-out');
|
|
if (out) { out.textContent += (typeof data.data === 'string' ? data.data : JSON.stringify(data.data)) + '\n'; out.scrollTop = out.scrollHeight; }
|
|
}
|
|
});
|
|
|
|
var _orig = WsClient.send.bind(WsClient);
|
|
WsClient.send = function(obj) { MonitorRing.push('↑', JSON.stringify(obj).substring(0,200)); _orig(obj); };
|
|
|
|
WsClient.connect('ws://' + location.hostname + ':8080/ws');
|
|
|
|
window.addEventListener('hashchange', function() {
|
|
Router.dispatch(window.location.hash);
|
|
});
|
|
Router.dispatch(window.location.hash);
|
|
|
|
document.addEventListener('click', function(e) {
|
|
var link = e.target.closest('#nav-links a');
|
|
if (link) { e.preventDefault(); window.location.hash = link.dataset.hash; if (window.innerWidth < 768) toggleNav(); }
|
|
});
|
|
|
|
setInterval(function() {
|
|
var el = document.getElementById('nav-time');
|
|
if (el) el.textContent = new Date().toLocaleTimeString();
|
|
}, 1000);
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|