- 新增 /device-logs 设备事件日志管理页 (admin 权限) - 支持按设备序列号/事件类型筛选查询 - 支持 admin 按条件删除日志 - 不同事件类型彩色标识 (在线=绿, 离线=红, 通信不良=橙) - 新增 /api/devices/<id>/status 设备状态 API - 设备列表页:每 5s 异步刷新所有设备在线状态 - 测试操作页:顶部显示设备状态,每 5s 异步刷新 - dnt_info state 支持三态显示 (在线/离线/通信不良) - 导航栏增加「设备日志」入口 (admin only)
88 lines
2.8 KiB
JavaScript
88 lines
2.8 KiB
JavaScript
// 设备列表页
|
|
|
|
async function loadDevices() {
|
|
const resp = await fetch("/api/devices");
|
|
const devices = await resp.json();
|
|
renderTable(devices);
|
|
}
|
|
|
|
function renderTable(devices) {
|
|
const tbody = document.querySelector("#device-table tbody");
|
|
tbody.innerHTML = devices.map(d => {
|
|
const stateLabel = getStateLabel(d.state);
|
|
const stateClass = getStateClass(d.state);
|
|
return `
|
|
<tr>
|
|
<td>${d.serial}</td>
|
|
<td class="editable-name" onclick="editName(${d.id}, '${esc(d.name)}', this)">
|
|
${d.name || '(点击编辑)'}
|
|
</td>
|
|
<td>${d.ip || '-'}</td>
|
|
<td class="${stateClass} status-cell" data-device-id="${d.id}">
|
|
${stateLabel}
|
|
</td>
|
|
<td>${d.version || '-'}</td>
|
|
<td>${d.last_login || '-'}</td>
|
|
<td>
|
|
<button class="btn-test" onclick="location.href='/test/${d.id}'">测试</button>
|
|
${USER_ROLE === 'admin' ? `<button class="btn-config" onclick="location.href='/fixture/${d.id}'">配置</button>` : ''}
|
|
</td>
|
|
</tr>`;
|
|
}).join("");
|
|
}
|
|
|
|
function getStateLabel(state) {
|
|
return {0: '离线', 1: '在线', 2: '通信不良'}[state] || '未知';
|
|
}
|
|
|
|
function getStateClass(state) {
|
|
return {0: 'status-offline', 1: 'status-online', 2: 'status-poor'}[state] || '';
|
|
}
|
|
|
|
// 异步刷新所有设备的在线状态
|
|
async function refreshDeviceStatuses() {
|
|
const cells = document.querySelectorAll(".status-cell");
|
|
for (const cell of cells) {
|
|
const deviceId = cell.dataset.deviceId;
|
|
if (!deviceId) continue;
|
|
try {
|
|
const resp = await fetch(`/api/devices/${deviceId}/status`);
|
|
const data = await resp.json();
|
|
if (data.ok) {
|
|
cell.textContent = data.state_name;
|
|
cell.className = getStateClass(data.state) + " status-cell";
|
|
cell.dataset.deviceId = deviceId;
|
|
}
|
|
} catch (e) {
|
|
// 网络错误静默跳过
|
|
}
|
|
}
|
|
}
|
|
|
|
function esc(s) { return s.replace(/'/g, "\\'").replace(/"/g, """); }
|
|
|
|
async function editName(id, currentName, td) {
|
|
const input = document.createElement("input");
|
|
input.value = currentName;
|
|
td.innerHTML = "";
|
|
td.appendChild(input);
|
|
input.focus();
|
|
|
|
async function save() {
|
|
const name = input.value.trim();
|
|
td.textContent = name || "(点击编辑)";
|
|
await fetch(`/api/devices/${id}/name`, {
|
|
method: "PUT",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ name }),
|
|
});
|
|
}
|
|
|
|
input.addEventListener("blur", save);
|
|
input.addEventListener("keydown", e => { if (e.key === "Enter") save(); });
|
|
}
|
|
|
|
loadDevices();
|
|
// 每 5 秒异步刷新设备在线状态
|
|
setInterval(refreshDeviceStatuses, 5000);
|