refactor: move overlay selection from console to device detail page

This commit is contained in:
tian 2026-07-27 18:35:08 +08:00
parent 8d8daf1b7f
commit 45470d0908
4 changed files with 56 additions and 56 deletions

View File

@ -837,6 +837,7 @@ func (u *UI) Routes() (chi.Router, error) {
r.Post("/devices/batch-config", u.actionDeviceBatchConfig)
r.Post("/discovery/search", u.actionDiscoverySearch)
r.Get("/devices/{id}", u.pageDevice)
r.Post("/devices/{id}/overlays", u.actionDeviceOverlaysSave)
r.Post("/devices/{id}/alias", u.actionDeviceAliasSave)
r.Get("/devices/{id}/config-versions", u.pageDeviceConfigVersions)
r.Get("/devices/{id}/config-versions/{versionID}", u.apiDeviceConfigVersion)
@ -1204,30 +1205,6 @@ func (u *UI) pageConsole(w http.ResponseWriter, r *http.Request) {
}
// Build console device data
var allOverlays []ConsoleOverlay
if u.preview != nil {
if overlays, err := u.preview.ListOverlayAssets(); err == nil {
for _, o := range overlays {
allOverlays = append(allOverlays, ConsoleOverlay{Name: o.Name, Description: o.Description})
}
}
}
// Pre-load device overlay selections
deviceOverlaySelections := map[string]map[string]bool{}
if u.preview != nil {
for _, dev := range devices {
if dev == nil {
continue
}
if names, err := u.preview.GetDeviceOverlays(dev.DeviceID); err == nil {
m := map[string]bool{}
for _, n := range names {
m[n] = true
}
deviceOverlaySelections[dev.DeviceID] = m
}
}
}
var consoleDevices []ConsoleDeviceData
for _, dev := range devices {
if dev == nil {
@ -1293,14 +1270,6 @@ func (u *UI) pageConsole(w http.ResponseWriter, r *http.Request) {
diskUsage = m.Disk
temp = m.Temperature
}
// Mark which overlays are enabled for this device
deviceOverlays := make([]ConsoleOverlay, len(allOverlays))
copy(deviceOverlays, allOverlays)
if sel, ok := deviceOverlaySelections[dev.DeviceID]; ok {
for i := range deviceOverlays {
deviceOverlays[i].Enabled = sel[deviceOverlays[i].Name]
}
}
consoleDevices = append(consoleDevices, ConsoleDeviceData{
Device: dev,
CPUUsage: cpuUsage,
@ -1311,7 +1280,6 @@ func (u *UI) pageConsole(w http.ResponseWriter, r *http.Request) {
MaxUnits: maxUnits,
Channels: channels,
Features: features,
Overlays: deviceOverlays,
Cameras: cameras,
})
}
@ -1406,7 +1374,6 @@ func (u *UI) actionConsoleSave(w http.ResponseWriter, r *http.Request) {
DeviceID: dev.DeviceID,
Features: features,
SourceNames: sources,
Overlays: cleanFormList(r.Form["device_"+dev.DeviceID+"_overlay"]),
})
}
@ -1418,7 +1385,6 @@ func (u *UI) actionConsoleSave(w http.ResponseWriter, r *http.Request) {
// Step 3: save features to DB BEFORE creating tasks (avoids SQLITE_BUSY race).
for _, req := range requests {
_ = u.preview.SaveDeviceFeatures(req.DeviceID, req.Features)
_ = u.preview.SaveDeviceOverlays(req.DeviceID, req.Overlays)
}
// Step 4: run pipeline (creates tasks asynchronously).
@ -1964,12 +1930,37 @@ func (u *UI) pageDevice(w http.ResponseWriter, r *http.Request) {
http.NotFound(w, r)
return
}
u.render(w, r, "device", u.deviceDetailPageData(dev))
data := u.deviceDetailPageData(dev)
data.Message = strings.TrimSpace(r.URL.Query().Get("msg"))
data.Error = strings.TrimSpace(r.URL.Query().Get("error"))
u.render(w, r, "device", data)
}
func (u *UI) actionDeviceOverlaysSave(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
if err := r.ParseForm(); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
overlays := cleanFormList(r.Form["overlay"])
if u.preview != nil {
_ = u.preview.SaveDeviceOverlays(id, overlays)
}
http.Redirect(w, r, "/devices/"+url.PathEscape(id)+"?msg="+url.QueryEscape("覆盖参数已保存"), http.StatusFound)
}
func (u *UI) deviceDetailPageData(dev *models.Device) PageData {
data := u.deviceControlPageData(dev)
data.Title = "设备详情"
// Load overlays for device-specific override selection
if u.preview != nil {
if overlays, err := u.preview.ListOverlayAssets(); err == nil {
data.AssetOverlays = overlays
if selected, err := u.preview.GetDeviceOverlays(dev.DeviceID); err == nil {
data.SelectedOverlays = selected
}
}
}
if data.ConfigStatus == nil && u.stateRepo != nil && dev != nil {
if state, err := u.stateRepo.Get(dev.DeviceID); err == nil && state != nil {
data.PersistedConfig = state
@ -6377,18 +6368,11 @@ type ConsoleDeviceData struct {
MaxUnits int
Channels []ConsoleChannel
Features []ConsoleFeature
Overlays []ConsoleOverlay
Cameras []string
Busy bool
BusyMsg string
}
type ConsoleOverlay struct {
Name string
Description string
Enabled bool
}
type ConsoleChannel struct {
Name string
Display string

View File

@ -68,19 +68,7 @@
{{end}}
</table>
</div>
{{if $cd.Overlays}}
<div class="console-section">
<div class="console-section-title">告警覆盖层</div>
<table class="console-feature-table">
{{range $o := $cd.Overlays}}
<tr>
<td class="console-feature-check"><input type="checkbox" name="device_{{$cd.Device.DeviceID}}_overlay" value="{{$o.Name}}"{{if $o.Enabled}} checked{{end}}></td>
<td class="console-feature-label" title="{{$o.Description}}">{{$o.Name}}</td>
</tr>
{{end}}
</table>
</div>
{{end}}
<div class="console-section">
<div class="console-section-title">设备负载</div>
<div class="console-metrics">

View File

@ -161,6 +161,34 @@
</div>
</section>
<section class="panel-block" id="device-overlays">
<div class="panel-head">
<div>
<h3 class="title-with-icon">{{icon "overlay"}}<span>调试参数覆盖</span></h3>
</div>
</div>
{{if .AssetOverlays}}
<form method="POST" action="/devices/{{.Device.DeviceID}}/overlays" style="margin-top:8px">
<div class="info-list compact-list">
{{range .AssetOverlays}}
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
<input type="checkbox" name="overlay" value="{{.Name}}" {{if hasString $.SelectedOverlays .Name}}checked{{end}}>
<span class="mono">{{.Name}}</span>
{{if .Description}}<span class="muted small">{{.Description}}</span>{{end}}
</label>
{{end}}
</div>
<div class="actions" style="margin-top:10px">
<button type="submit" class="btn primary">保存覆盖参数</button>
</div>
</form>
{{else}}
<div class="empty-state compact">
<div class="muted">暂无可用的调试参数覆盖。在 <a href="/assets/overlays">调试参数管理</a> 中创建。</div>
</div>
{{end}}
</section>
<section class="panel-block" id="device-models">
<div class="panel-head">
<div>

Binary file not shown.