157 lines
6.5 KiB
HTML
157 lines
6.5 KiB
HTML
{{define "dashboard"}}
|
|
<div class="stats">
|
|
<div class="stat accent-red">
|
|
<div class="k">{{icon "bell"}}<span>今日告警</span></div>
|
|
<div class="v">{{.TodayAlarmCount}}</div>
|
|
<div class="hint">今日告警总数</div>
|
|
</div>
|
|
<div class="stat accent-amber">
|
|
<div class="k">{{icon "bell"}}<span>未处理告警</span></div>
|
|
<div class="v" {{if gt .UnacknowledgedAlarmCount 0}}style="color:var(--amber)"{{end}}>{{.UnacknowledgedAlarmCount}}</div>
|
|
<div class="hint">待确认和关闭的告警</div>
|
|
</div>
|
|
<div class="stat accent-green">
|
|
<div class="k">{{icon "devices"}}<span>在线设备</span></div>
|
|
<div class="v">{{.OnlineCount}}<span style="font-size:14px;color:var(--muted)"> / {{.DeviceCount}}</span></div>
|
|
<div class="hint">设备在线率</div>
|
|
</div>
|
|
<div class="stat accent-slate">
|
|
<div class="k">{{icon "apply"}}<span>检测路数</span></div>
|
|
<div class="v">{{.DetectionChannelCount}}</div>
|
|
<div class="hint">已配置的检测通道</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div style="flex:1 1 480px">
|
|
<div class="card" style="border-left:3px solid var(--red);min-height:200px">
|
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px">
|
|
<h3 class="title-with-icon" style="margin:0">{{icon "bell"}}<span>实时告警</span></h3>
|
|
<a class="btn ghost" href="/ui/alarms" style="font-size:11px">查看全部 →</a>
|
|
</div>
|
|
<div id="live-alarms" style="display:flex;flex-direction:column;gap:6px">
|
|
{{range .AlarmRecords}}
|
|
<div class="live-alarm-card" onclick="location.href='/ui/alarms'" style="padding:8px 10px;border:1px solid var(--border);border-radius:var(--radius);background:var(--surface-soft);cursor:pointer;display:flex;align-items:center;gap:10px">
|
|
<span class="pill {{severityClass .Severity}}" style="flex-shrink:0">{{severityLabel .Severity}}</span>
|
|
<span style="font-weight:500;font-size:12px">{{ruleLabel .RuleName}}</span>
|
|
<span class="muted small mono" style="margin-left:auto;flex-shrink:0">{{shortID .DeviceID}}</span>
|
|
</div>
|
|
{{else}}
|
|
<div class="empty-state compact" style="border-style:solid">
|
|
<div class="empty-title">暂无告警</div>
|
|
<div class="muted">有新告警时将实时显示在这里。</div>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="flex:1 1 380px">
|
|
<div class="card" style="min-height:200px">
|
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px">
|
|
<h3 style="margin:0">{{icon "online"}}<span style="margin-left:6px">异常设备</span></h3>
|
|
</div>
|
|
{{if .AttentionDevices}}
|
|
{{range .AttentionDevices}}
|
|
<div style="padding:8px 10px;display:flex;align-items:center;gap:8px;border-bottom:1px solid var(--border)">
|
|
<span class="pill bad">离线</span>
|
|
<a class="mono" href="/ui/devices/{{.DeviceID}}" style="font-size:12px">{{.DeviceName}}</a>
|
|
<span class="muted small" style="margin-left:auto">{{ago .LastSeenMs}}</span>
|
|
</div>
|
|
{{end}}
|
|
{{else}}
|
|
<div class="empty-state compact" style="border-style:solid">
|
|
<div class="muted">所有设备运行正常。</div>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
|
|
<h2 style="margin:0">最近任务</h2>
|
|
<a class="btn ghost" href="/ui/tasks" style="font-size:11px">更多 →</a>
|
|
</div>
|
|
<div class="table-wrap" style="margin-top:10px">
|
|
<table>
|
|
<thead><tr><th>任务</th><th>操作</th><th>状态</th><th>创建时间</th><th>完成时间</th></tr></thead>
|
|
<tbody>
|
|
{{range $i, $t := .Tasks}}
|
|
{{if lt $i 5}}
|
|
<tr>
|
|
<td><a class="mono" href="/ui/tasks/{{$t.ID}}">{{shortID $t.ID}}</a></td>
|
|
<td>{{taskActionLabel $t.Type}}</td>
|
|
<td><span class="{{taskStatusClass $t.Status}}">{{taskStatusLabel $t.Status}}</span></td>
|
|
<td class="muted small">{{$t.CreatedAt}}</td>
|
|
<td class="muted small">{{$t.CompletedAt}}</td>
|
|
</tr>
|
|
{{end}}
|
|
{{else}}
|
|
<tr><td colspan="5" class="muted">暂无任务记录。</td></tr>
|
|
{{end}}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
(function(){
|
|
var alarmContainer = document.getElementById("live-alarms");
|
|
var maxCards = 10;
|
|
var emptyEl = alarmContainer.querySelector(".empty-state");
|
|
|
|
function addAlarmCard(alarm) {
|
|
if (emptyEl) {
|
|
emptyEl.remove();
|
|
emptyEl = null;
|
|
}
|
|
var card = document.createElement("div");
|
|
card.className = "live-alarm-card";
|
|
card.style.cssText = "padding:8px 10px;border:1px solid var(--border);border-radius:var(--radius);background:var(--surface-soft);cursor:pointer;display:flex;align-items:center;gap:10px;animation:fadeIn .3s ease";
|
|
card.onclick = function(){ location.href="/ui/alarms"; };
|
|
|
|
var sevMap = {critical:"严重",warning:"警告",info:"信息"};
|
|
var sevClassMap = {critical:"bad",warning:"warn",info:"ok"};
|
|
var sevLabel = sevMap[alarm.severity] || alarm.severity || "警告";
|
|
var sevClass = sevClassMap[alarm.severity] || "warn";
|
|
|
|
var ruleMap = {
|
|
"unknown_face":"陌生人员告警","known_person":"已登记人员",
|
|
"non_compliant_workshoe":"未穿劳保鞋","non_black_shoe":"工鞋不合规"
|
|
};
|
|
var ruleLabel = ruleMap[alarm.rule_name] || alarm.rule_name;
|
|
|
|
var deviceID = alarm.device_id || "";
|
|
var devShort = deviceID.length > 8 ? deviceID.substring(0,8) : deviceID;
|
|
|
|
card.innerHTML =
|
|
'<span class="pill ' + sevClass + '" style="flex-shrink:0">' + sevLabel + '</span>' +
|
|
'<span style="font-weight:500;font-size:12px">' + ruleLabel + '</span>' +
|
|
'<span class="muted small mono" style="margin-left:auto;flex-shrink:0">' + devShort + '</span>';
|
|
|
|
alarmContainer.insertBefore(card, alarmContainer.firstChild);
|
|
while (alarmContainer.children.length > maxCards) {
|
|
alarmContainer.removeChild(alarmContainer.lastChild);
|
|
}
|
|
}
|
|
|
|
try {
|
|
var es = new EventSource("/api/alarms/stream");
|
|
es.onmessage = function(e) {
|
|
try {
|
|
var alarm = JSON.parse(e.data);
|
|
addAlarmCard(alarm);
|
|
} catch(ignored) {}
|
|
};
|
|
} catch(ignored) {}
|
|
})();
|
|
</script>
|
|
|
|
<style>
|
|
.live-alarm-card:hover{background:var(--sidebar-hover)!important}
|
|
@keyframes fadeIn{from{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}
|
|
</style>
|
|
|
|
{{end}}
|