147 lines
5.4 KiB
HTML
147 lines
5.4 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}设备日志 - EDC 工装管理系统{% endblock %}
|
|
|
|
{% block content %}
|
|
<h2>设备事件日志</h2>
|
|
|
|
<div class="search-bar">
|
|
<label>设备序列号:<input type="text" id="search-serial" placeholder="筛选设备..."></label>
|
|
<label>事件类型:
|
|
<select id="search-event">
|
|
<option value="">全部</option>
|
|
<option value="login">登录</option>
|
|
<option value="online">在线</option>
|
|
<option value="offline">离线</option>
|
|
<option value="poor">通信不良</option>
|
|
<option value="tcp_connect">TCP连接</option>
|
|
<option value="tcp_disconnect">TCP断开</option>
|
|
</select>
|
|
</label>
|
|
<button onclick="searchLogs(1)" class="btn-search">查询</button>
|
|
{% if current_user.role == 'admin' %}
|
|
<button onclick="confirmDeleteLogs()" class="btn-delete">🗑 删除</button>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>设备序列号</th>
|
|
<th>设备IP</th>
|
|
<th>事件类型</th>
|
|
<th>事件内容</th>
|
|
<th>时间</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="log-tbody"></tbody>
|
|
</table>
|
|
|
|
<div class="pagination" id="pagination"></div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
let currentPage = 1, totalPages = 1;
|
|
|
|
async function searchLogs(page = 1) {
|
|
currentPage = page;
|
|
const serial = document.getElementById("search-serial").value;
|
|
const event_type = document.getElementById("search-event").value;
|
|
const params = new URLSearchParams({page, per_page: 30});
|
|
if (serial) params.set("serial", serial);
|
|
if (event_type) params.set("event_type", event_type);
|
|
|
|
const resp = await fetch(`/api/device-logs?${params}`);
|
|
const data = await resp.json();
|
|
renderTable(data.records);
|
|
totalPages = data.pages;
|
|
renderPagination();
|
|
}
|
|
|
|
function renderTable(records) {
|
|
const tbody = document.getElementById("log-tbody");
|
|
if (!records.length) {
|
|
tbody.innerHTML = '<tr><td colspan="6" style="text-align:center;color:#999;">暂无记录</td></tr>';
|
|
return;
|
|
}
|
|
tbody.innerHTML = records.map(r => {
|
|
let typeStyle = '';
|
|
if (r.event_type === 'online' || r.event_type === 'login') typeStyle = 'color:#27ae60;font-weight:bold;';
|
|
else if (r.event_type === 'offline') typeStyle = 'color:#e74c3c;font-weight:bold;';
|
|
else if (r.event_type === 'poor') typeStyle = 'color:#f39c12;font-weight:bold;';
|
|
else if (r.event_type === 'tcp_disconnect') typeStyle = 'color:#e74c3c;';
|
|
else if (r.event_type === 'tcp_connect') typeStyle = 'color:#3498db;';
|
|
return `
|
|
<tr>
|
|
<td>${r.id}</td>
|
|
<td>${escHtml(r.device_serial || '-')}</td>
|
|
<td>${r.device_ip || '-'}</td>
|
|
<td style="${typeStyle}">${eventLabel(r.event_type)}</td>
|
|
<td style="max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${escHtml(r.event_content || '')}">${r.event_content || '-'}</td>
|
|
<td>${fmtTime(r.create_time)}</td>
|
|
</tr>`;
|
|
}).join("");
|
|
}
|
|
|
|
function eventLabel(t) {
|
|
const m = {login: '登录', online: '在线', offline: '离线', poor: '通信不良',
|
|
tcp_connect: 'TCP连接', tcp_disconnect: 'TCP断开'};
|
|
return m[t] || t;
|
|
}
|
|
|
|
function fmtTime(v) {
|
|
if (!v) return '-';
|
|
const d = new Date(v);
|
|
if (isNaN(d.getTime())) return String(v).substring(0, 19);
|
|
const y = d.getFullYear();
|
|
const m = String(d.getMonth() + 1).padStart(2, '0');
|
|
const d2 = String(d.getDate()).padStart(2, '0');
|
|
const h = String(d.getHours()).padStart(2, '0');
|
|
const min = String(d.getMinutes()).padStart(2, '0');
|
|
const s = String(d.getSeconds()).padStart(2, '0');
|
|
return `${y}-${m}-${d2} ${h}:${min}:${s}`;
|
|
}
|
|
|
|
function renderPagination() {
|
|
const div = document.getElementById("pagination");
|
|
let html = `<button onclick="searchLogs(${currentPage-1})" ${currentPage<=1?'disabled':''}>上一页</button>`;
|
|
for (let i = 1; i <= totalPages; i++) {
|
|
html += `<button onclick="searchLogs(${i})" class="${i===currentPage?'active':''}">${i}</button>`;
|
|
}
|
|
html += `<button onclick="searchLogs(${currentPage+1})" ${currentPage>=totalPages?'disabled':''}>下一页</button>`;
|
|
div.innerHTML = html;
|
|
}
|
|
|
|
function escHtml(s) {
|
|
return String(s || '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
}
|
|
|
|
async function confirmDeleteLogs() {
|
|
const serial = document.getElementById("search-serial").value;
|
|
const event_type = document.getElementById("search-event").value;
|
|
if (!serial && !event_type) {
|
|
alert("请至少输入设备序列号或选择事件类型作为删除条件");
|
|
return;
|
|
}
|
|
const msg = `确认删除设备日志?\n条件: serial=${serial || '(无)'} type=${event_type || '(无)'}\n此操作不可撤销!`;
|
|
if (!confirm(msg)) return;
|
|
|
|
const resp = await fetch("/api/device-logs/delete", {
|
|
method: "POST",
|
|
headers: {"Content-Type": "application/json"},
|
|
body: JSON.stringify({serial, event_type}),
|
|
});
|
|
const data = await resp.json();
|
|
if (data.ok) {
|
|
alert(`已删除 ${data.deleted} 条记录`);
|
|
searchLogs(1);
|
|
} else {
|
|
alert("删除失败: " + (data.error || "未知错误"));
|
|
}
|
|
}
|
|
|
|
searchLogs(1);
|
|
</script>
|
|
{% endblock %}
|