|
|
|
|
@ -5,26 +5,27 @@ namespace CncSimulator.Admin
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 管理页面HTML生成器。
|
|
|
|
|
/// 生成总管理页面和单地址管理页面的完整HTML+CSS+JS。
|
|
|
|
|
/// 总管理页面:显示数据库地址+手动启停。
|
|
|
|
|
/// 单地址页面:设备卡片+统计+设备增减。
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class AdminHandler
|
|
|
|
|
{
|
|
|
|
|
/// <summary>生成总管理页面(网关页面)</summary>
|
|
|
|
|
/// <summary>生成总管理页面</summary>
|
|
|
|
|
public string BuildGatewayPage(SimulatorEngine engine)
|
|
|
|
|
{
|
|
|
|
|
var sb = new StringBuilder();
|
|
|
|
|
sb.AppendLine("<!DOCTYPE html>");
|
|
|
|
|
sb.AppendLine("<html lang='zh-CN'><head><meta charset='utf-8'>");
|
|
|
|
|
sb.AppendLine("<!DOCTYPE html><html lang='zh-CN'><head><meta charset='utf-8'>");
|
|
|
|
|
sb.AppendLine("<meta name='viewport' content='width=device-width, initial-scale=1'>");
|
|
|
|
|
sb.AppendLine("<title>CNC 模拟采集服务 - 总管理</title>");
|
|
|
|
|
sb.AppendLine("<style>");
|
|
|
|
|
sb.AppendLine("* { margin:0; padding:0; box-sizing:border-box; }");
|
|
|
|
|
sb.AppendLine("body { font-family:'Microsoft YaHei',sans-serif; background:#f0f2f5; color:#333; }");
|
|
|
|
|
sb.AppendLine(".header { background:#1890ff; color:#fff; padding:16px 24px; display:flex; justify-content:space-between; align-items:center; }");
|
|
|
|
|
sb.AppendLine(".header h1 { font-size:20px; font-weight:600; }");
|
|
|
|
|
sb.AppendLine(".header h1 { font-size:20px; }");
|
|
|
|
|
sb.AppendLine(".header-actions button { margin-left:8px; padding:6px 16px; border:none; border-radius:4px; cursor:pointer; font-size:14px; }");
|
|
|
|
|
sb.AppendLine(".btn-start { background:#52c41a; color:#fff; }");
|
|
|
|
|
sb.AppendLine(".btn-stop { background:#ff4d4f; color:#fff; }");
|
|
|
|
|
sb.AppendLine(".btn-blue { background:#1890ff; color:#fff; }");
|
|
|
|
|
sb.AppendLine(".container { max-width:1200px; margin:20px auto; padding:0 20px; }");
|
|
|
|
|
sb.AppendLine(".section { background:#fff; border-radius:8px; padding:20px; margin-bottom:16px; box-shadow:0 1px 4px rgba(0,0,0,0.1); }");
|
|
|
|
|
sb.AppendLine(".section h2 { font-size:16px; margin-bottom:12px; padding-bottom:8px; border-bottom:1px solid #e8e8e8; }");
|
|
|
|
|
@ -34,39 +35,64 @@ namespace CncSimulator.Admin
|
|
|
|
|
sb.AppendLine(".status-dot { display:inline-block; width:8px; height:8px; border-radius:50%; margin-right:6px; }");
|
|
|
|
|
sb.AppendLine(".status-running { background:#52c41a; }");
|
|
|
|
|
sb.AppendLine(".status-stopped { background:#d9d9d9; }");
|
|
|
|
|
sb.AppendLine(".btn-link { color:#1890ff; cursor:pointer; text-decoration:none; border:none; background:none; font-size:14px; }");
|
|
|
|
|
sb.AppendLine(".log-area { max-height:300px; overflow-y:auto; font-family:Consolas,monospace; font-size:13px; line-height:1.8; color:#666; }");
|
|
|
|
|
sb.AppendLine("button { cursor:pointer; }");
|
|
|
|
|
sb.AppendLine(".btn-sm { padding:4px 12px; border:1px solid #d9d9d9; border-radius:4px; background:#fff; font-size:13px; }");
|
|
|
|
|
sb.AppendLine(".btn-sm:hover { border-color:#1890ff; color:#1890ff; }");
|
|
|
|
|
sb.AppendLine(".btn-sm-primary { padding:4px 12px; border:1px solid #1890ff; border-radius:4px; background:#1890ff; color:#fff; font-size:13px; }");
|
|
|
|
|
sb.AppendLine(".btn-sm-danger { padding:4px 12px; border:1px solid #ff4d4f; border-radius:4px; background:#fff; color:#ff4d4f; font-size:13px; }");
|
|
|
|
|
sb.AppendLine(".interval-input { width:60px; padding:2px 6px; border:1px solid #d9d9d9; border-radius:4px; text-align:center; }");
|
|
|
|
|
sb.AppendLine("</style></head><body>");
|
|
|
|
|
sb.AppendLine("<div class='header'>");
|
|
|
|
|
sb.AppendLine(" <h1>CNC 模拟采集服务</h1>");
|
|
|
|
|
sb.AppendLine(" <div class='header-actions'>");
|
|
|
|
|
sb.AppendLine(" <button class='btn-start' onclick='startAll()'>全部启动</button>");
|
|
|
|
|
sb.AppendLine(" <button class='btn-stop' onclick='stopAll()'>全部停止</button>");
|
|
|
|
|
sb.AppendLine(" <button class='btn-blue' onclick='reloadDb()'>刷新数据库</button>");
|
|
|
|
|
sb.AppendLine(" </div>");
|
|
|
|
|
sb.AppendLine("</div>");
|
|
|
|
|
sb.AppendLine("<div class='container'>");
|
|
|
|
|
sb.AppendLine(" <div class='section'><h2>地址列表</h2>");
|
|
|
|
|
sb.AppendLine(" <table><thead><tr><th>名称</th><th>端口</th><th>状态</th><th>设备数</th><th>数据频率</th><th>请求次数</th><th>操作</th></tr></thead>");
|
|
|
|
|
sb.AppendLine(" <tbody id='addressTable'></tbody></table>");
|
|
|
|
|
sb.AppendLine(" <div class='section'><h2>数据库采集地址</h2>");
|
|
|
|
|
sb.AppendLine(" <table><thead><tr><th>ID</th><th>名称</th><th>机床数</th><th>状态</th><th>端口</th><th>频率(秒)</th><th>总零件</th><th>操作</th></tr></thead>");
|
|
|
|
|
sb.AppendLine(" <tbody id='addrTable'></tbody></table>");
|
|
|
|
|
sb.AppendLine(" </div>");
|
|
|
|
|
sb.AppendLine(" <div class='section'><h2>控制台日志</h2>");
|
|
|
|
|
sb.AppendLine(" <div class='log-area' id='logArea'>加载中...</div>");
|
|
|
|
|
sb.AppendLine(" <div class='section'><h2>运行中模拟端口</h2>");
|
|
|
|
|
sb.AppendLine(" <table><thead><tr><th>名称</th><th>端口</th><th>设备</th><th>请求次数</th><th>总零件</th><th>操作</th></tr></thead>");
|
|
|
|
|
sb.AppendLine(" <tbody id='runningTable'></tbody></table>");
|
|
|
|
|
sb.AppendLine(" </div>");
|
|
|
|
|
sb.AppendLine("</div>");
|
|
|
|
|
sb.AppendLine("<script>");
|
|
|
|
|
sb.AppendLine("var logLines=[];var maxLogs=50;");
|
|
|
|
|
sb.AppendLine("function refresh(){");
|
|
|
|
|
sb.AppendLine(" fetch('/admin/api/db-addresses').then(r=>r.json()).then(addrs=>{");
|
|
|
|
|
sb.AppendLine(" var tb=document.getElementById('addrTable');tb.innerHTML='';");
|
|
|
|
|
sb.AppendLine(" addrs.forEach(function(a){");
|
|
|
|
|
sb.AppendLine(" var st=a.isRunning?'<span class=\"status-dot status-running\"></span>运行中':'<span class=\"status-dot status-stopped\"></span>未启动';");
|
|
|
|
|
sb.AppendLine(" var portVal=a.runningPort||'';");
|
|
|
|
|
sb.AppendLine(" var h='<tr><td>'+a.dbId+'</td><td>'+a.name+'</td><td>'+a.machineCount+'台</td><td>'+st+'</td>';");
|
|
|
|
|
sb.AppendLine(" h+='<td>'+(a.isRunning?a.runningPort:'-')+'</td>';");
|
|
|
|
|
sb.AppendLine(" h+='<td>'+(a.isRunning?'<input class=\"interval-input\" id=\"int_'+a.dbId+'\" value=\"10\" disabled>':'<input class=\"interval-input\" id=\"int_'+a.dbId+'\" value=\"10\">')+'</td>';");
|
|
|
|
|
sb.AppendLine(" h+='<td id=\"parts_'+a.dbId+'\">-</td>';");
|
|
|
|
|
sb.AppendLine(" h+='<td>';");
|
|
|
|
|
sb.AppendLine(" if(a.isRunning){h+='<button class=\"btn-sm-danger\" onclick=\"stopAddr('+a.dbId+')\">停止</button> ';");
|
|
|
|
|
sb.AppendLine(" h+='<a class=\"btn-sm\" href=\"http://localhost:'+a.runningPort+'/admin\" target=\"_blank\">管理</a> ';}");
|
|
|
|
|
sb.AppendLine(" else{h+='<button class=\"btn-sm-primary\" onclick=\"startAddr('+a.dbId+')\">启动</button>'}");
|
|
|
|
|
sb.AppendLine(" h+='</td></tr>';");
|
|
|
|
|
sb.AppendLine(" tb.innerHTML+=h;");
|
|
|
|
|
sb.AppendLine(" });");
|
|
|
|
|
sb.AppendLine(" }).catch(function(){});");
|
|
|
|
|
sb.AppendLine(" fetch('/admin/api/status').then(r=>r.json()).then(data=>{");
|
|
|
|
|
sb.AppendLine(" var tb=document.getElementById('addressTable');tb.innerHTML='';");
|
|
|
|
|
sb.AppendLine(" data.forEach(function(a){");
|
|
|
|
|
sb.AppendLine(" var st=a.isRunning?'<span class=\"status-dot status-running\"></span>运行中':'<span class=\"status-dot status-stopped\"></span>已停止';");
|
|
|
|
|
sb.AppendLine(" tb.innerHTML+='<tr><td>'+a.name+'</td><td>'+a.port+'</td><td>'+st+'</td><td>'+a.onlineDevices+'/'+a.totalDevices+'台</td><td>'+a.dataChangeInterval+'秒</td><td>'+a.requestCount+'</td><td><a class=\"btn-link\" href=\"http://localhost:'+a.port+'/admin\" target=\"_blank\">管理</a></td></tr>';");
|
|
|
|
|
sb.AppendLine(" var rt=document.getElementById('runningTable');rt.innerHTML='';");
|
|
|
|
|
sb.AppendLine(" data.forEach(function(s){");
|
|
|
|
|
sb.AppendLine(" rt.innerHTML+='<tr><td>'+s.name+'</td><td>'+s.port+'</td><td>'+s.onlineDevices+'/'+s.totalDevices+'</td><td>'+s.requestCount+'</td><td>'+(s.totalParts||0)+'</td><td><a class=\"btn-sm\" href=\"http://localhost:'+s.port+'/admin\" target=\"_blank\">管理</a> <button class=\"btn-sm-danger\" onclick=\"stopAddr('+s.dbAddressId+')\">停止</button></td></tr>';");
|
|
|
|
|
sb.AppendLine(" });");
|
|
|
|
|
sb.AppendLine(" // 更新零件数");
|
|
|
|
|
sb.AppendLine(" data.forEach(function(s){ var el=document.getElementById('parts_'+s.dbAddressId); if(el) el.textContent=s.totalParts||0; });");
|
|
|
|
|
sb.AppendLine(" }).catch(function(){});");
|
|
|
|
|
sb.AppendLine("}");
|
|
|
|
|
sb.AppendLine("function addLog(msg){var now=new Date();var ts=now.toTimeString().substr(0,8);logLines.unshift(ts+' '+msg);if(logLines.length>maxLogs)logLines.length=maxLogs;document.getElementById('logArea').innerHTML=logLines.join('<br>');}");
|
|
|
|
|
sb.AppendLine("function startAll(){fetch('/admin/api/start',{method:'POST'}).then(function(){addLog('全部启动');refresh()});}");
|
|
|
|
|
sb.AppendLine("function stopAll(){fetch('/admin/api/stop',{method:'POST'}).then(function(){addLog('全部停止');refresh()});}");
|
|
|
|
|
sb.AppendLine("function startAddr(dbId){var intEl=document.getElementById('int_'+dbId);var interval=intEl?parseInt(intEl.value):10;fetch('/admin/api/start-address',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({dbAddressId:dbId,interval:interval})}).then(r=>r.json()).then(function(){refresh();});}");
|
|
|
|
|
sb.AppendLine("function stopAddr(dbId){fetch('/admin/api/stop-address',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({dbAddressId:dbId})}).then(function(){refresh();});}");
|
|
|
|
|
sb.AppendLine("function startAll(){fetch('/admin/api/start-all',{method:'POST'}).then(function(){refresh();});}");
|
|
|
|
|
sb.AppendLine("function stopAll(){fetch('/admin/api/stop-all',{method:'POST'}).then(function(){refresh();});}");
|
|
|
|
|
sb.AppendLine("function reloadDb(){fetch('/admin/api/reload-db',{method:'POST'}).then(function(){refresh();});}");
|
|
|
|
|
sb.AppendLine("setInterval(refresh,2000);refresh();");
|
|
|
|
|
sb.AppendLine("</script></body></html>");
|
|
|
|
|
return sb.ToString();
|
|
|
|
|
@ -76,15 +102,14 @@ namespace CncSimulator.Admin
|
|
|
|
|
public string BuildSingleAddressPage(SimulatorServer server)
|
|
|
|
|
{
|
|
|
|
|
var sb = new StringBuilder();
|
|
|
|
|
sb.AppendLine("<!DOCTYPE html>");
|
|
|
|
|
sb.AppendLine("<html lang='zh-CN'><head><meta charset='utf-8'>");
|
|
|
|
|
sb.AppendLine("<!DOCTYPE html><html lang='zh-CN'><head><meta charset='utf-8'>");
|
|
|
|
|
sb.AppendLine("<meta name='viewport' content='width=device-width, initial-scale=1'>");
|
|
|
|
|
sb.AppendLine("<title>" + server.Name + " - 模拟管理</title>");
|
|
|
|
|
sb.AppendLine("<style>");
|
|
|
|
|
sb.AppendLine("* { margin:0; padding:0; box-sizing:border-box; }");
|
|
|
|
|
sb.AppendLine("body { font-family:'Microsoft YaHei',sans-serif; background:#f0f2f5; color:#333; }");
|
|
|
|
|
sb.AppendLine(".header { background:#1890ff; color:#fff; padding:16px 24px; display:flex; justify-content:space-between; align-items:center; }");
|
|
|
|
|
sb.AppendLine(".header h1 { font-size:18px; font-weight:600; }");
|
|
|
|
|
sb.AppendLine(".header h1 { font-size:18px; }");
|
|
|
|
|
sb.AppendLine(".header-actions button { margin-left:8px; padding:6px 16px; border:none; border-radius:4px; cursor:pointer; font-size:14px; }");
|
|
|
|
|
sb.AppendLine(".btn-start { background:#52c41a; color:#fff; }");
|
|
|
|
|
sb.AppendLine(".btn-stop { background:#ff4d4f; color:#fff; }");
|
|
|
|
|
@ -93,26 +118,31 @@ namespace CncSimulator.Admin
|
|
|
|
|
sb.AppendLine(".section h2 { font-size:16px; margin-bottom:12px; padding-bottom:8px; border-bottom:1px solid #e8e8e8; }");
|
|
|
|
|
sb.AppendLine(".settings-row { display:flex; gap:24px; align-items:center; flex-wrap:wrap; margin-bottom:8px; }");
|
|
|
|
|
sb.AppendLine(".settings-row label { font-size:14px; color:#666; min-width:100px; }");
|
|
|
|
|
sb.AppendLine(".settings-row input[type=number] { width:80px; padding:4px 8px; border:1px solid #d9d9d9; border-radius:4px; }");
|
|
|
|
|
sb.AppendLine(".settings-row select { padding:4px 8px; border:1px solid #d9d9d9; border-radius:4px; }");
|
|
|
|
|
sb.AppendLine(".settings-row input[type=radio] { margin-right:4px; }");
|
|
|
|
|
sb.AppendLine(".device-cards { display:grid; grid-template-columns:repeat(auto-fill,minmax(320px,1fr)); gap:12px; }");
|
|
|
|
|
sb.AppendLine(".settings-row input,.settings-row select { padding:4px 8px; border:1px solid #d9d9d9; border-radius:4px; }");
|
|
|
|
|
sb.AppendLine(".btn-apply { padding:4px 12px; border:1px solid #1890ff; border-radius:4px; background:#fff; color:#1890ff; cursor:pointer; }");
|
|
|
|
|
sb.AppendLine(".stats-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(180px,1fr)); gap:12px; }");
|
|
|
|
|
sb.AppendLine(".stat-item { text-align:center; padding:12px; background:#fafafa; border-radius:4px; }");
|
|
|
|
|
sb.AppendLine(".stat-item .value { font-size:24px; font-weight:600; color:#1890ff; }");
|
|
|
|
|
sb.AppendLine(".stat-item .label { font-size:13px; color:#999; margin-top:4px; }");
|
|
|
|
|
sb.AppendLine(".device-cards { display:grid; grid-template-columns:repeat(auto-fill,minmax(300px,1fr)); gap:12px; }");
|
|
|
|
|
sb.AppendLine(".device-card { border:1px solid #e8e8e8; border-radius:6px; padding:12px; }");
|
|
|
|
|
sb.AppendLine(".device-card.offline { opacity:0.5; background:#fafafa; }");
|
|
|
|
|
sb.AppendLine(".device-card h3 { font-size:14px; margin-bottom:8px; }");
|
|
|
|
|
sb.AppendLine(".device-card h3 { font-size:14px; margin-bottom:6px; }");
|
|
|
|
|
sb.AppendLine(".device-card .info { font-size:13px; color:#666; line-height:1.8; }");
|
|
|
|
|
sb.AppendLine(".device-card .actions { margin-top:8px; display:flex; flex-wrap:wrap; gap:4px; }");
|
|
|
|
|
sb.AppendLine(".device-card .actions button { padding:2px 8px; font-size:12px; border:1px solid #d9d9d9; border-radius:3px; background:#fff; cursor:pointer; }");
|
|
|
|
|
sb.AppendLine(".device-card .actions button:hover { border-color:#1890ff; color:#1890ff; }");
|
|
|
|
|
sb.AppendLine(".device-card .actions button.btn-remove { border-color:#ff4d4f; color:#ff4d4f; }");
|
|
|
|
|
sb.AppendLine(".parts-table { width:100%; border-collapse:collapse; font-size:13px; }");
|
|
|
|
|
sb.AppendLine(".parts-table th,.parts-table td { padding:6px 8px; text-align:left; border-bottom:1px solid #f0f0f0; }");
|
|
|
|
|
sb.AppendLine(".parts-table th { background:#fafafa; color:#666; }");
|
|
|
|
|
sb.AppendLine(".json-preview { max-height:200px; overflow:auto; background:#fafafa; padding:12px; border-radius:4px; font-family:Consolas,monospace; font-size:12px; white-space:pre-wrap; word-break:break-all; }");
|
|
|
|
|
sb.AppendLine(".log-table { width:100%; border-collapse:collapse; font-size:13px; }");
|
|
|
|
|
sb.AppendLine(".log-table th,.log-table td { padding:6px 8px; text-align:left; border-bottom:1px solid #f0f0f0; }");
|
|
|
|
|
sb.AppendLine(".log-table th { background:#fafafa; color:#666; font-weight:600; }");
|
|
|
|
|
sb.AppendLine(".stats-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(180px,1fr)); gap:12px; }");
|
|
|
|
|
sb.AppendLine(".stat-item { text-align:center; padding:12px; background:#fafafa; border-radius:4px; }");
|
|
|
|
|
sb.AppendLine(".stat-item .value { font-size:24px; font-weight:600; color:#1890ff; }");
|
|
|
|
|
sb.AppendLine(".stat-item .label { font-size:13px; color:#999; margin-top:4px; }");
|
|
|
|
|
sb.AppendLine(".btn-apply { padding:4px 12px; border:1px solid #1890ff; border-radius:4px; background:#fff; color:#1890ff; cursor:pointer; }");
|
|
|
|
|
sb.AppendLine(".log-table th { background:#fafafa; color:#666; }");
|
|
|
|
|
sb.AppendLine(".add-device-row { display:flex; gap:8px; align-items:center; margin-top:12px; }");
|
|
|
|
|
sb.AppendLine(".add-device-row input { padding:4px 8px; border:1px solid #d9d9d9; border-radius:4px; font-size:13px; }");
|
|
|
|
|
sb.AppendLine(".add-device-row button { padding:4px 12px; border:1px solid #52c41a; border-radius:4px; background:#52c41a; color:#fff; font-size:13px; cursor:pointer; }");
|
|
|
|
|
sb.AppendLine("</style></head><body>");
|
|
|
|
|
sb.AppendLine("<div class='header'>");
|
|
|
|
|
sb.AppendLine(" <h1>" + server.Name + " (端口 " + server.Port + ")</h1>");
|
|
|
|
|
@ -123,6 +153,20 @@ namespace CncSimulator.Admin
|
|
|
|
|
sb.AppendLine(" </div>");
|
|
|
|
|
sb.AppendLine("</div>");
|
|
|
|
|
sb.AppendLine("<div class='container'>");
|
|
|
|
|
|
|
|
|
|
// 统计概览
|
|
|
|
|
sb.AppendLine(" <div class='section'><h2>统计概览</h2>");
|
|
|
|
|
sb.AppendLine(" <div class='stats-grid' id='statsGrid'>加载中...</div>");
|
|
|
|
|
sb.AppendLine(" </div>");
|
|
|
|
|
|
|
|
|
|
// 零件统计表(按设备+NC程序)
|
|
|
|
|
sb.AppendLine(" <div class='section'><h2>零件统计(按设备+NC程序)</h2>");
|
|
|
|
|
sb.AppendLine(" <table class='parts-table'>");
|
|
|
|
|
sb.AppendLine(" <thead><tr><th>设备编码</th><th>描述</th><th>NC程序</th><th>零件数</th><th>当前零件</th></tr></thead>");
|
|
|
|
|
sb.AppendLine(" <tbody id='partsTable'></tbody>");
|
|
|
|
|
sb.AppendLine(" </table>");
|
|
|
|
|
sb.AppendLine(" </div>");
|
|
|
|
|
|
|
|
|
|
// 全局设置
|
|
|
|
|
sb.AppendLine(" <div class='section'><h2>全局设置</h2>");
|
|
|
|
|
sb.AppendLine(" <div class='settings-row'>");
|
|
|
|
|
@ -138,40 +182,65 @@ namespace CncSimulator.Admin
|
|
|
|
|
sb.AppendLine(" <div class='settings-row'>");
|
|
|
|
|
sb.AppendLine(" <label>网络异常模拟:</label>");
|
|
|
|
|
sb.AppendLine(" <select id='networkSelect' onchange='setNetwork(this.value)'>");
|
|
|
|
|
sb.AppendLine(" <option value='normal'>正常</option>");
|
|
|
|
|
sb.AppendLine(" <option value='http500'>HTTP 500</option>");
|
|
|
|
|
sb.AppendLine(" <option value='timeout'>连接超时(60s)</option>");
|
|
|
|
|
sb.AppendLine(" <option value='refuse'>拒绝连接</option>");
|
|
|
|
|
sb.AppendLine(" <option value='empty'>返回空数组</option>");
|
|
|
|
|
sb.AppendLine(" <option value='malformed'>畸形JSON</option>");
|
|
|
|
|
sb.AppendLine(" <option value='normal'>正常</option><option value='http500'>HTTP 500</option>");
|
|
|
|
|
sb.AppendLine(" <option value='timeout'>连接超时</option><option value='refuse'>拒绝连接</option>");
|
|
|
|
|
sb.AppendLine(" <option value='empty'>返回空数组</option><option value='malformed'>畸形JSON</option>");
|
|
|
|
|
sb.AppendLine(" </select>");
|
|
|
|
|
sb.AppendLine(" </div>");
|
|
|
|
|
sb.AppendLine(" </div>");
|
|
|
|
|
// 设备状态卡片
|
|
|
|
|
sb.AppendLine(" <div class='section'><h2>设备状态卡片</h2>");
|
|
|
|
|
|
|
|
|
|
// 设备状态卡片(含添加/移除)
|
|
|
|
|
sb.AppendLine(" <div class='section'><h2>设备状态</h2>");
|
|
|
|
|
sb.AppendLine(" <div class='device-cards' id='deviceCards'>加载中...</div>");
|
|
|
|
|
sb.AppendLine(" <div class='add-device-row'>");
|
|
|
|
|
sb.AppendLine(" <input id='addDeviceCode' placeholder='设备编码(如CNC-NEW)'>");
|
|
|
|
|
sb.AppendLine(" <input id='addDeviceDesc' placeholder='设备描述'>");
|
|
|
|
|
sb.AppendLine(" <button onclick='addDevice()'>添加设备</button>");
|
|
|
|
|
sb.AppendLine(" </div>");
|
|
|
|
|
sb.AppendLine(" </div>");
|
|
|
|
|
|
|
|
|
|
// JSON预览
|
|
|
|
|
sb.AppendLine(" <div class='section'><h2>当前返回JSON预览</h2>");
|
|
|
|
|
sb.AppendLine(" <div class='section'><h2>当前返回JSON</h2>");
|
|
|
|
|
sb.AppendLine(" <button class='btn-apply' onclick='refreshJson()' style='margin-bottom:8px;'>刷新</button>");
|
|
|
|
|
sb.AppendLine(" <div class='json-preview' id='jsonPreview'>加载中...</div>");
|
|
|
|
|
sb.AppendLine(" </div>");
|
|
|
|
|
|
|
|
|
|
// 日志
|
|
|
|
|
sb.AppendLine(" <div class='section'><h2>返回数据日志(最近100条)</h2>");
|
|
|
|
|
sb.AppendLine(" <div class='section'><h2>日志</h2>");
|
|
|
|
|
sb.AppendLine(" <table class='log-table'>");
|
|
|
|
|
sb.AppendLine(" <thead><tr><th>#</th><th>时间</th><th>设备数</th><th>关键数据</th><th>耗时</th><th>操作</th></tr></thead>");
|
|
|
|
|
sb.AppendLine(" <thead><tr><th>#</th><th>时间</th><th>设备数</th><th>关键数据</th><th>耗时</th></tr></thead>");
|
|
|
|
|
sb.AppendLine(" <tbody id='logTable'></tbody>");
|
|
|
|
|
sb.AppendLine(" </table>");
|
|
|
|
|
sb.AppendLine(" </div>");
|
|
|
|
|
// 统计
|
|
|
|
|
sb.AppendLine(" <div class='section'><h2>统计</h2>");
|
|
|
|
|
sb.AppendLine(" <div class='stats-grid' id='statsGrid'>加载中...</div>");
|
|
|
|
|
sb.AppendLine(" </div>");
|
|
|
|
|
sb.AppendLine("</div>");
|
|
|
|
|
|
|
|
|
|
// JavaScript
|
|
|
|
|
sb.AppendLine("<script>");
|
|
|
|
|
sb.AppendLine("var scenarioNames={'machining':'正常加工','same_part':'同一零件','idle':'待机空闲','program_change':'换零件','manual_reset':'手动清零','power_off':'断电','power_on':'恢复开机','pause':'暂停加工'};");
|
|
|
|
|
sb.AppendLine("var scenarioNames={'machining':'正常加工','same_part':'同一零件','idle':'待机','program_change':'换零件','manual_reset':'手动清零','power_off':'断电','power_on':'恢复开机','pause':'暂停'};");
|
|
|
|
|
sb.AppendLine("var runNames={0:'待机',1:'运行',3:'加工中'};");
|
|
|
|
|
|
|
|
|
|
// 刷新统计
|
|
|
|
|
sb.AppendLine("function refreshStats(){");
|
|
|
|
|
sb.AppendLine(" fetch('/admin/api/stats').then(r=>r.json()).then(st=>{");
|
|
|
|
|
sb.AppendLine(" document.getElementById('statsGrid').innerHTML=");
|
|
|
|
|
sb.AppendLine(" '<div class=\"stat-item\"><div class=\"value\">'+st.totalDevices+'</div><div class=\"label\">总设备数</div></div>';");
|
|
|
|
|
sb.AppendLine(" var sg=document.getElementById('statsGrid');");
|
|
|
|
|
sb.AppendLine(" sg.innerHTML+='<div class=\"stat-item\"><div class=\"value\">'+st.onlineDevices+'</div><div class=\"label\">在线设备</div></div>';");
|
|
|
|
|
sb.AppendLine(" sg.innerHTML+='<div class=\"stat-item\"><div class=\"value\">'+st.totalParts+'</div><div class=\"label\">总加工零件</div></div>';");
|
|
|
|
|
// 零件统计表
|
|
|
|
|
sb.AppendLine(" var pt=document.getElementById('partsTable');pt.innerHTML='';");
|
|
|
|
|
sb.AppendLine(" var devs=st.partsByDevice;");
|
|
|
|
|
sb.AppendLine(" Object.keys(devs).forEach(function(code){");
|
|
|
|
|
sb.AppendLine(" var d=devs[code];");
|
|
|
|
|
sb.AppendLine(" var progs=d.programs||{};");
|
|
|
|
|
sb.AppendLine(" Object.keys(progs).forEach(function(prog){");
|
|
|
|
|
sb.AppendLine(" pt.innerHTML+='<tr><td>'+code+'</td><td>'+d.desc+'</td><td>'+prog+'</td><td>'+progs[prog]+'</td><td>'+(prog===d.currentProgram?d.currentPartCount:'-')+'</td></tr>';");
|
|
|
|
|
sb.AppendLine(" });");
|
|
|
|
|
sb.AppendLine(" });");
|
|
|
|
|
sb.AppendLine(" }).catch(function(){});");
|
|
|
|
|
sb.AppendLine("}");
|
|
|
|
|
|
|
|
|
|
// 刷新设备卡片
|
|
|
|
|
sb.AppendLine("function refresh(){");
|
|
|
|
|
sb.AppendLine(" fetch('/admin/api/status').then(r=>r.json()).then(data=>{");
|
|
|
|
|
sb.AppendLine(" var cards=document.getElementById('deviceCards');cards.innerHTML='';");
|
|
|
|
|
@ -181,92 +250,40 @@ namespace CncSimulator.Admin
|
|
|
|
|
sb.AppendLine(" var h='<div class=\"device-card '+cls+'\">';");
|
|
|
|
|
sb.AppendLine(" h+='<h3>'+d.deviceCode+' '+d.desc+(d.isOnline?'':' (离线)')+'</h3>';");
|
|
|
|
|
sb.AppendLine(" h+='<div class=\"info\">';");
|
|
|
|
|
sb.AppendLine(" h+='场景: '+scName+' ('+d.scenarioTick+'/'+d.scenarioDuration+')<br>';");
|
|
|
|
|
sb.AppendLine(" if(d.isOnline){");
|
|
|
|
|
sb.AppendLine(" h+='程序: '+d.programName+'<br>';");
|
|
|
|
|
sb.AppendLine(" h+='零件数: '+d.partCount+'<br>';");
|
|
|
|
|
sb.AppendLine(" h+='运行状态: '+d.runStatus+' ('+runNames[d.runStatus]+')<br>';");
|
|
|
|
|
sb.AppendLine(" h+='主轴: '+d.spindleSpeedActual+'/'+d.spindleSpeedSet+' 负载'+d.spindleLoad+'%<br>';");
|
|
|
|
|
sb.AppendLine(" h+='进给: '+d.feedSpeedActual+'/'+d.feedSpeedSet+'<br>';");
|
|
|
|
|
sb.AppendLine(" h+='加工: '+d.machiningStatus;");
|
|
|
|
|
sb.AppendLine(" } else { h+='(设备离线,不返回数据)'; }");
|
|
|
|
|
sb.AppendLine(" h+='场景:'+scName+' ('+d.scenarioTick+'/'+d.scenarioDuration+') 程序:'+d.programName+' 零件:'+d.partCount+' 状态:'+runNames[d.runStatus];");
|
|
|
|
|
sb.AppendLine(" h+='</div>';");
|
|
|
|
|
|
|
|
|
|
// 按钮使用data属性避免转义问题
|
|
|
|
|
sb.AppendLine(" h+='<div class=\"actions\" data-device=\"'+d.deviceCode+'\">';");
|
|
|
|
|
sb.AppendLine(" h+='<button data-event=\"program_change\">换零件</button>';");
|
|
|
|
|
sb.AppendLine(" h+='<button data-event=\"manual_reset\">手动清零</button>';");
|
|
|
|
|
sb.AppendLine(" h+='<button data-event=\"manual_reset\">清零</button>';");
|
|
|
|
|
sb.AppendLine(" h+='<button data-event=\"power_off\">断电</button>';");
|
|
|
|
|
sb.AppendLine(" h+='<button data-event=\"pause\">暂停</button>';");
|
|
|
|
|
sb.AppendLine(" h+='<button data-event=\"power_on\">恢复</button>';");
|
|
|
|
|
sb.AppendLine(" h+='<button data-event=\"machining\">加工</button>';");
|
|
|
|
|
sb.AppendLine(" h+='<button data-event=\"idle\">待机</button>';");
|
|
|
|
|
sb.AppendLine(" h+='<button class=\"btn-remove\" onclick=\"removeDevice(\\''+d.deviceCode+'\\')\">移除</button>';");
|
|
|
|
|
sb.AppendLine(" h+='</div></div>';");
|
|
|
|
|
sb.AppendLine(" cards.innerHTML+=h;");
|
|
|
|
|
sb.AppendLine(" });");
|
|
|
|
|
|
|
|
|
|
// 事件委托处理按钮点击
|
|
|
|
|
sb.AppendLine(" document.querySelectorAll('.device-card .actions button').forEach(function(btn){");
|
|
|
|
|
sb.AppendLine(" btn.onclick=function(){");
|
|
|
|
|
sb.AppendLine(" var dev=this.parentElement.getAttribute('data-device');");
|
|
|
|
|
sb.AppendLine(" var evt=this.getAttribute('data-event');");
|
|
|
|
|
sb.AppendLine(" triggerEvent(dev,evt);");
|
|
|
|
|
sb.AppendLine(" };");
|
|
|
|
|
sb.AppendLine(" document.querySelectorAll('.device-card .actions button[data-event]').forEach(function(btn){");
|
|
|
|
|
sb.AppendLine(" btn.onclick=function(){var dev=this.parentElement.getAttribute('data-device');var evt=this.getAttribute('data-event');triggerEvent(dev,evt);};");
|
|
|
|
|
sb.AppendLine(" });");
|
|
|
|
|
|
|
|
|
|
// 统计
|
|
|
|
|
sb.AppendLine(" var sg=document.getElementById('statsGrid');");
|
|
|
|
|
sb.AppendLine(" sg.innerHTML='<div class=\"stat-item\"><div class=\"value\">'+data.requestCount+'</div><div class=\"label\">总请求次数</div></div>';");
|
|
|
|
|
sb.AppendLine(" sg.innerHTML+='<div class=\"stat-item\"><div class=\"value\">'+data.successCount+'</div><div class=\"label\">成功次数</div></div>';");
|
|
|
|
|
sb.AppendLine(" sg.innerHTML+='<div class=\"stat-item\"><div class=\"value\">'+data.failCount+'</div><div class=\"label\">失败次数</div></div>';");
|
|
|
|
|
sb.AppendLine(" sg.innerHTML+='<div class=\"stat-item\"><div class=\"value\">'+data.onlineDevices+'/'+data.totalDevices+'</div><div class=\"label\">在线/总设备</div></div>';");
|
|
|
|
|
sb.AppendLine(" sg.innerHTML+='<div class=\"stat-item\"><div class=\"value\">'+data.uptime+'</div><div class=\"label\">运行时长</div></div>';");
|
|
|
|
|
sb.AppendLine(" sg.innerHTML+='<div class=\"stat-item\"><div class=\"value\">'+data.dataChangeInterval+'s</div><div class=\"label\">数据频率</div></div>';");
|
|
|
|
|
sb.AppendLine(" }).catch(function(){});");
|
|
|
|
|
sb.AppendLine("}");
|
|
|
|
|
|
|
|
|
|
sb.AppendLine("function refreshJson(){");
|
|
|
|
|
sb.AppendLine(" fetch('/data').then(r=>r.text()).then(json=>{");
|
|
|
|
|
sb.AppendLine(" try{document.getElementById('jsonPreview').textContent=JSON.stringify(JSON.parse(json),null,2);}");
|
|
|
|
|
sb.AppendLine(" catch(e){document.getElementById('jsonPreview').textContent=json;}");
|
|
|
|
|
sb.AppendLine(" }).catch(function(e){document.getElementById('jsonPreview').textContent='请求失败: '+e.message;});");
|
|
|
|
|
sb.AppendLine("}");
|
|
|
|
|
sb.AppendLine("function refreshJson(){fetch('/data').then(r=>r.text()).then(json=>{try{document.getElementById('jsonPreview').textContent=JSON.stringify(JSON.parse(json),null,2);}catch(e){document.getElementById('jsonPreview').textContent=json;}}).catch(function(e){document.getElementById('jsonPreview').textContent='请求失败:'+e.message;});}");
|
|
|
|
|
|
|
|
|
|
sb.AppendLine("var allLogs=[];");
|
|
|
|
|
sb.AppendLine("function refreshLogs(){");
|
|
|
|
|
sb.AppendLine(" fetch('/admin/api/logs').then(r=>r.json()).then(logs=>{");
|
|
|
|
|
sb.AppendLine(" allLogs=logs;var tb=document.getElementById('logTable');tb.innerHTML='';");
|
|
|
|
|
sb.AppendLine(" logs.forEach(function(l){");
|
|
|
|
|
sb.AppendLine(" var tr=document.createElement('tr');");
|
|
|
|
|
sb.AppendLine(" tr.innerHTML='<td>'+l.index+'</td><td>'+l.timestamp+'</td><td>'+l.deviceCount+'</td><td>'+l.keyData+'</td><td>'+l.duration+'ms</td><td></td>';");
|
|
|
|
|
sb.AppendLine(" var btn=document.createElement('button');");
|
|
|
|
|
sb.AppendLine(" btn.textContent='完整JSON';btn.style.cssText='font-size:12px;padding:2px 6px;cursor:pointer;';");
|
|
|
|
|
sb.AppendLine(" btn.onclick=(function(idx){return function(){showJson(idx);};})(l.index);");
|
|
|
|
|
sb.AppendLine(" tr.cells[5].appendChild(btn);");
|
|
|
|
|
sb.AppendLine(" tb.appendChild(tr);");
|
|
|
|
|
sb.AppendLine(" });");
|
|
|
|
|
sb.AppendLine(" }).catch(function(){});");
|
|
|
|
|
sb.AppendLine("}");
|
|
|
|
|
|
|
|
|
|
sb.AppendLine("function showJson(idx){");
|
|
|
|
|
sb.AppendLine(" var l=allLogs.find(function(x){return x.index===idx;});");
|
|
|
|
|
sb.AppendLine(" if(l){try{document.getElementById('jsonPreview').textContent=JSON.stringify(JSON.parse(l.fullJson),null,2);}catch(e){document.getElementById('jsonPreview').textContent=l.fullJson;}}");
|
|
|
|
|
sb.AppendLine("}");
|
|
|
|
|
sb.AppendLine("function refreshLogs(){fetch('/admin/api/logs').then(r=>r.json()).then(logs=>{var tb=document.getElementById('logTable');tb.innerHTML='';logs.forEach(function(l){tb.innerHTML+='<tr><td>'+l.index+'</td><td>'+l.timestamp+'</td><td>'+l.deviceCount+'</td><td>'+l.keyData+'</td><td>'+l.duration+'ms</td></tr>';});}).catch(function(){});}");
|
|
|
|
|
|
|
|
|
|
sb.AppendLine("function triggerEvent(dev,evt){fetch('/admin/api/event',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({deviceId:dev,eventType:evt})}).then(function(){setTimeout(function(){refresh();refreshStats();},500);});}");
|
|
|
|
|
sb.AppendLine("function addDevice(){var code=document.getElementById('addDeviceCode').value;var desc=document.getElementById('addDeviceDesc').value;if(!code)return;fetch('/admin/api/add-device',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({deviceCode:code,desc:desc})}).then(function(){refresh();refreshStats();});}");
|
|
|
|
|
sb.AppendLine("function removeDevice(code){if(!confirm('确定移除设备'+code+'?'))return;fetch('/admin/api/remove-device',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({deviceCode:code})}).then(function(){refresh();refreshStats();});}");
|
|
|
|
|
sb.AppendLine("function startSim(){fetch('/admin/api/start',{method:'POST'}).then(function(){refresh();});}");
|
|
|
|
|
sb.AppendLine("function stopSim(){fetch('/admin/api/stop',{method:'POST'}).then(function(){refresh();});}");
|
|
|
|
|
sb.AppendLine("function setInterval2(){var v=document.getElementById('intervalInput').value;fetch('/admin/api/interval',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({value:parseInt(v)})}).then(function(){refresh();});}");
|
|
|
|
|
sb.AppendLine("function setInterval2(){var v=document.getElementById('intervalInput').value;fetch('/admin/api/interval',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({value:parseInt(v)})}).then(function(){});}");
|
|
|
|
|
sb.AppendLine("function setNetwork(type){fetch('/admin/api/network',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({type:type})}).then(function(){});}");
|
|
|
|
|
sb.AppendLine("function triggerEvent(deviceId,eventType){fetch('/admin/api/event',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({deviceId:deviceId,eventType:eventType})}).then(function(){setTimeout(refresh,500);});}");
|
|
|
|
|
|
|
|
|
|
sb.AppendLine("document.querySelectorAll('input[name=mode]').forEach(function(r){");
|
|
|
|
|
sb.AppendLine(" r.addEventListener('change',function(){");
|
|
|
|
|
sb.AppendLine(" fetch('/admin/api/mode',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({mode:this.value})}).then(function(){});");
|
|
|
|
|
sb.AppendLine(" });");
|
|
|
|
|
sb.AppendLine("});");
|
|
|
|
|
sb.AppendLine("document.querySelectorAll('input[name=mode]').forEach(function(r){r.addEventListener('change',function(){fetch('/admin/api/mode',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({mode:this.value})}).then(function(){});});});");
|
|
|
|
|
|
|
|
|
|
sb.AppendLine("setInterval(function(){refresh();refreshJson();refreshLogs();},2000);");
|
|
|
|
|
sb.AppendLine("refresh();refreshJson();refreshLogs();");
|
|
|
|
|
sb.AppendLine("setInterval(function(){refresh();refreshStats();refreshLogs();refreshJson();},2000);");
|
|
|
|
|
sb.AppendLine("refresh();refreshStats();refreshLogs();refreshJson();");
|
|
|
|
|
sb.AppendLine("</script></body></html>");
|
|
|
|
|
return sb.ToString();
|
|
|
|
|
}
|
|
|
|
|
|