| | |
| | | section { margin-bottom: 20px; background: #fff; border: 1px solid #d5dde3; } |
| | | .section-title, .device-title { display: flex; align-items: center; justify-content: space-between; gap: 12px; min-height: 44px; padding: 0 16px; background: #edf2f5; border-bottom: 1px solid #d5dde3; } |
| | | h2, h3 { margin: 0; font-size: 14px; font-weight: 600; letter-spacing: 0; } |
| | | .summary { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); } |
| | | .summary { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); } |
| | | .summary > div { min-height: 76px; padding: 15px 16px; border-right: 1px solid #e2e8ec; } |
| | | .summary > div:last-child { border-right: 0; } |
| | | .label { display: block; margin-bottom: 7px; color: #6b7780; font-size: 12px; } |
| | |
| | | .switch-label > span { margin: 0; } |
| | | button { height: 34px; padding: 0 13px; color: #fff; background: #2877a9; border: 1px solid #2877a9; border-radius: 3px; cursor: pointer; } |
| | | button:hover { background: #1f638e; } |
| | | button:disabled { cursor: not-allowed; opacity: .5; } |
| | | button.danger { color: #a03946; background: #fff; border-color: #d8a6ad; } |
| | | button.danger:hover { background: #fff3f4; } |
| | | .power-actions { display: flex; flex-wrap: wrap; gap: 8px; } |
| | | .device { margin: 16px; border: 1px solid #d5dde3; } |
| | | .device-title { background: #f7f9fa; } |
| | | table { width: 100%; border-collapse: collapse; table-layout: fixed; } |
| | |
| | | <main> |
| | | <div id="error" class="error"></div> |
| | | <section> |
| | | <div class="section-title"><h2>运行状态</h2></div> |
| | | <div class="section-title"> |
| | | <h2>运行状态</h2> |
| | | <div class="power-actions"> |
| | | <button id="power-on" type="button">一键模拟开机</button> |
| | | <button id="power-off" class="danger" type="button">一键模拟关机</button> |
| | | <button id="reset-simulator" class="danger" type="button">重置模拟器</button> |
| | | </div> |
| | | </div> |
| | | <div class="summary"> |
| | | <div><span class="label">模拟器</span><span id="runtime" class="value status">未知</span></div> |
| | | <div><span class="label">模拟器服务</span><span id="runtime" class="value status">未知</span></div> |
| | | <div><span class="label">现场设备</span><span id="device-power" class="value status">未知</span></div> |
| | | <div><span class="label">监听地址</span><span id="address" class="value">-</span></div> |
| | | <div><span class="label">检测柜</span><span id="device-count" class="value">0 台</span></div> |
| | | <div><span class="label">采集器</span><span id="collector-count" class="value">0 台</span></div> |
| | | </div> |
| | | </section> |
| | | <section> |
| | | <div class="section-title"><h2>添加检测柜</h2><button id="reset-simulator" class="danger" type="button">重置模拟器</button></div> |
| | | <div class="section-title"><h2>添加检测柜</h2></div> |
| | | <form id="add-device" class="add-form"> |
| | | <label><span>虚拟 IP</span><input id="new-ip" type="text" value="127.0.0.3" required></label> |
| | | <label><span>通道数</span><input id="new-channel-count" type="number" min="1" max="99" value="4" required></label> |
| | |
| | | const runtime = document.querySelector('#runtime'); |
| | | runtime.textContent = state.running ? '运行中' : '已停止'; |
| | | runtime.classList.toggle('online', state.running); |
| | | const devicesPoweredOn = state.devices.length + state.collectors.length > 0 |
| | | && state.devices.every(device => device.online |
| | | && device.channels.every(channel => channel.online)) |
| | | && state.collectors.every(collector => collector.online); |
| | | const devicesPoweredOff = state.devices.every(device => !device.online |
| | | && device.channels.every(channel => !channel.online && channel.workStatus === 'STOPPED')) |
| | | && state.collectors.every(collector => !collector.online); |
| | | const devicePower = document.querySelector('#device-power'); |
| | | devicePower.textContent = devicesPoweredOn ? '已开机' : devicesPoweredOff ? '已关机' : '部分开启'; |
| | | devicePower.classList.toggle('online', devicesPoweredOn); |
| | | document.querySelector('#power-on').disabled = devicesPoweredOn; |
| | | document.querySelector('#power-off').disabled = devicesPoweredOff; |
| | | document.querySelector('#address').textContent = state.listenAddress; |
| | | document.querySelector('#device-count').textContent = `${state.devices.length} 台`; |
| | | document.querySelector('#collector-count').textContent = `${state.collectors.length} 台`; |
| | |
| | | try { render(await request('/reset', { method: 'POST' })); } catch (error) { showError(error); } |
| | | }); |
| | | |
| | | document.getElementById('power-on').addEventListener('click', async () => { |
| | | try { render(await request('/power', { method: 'POST', body: JSON.stringify({ poweredOn: true }) })); } |
| | | catch (error) { showError(error); } |
| | | }); |
| | | |
| | | document.getElementById('power-off').addEventListener('click', async () => { |
| | | if (!confirm('确认关闭全部模拟检测柜和采集器?已有历史会保留,停止增长。')) return; |
| | | try { render(await request('/power', { method: 'POST', body: JSON.stringify({ poweredOn: false }) })); } |
| | | catch (error) { showError(error); } |
| | | }); |
| | | |
| | | document.querySelector('#add-device').addEventListener('submit', async event => { |
| | | event.preventDefault(); |
| | | try { |