refactor: server-side device metrics, no JS

This commit is contained in:
tian 2026-05-08 19:45:26 +08:00
parent 0dede50f9e
commit c2588e7785
2 changed files with 41 additions and 34 deletions

View File

@ -83,6 +83,7 @@ type PageData struct {
ResourceStatusBoard *service.ResourceStatusBoard
AlarmRecords []service.AlarmRecord
TodayAlarmCount int
DeviceMetrics []DeviceMetric
FaceGalleryPersons []storage.PersonRecord
Templates []service.Template
Template *service.Template
@ -777,6 +778,25 @@ func (u *UI) pageDashboard(w http.ResponseWriter, r *http.Request) {
data.Tasks = u.tasks.ListTasks()
}
data.AttentionDevices = nil
// Load device metrics
if u.agent != nil {
for _, dev := range data.Devices {
if dev == nil || !dev.Online { continue }
body, code, err := u.agent.Do("GET", dev.IP, dev.AgentPort, "/v1/metrics", nil)
if err != nil || code != 200 { continue }
var m struct {
CPU struct{ UsagePct float64 } `json:"cpu"`
Memory struct{ TotalKB, AvailableKB uint64 } `json:"memory"`
NPU struct{ UsagePct float64 } `json:"npu"`
}
json.Unmarshal(body, &m)
var mem float64
if m.Memory.TotalKB > 0 { mem = float64(m.Memory.TotalKB-m.Memory.AvailableKB) / float64(m.Memory.TotalKB) * 100 }
data.DeviceMetrics = append(data.DeviceMetrics, DeviceMetric{
Name: dev.DisplayName(), CPU: m.CPU.UsagePct, Mem: mem, NPU: m.NPU.UsagePct,
})
}
}
for _, dev := range data.Devices {
if dev != nil && !dev.Online {
data.AttentionDevices = append(data.AttentionDevices, dev)
@ -4191,3 +4211,10 @@ func (u *UI) apiDeviceMetrics(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(code)
w.Write(body)
}
type DeviceMetric struct {
Name string `json:"name"`
CPU float64 `json:"cpu"`
Mem float64 `json:"mem"`
NPU float64 `json:"npu"`
}

View File

@ -28,7 +28,20 @@
<h3 class="title-with-icon" style="margin:0">{{icon "heartbeat"}}<span>设备负载</span></h3>
<span class="muted small" id="metrics-time"></span>
</div>
<div id="metrics-body" style="display:flex;flex-wrap:wrap;gap:12px"><span class="muted">加载中…</span></div>
<div id="metrics-body" style="display:flex;flex-wrap:wrap;gap:12px">
{{range .DeviceMetrics}}
<div style="padding:8px 12px;border:1px solid var(--border);border-radius:var(--radius);background:var(--surface-soft);min-width:160px">
<div style="font-weight:500;font-size:12px;margin-bottom:4px">{{.Name}}</div>
<div style="display:flex;gap:12px;font-size:11px">
<span>CPU <strong>{{printf "%.0f%%" .CPU}}</strong></span>
<span>内存 <strong>{{printf "%.0f%%" .Mem}}</strong></span>
<span>NPU <strong>{{printf "%.0f%%" .NPU}}</strong></span>
</div>
</div>
{{else}}
<span class="muted">无在线设备</span>
{{end}}
</div>
</div>
<div class="card">
@ -74,37 +87,4 @@
</div>
</div>
<script>
(function(){
var body=document.getElementById('metrics-body'),time=document.getElementById('metrics-time');
function load(){
time.textContent='加载中…';
var devs=[{{range .Devices}}{{if .Online}}{id:"{{.DeviceID}}",name:"{{.DisplayName}}"},{{end}}{{end}}];
if(!devs.length){body.innerHTML='<span class="muted">无在线设备</span>';return}
Promise.all(devs.map(function(d){
return fetch('/ui/api/device-metrics?device_id='+d.id).then(function(r){return r.json()}).catch(function(){return null}).then(function(m){return{name:d.name,data:m}});
})).then(function(r){
var html='';
r.forEach(function(item){
var m=item.data;
var cpu=m&&m.cpu?m.cpu.usage_pct:'-';
var mem=m&&m.memory?((m.memory.used_mb||0)/(m.memory.total_mb||1)*100).toFixed(0):'-';
var npu=m&&m.npu?m.npu.usage_pct:'-';
html+='<div style="padding:8px 12px;border:1px solid var(--border);border-radius:var(--radius);background:var(--surface-soft);min-width:160px">';
html+='<div style="font-weight:500;font-size:12px;margin-bottom:4px">'+esc(item.name)+'</div>';
html+='<div style="display:flex;gap:12px;font-size:11px">';
html+='<span>CPU <strong>'+cpu+'%</strong></span>';
html+='<span>内存 <strong>'+mem+'%</strong></span>';
html+='<span>NPU <strong>'+npu+'%</strong></span>';
html+='</div></div>';
});
body.innerHTML=html;
var now=new Date();
time.textContent=now.getHours()+':'+String(now.getMinutes()).padStart(2,'0')+':'+String(now.getSeconds()).padStart(2,'0');
});
}
function esc(s){return(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');}
load();setInterval(load,10000);
})();
</script>
{{end}}