- auth.py: 新增 privileged_required 装饰器 (admin+manager),admin_required 仅限用户管理 - 路由权限: fixture/logs/device_logs/test_data 的 admin 检查改为 admin+manager - 前端: 导航栏/删除按钮/配置按钮扩展为 admin+manager 可见 - 用户管理: 角色下拉增加 manager 选项,仍仅 admin 可访问 - 新增 /change-password 路由+模板,所有登录用户可自行修改密码 - edc_server models.py: role COMMENT 更新 + ALTER TABLE 迁移
88 lines
2.9 KiB
JavaScript
88 lines
2.9 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' || USER_ROLE === 'manager' ? `<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);
|