From 513062f08e6b3217f690ea25298f1bf4078c7f2d Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Mon, 20 Apr 2026 00:31:40 +0800 Subject: [PATCH] Add device overview batch selection mode --- internal/web/ui.go | 271 +++++++++++++++---------- internal/web/ui/templates/devices.html | 180 +++++++++------- internal/web/ui_test.go | 64 +++++- 3 files changed, 337 insertions(+), 178 deletions(-) diff --git a/internal/web/ui.go b/internal/web/ui.go index 00dca6e..3336ccc 100644 --- a/internal/web/ui.go +++ b/internal/web/ui.go @@ -40,26 +40,28 @@ type PageData struct { OfflineCount int FoundCount int - Devices []*models.Device - DeviceRows []DeviceOverviewRow - AttentionDevices []*models.Device - Found []*models.Device - Device *models.Device - ConfigStatus *ConfigStatusView - ConfigStatusText string - ConfigStatusErr string - ConfigSources service.ConfigPreviewSources - ConfigPreview *service.ConfigPreviewResult - ResultTitle string - SelectedTemplate string - SelectedProfile string - SelectedOverlays []string - SelectedConfigID string - SelectedVersion string - Tasks []models.Task - Task *models.Task - Templates []service.Template - Template *service.Template + Devices []*models.Device + DeviceRows []DeviceOverviewRow + AttentionDevices []*models.Device + Found []*models.Device + Device *models.Device + ConfigStatus *ConfigStatusView + ConfigStatusText string + ConfigStatusErr string + ConfigSources service.ConfigPreviewSources + ConfigPreview *service.ConfigPreviewResult + ResultTitle string + SelectedTemplate string + SelectedProfile string + SelectedOverlays []string + SelectedConfigID string + SelectedVersion string + Tasks []models.Task + Task *models.Task + Templates []service.Template + Template *service.Template + SelectedDeviceIDs []string + SelectedQuery string RawJSON string RawText string @@ -177,29 +179,29 @@ func NewUI(discovery *service.DiscoveryService, registry *service.RegistryServic func tablerIconSVG(name string) string { icons := map[string]string{ - "devices": ``, - "assets": ``, - "audit": ``, - "system": ``, - "online": ``, - "detail": ``, - "control": ``, - "device": ``, - "status": ``, - "config": ``, - "overview": ``, - "tech": ``, - "preview": ``, - "apply": ``, - "service": ``, - "result": ``, - "meta": ``, - "template": ``, - "profile": ``, - "overlay": ``, - "release": ``, + "devices": ``, + "assets": ``, + "audit": ``, + "system": ``, + "online": ``, + "detail": ``, + "control": ``, + "device": ``, + "status": ``, + "config": ``, + "overview": ``, + "tech": ``, + "preview": ``, + "apply": ``, + "service": ``, + "result": ``, + "meta": ``, + "template": ``, + "profile": ``, + "overlay": ``, + "release": ``, "discovery": ``, - "shield": ``, + "shield": ``, "heartbeat": ``, } if svg, ok := icons[name]; ok { @@ -329,46 +331,7 @@ func (u *UI) pageDashboard(w http.ResponseWriter, r *http.Request) { } func (u *UI) pageDevices(w http.ResponseWriter, r *http.Request) { - u.ensureDevicesLoaded() - devices := u.registry.GetDevices() - rows := make([]DeviceOverviewRow, 0, len(devices)) - for _, dev := range devices { - row := DeviceOverviewRow{Device: dev} - status, _, err := u.loadConfigStatus(dev) - row.ConfigStatus = status - if err != nil { - row.ConfigStatusErr = err.Error() - } - rows = append(rows, row) - } - online := 0 - attention := 0 - for _, d := range devices { - if d.Online { - online++ - } else { - attention++ - } - } - failedTasks := 0 - if u.tasks != nil { - for _, t := range u.tasks.ListTasks() { - if t.Status == models.TaskFailed { - failedTasks++ - } - } - } - u.render(w, r, "devices", PageData{ - Title: "设备", - Devices: devices, - DeviceRows: rows, - DeviceCount: len(devices), - OnlineCount: online, - OfflineCount: len(devices) - online, - RunningTaskCount: 0, - FailedTaskCount: failedTasks, - FoundCount: attention, - }) + u.render(w, r, "devices", u.deviceOverviewPageData(r, nil, "")) } func (u *UI) pageDeviceAdd(w http.ResponseWriter, r *http.Request) { @@ -438,14 +401,7 @@ func (u *UI) actionDevicesBatchAction(w http.ResponseWriter, r *http.Request) { action := strings.TrimSpace(r.FormValue("action")) deviceIDs := r.Form["device_id"] if len(deviceIDs) == 0 { - devices := u.registry.GetDevices() - online := 0 - for _, d := range devices { - if d.Online { - online++ - } - } - u.render(w, r, "devices", PageData{Title: "设备", Devices: devices, DeviceCount: len(devices), OnlineCount: online, OfflineCount: len(devices) - online, Error: "请先选择设备"}) + u.render(w, r, "devices", u.deviceOverviewPageData(r, nil, "请先选择设备")) return } @@ -454,14 +410,7 @@ func (u *UI) actionDevicesBatchAction(w http.ResponseWriter, r *http.Request) { case "media_start", "media_restart", "media_stop", "reload", "rollback": typeStr = action default: - devices := u.registry.GetDevices() - online := 0 - for _, d := range devices { - if d.Online { - online++ - } - } - u.render(w, r, "devices", PageData{Title: "设备", Devices: devices, DeviceCount: len(devices), OnlineCount: online, OfflineCount: len(devices) - online, Error: "不支持的操作: " + action}) + u.render(w, r, "devices", u.deviceOverviewPageData(r, deviceIDs, "不支持的操作: "+action)) return } @@ -480,14 +429,7 @@ func (u *UI) actionDevicesBatchAction(w http.ResponseWriter, r *http.Request) { task, err := u.tasks.CreateTask(typeStr, deviceIDs, payload) if err != nil { - devices := u.registry.GetDevices() - online := 0 - for _, d := range devices { - if d.Online { - online++ - } - } - u.render(w, r, "devices", PageData{Title: "设备", Devices: devices, DeviceCount: len(devices), OnlineCount: online, OfflineCount: len(devices) - online, Error: err.Error()}) + u.render(w, r, "devices", u.deviceOverviewPageData(r, deviceIDs, err.Error())) return } @@ -1139,6 +1081,123 @@ func cleanFormList(values []string) []string { return out } +func selectedIDsFromQuery(values []string) []string { + values = cleanFormList(values) + if len(values) == 0 { + return nil + } + seen := make(map[string]struct{}, len(values)) + out := make([]string, 0, len(values)) + for _, value := range values { + if _, ok := seen[value]; ok { + continue + } + seen[value] = struct{}{} + out = append(out, value) + } + return out +} + +func filterSelectedDeviceIDs(devices []*models.Device, candidates []string) []string { + if len(candidates) == 0 || len(devices) == 0 { + return nil + } + known := make(map[string]struct{}, len(devices)) + for _, dev := range devices { + if dev == nil { + continue + } + id := strings.TrimSpace(dev.DeviceID) + if id != "" { + known[id] = struct{}{} + } + } + seen := make(map[string]struct{}, len(candidates)) + out := make([]string, 0, len(candidates)) + for _, id := range candidates { + id = strings.TrimSpace(id) + if id == "" { + continue + } + if _, ok := known[id]; !ok { + continue + } + if _, ok := seen[id]; ok { + continue + } + seen[id] = struct{}{} + out = append(out, id) + } + if len(out) == 0 { + return nil + } + return out +} + +func selectedQueryString(ids []string) string { + if len(ids) == 0 { + return "" + } + values := url.Values{} + for _, id := range ids { + values.Add("selected", id) + } + return values.Encode() +} + +func (u *UI) deviceOverviewPageData(r *http.Request, selectedIDs []string, errMsg string) PageData { + u.ensureDevicesLoaded() + devices := u.registry.GetDevices() + rows := make([]DeviceOverviewRow, 0, len(devices)) + for _, dev := range devices { + row := DeviceOverviewRow{Device: dev} + status, _, err := u.loadConfigStatus(dev) + row.ConfigStatus = status + if err != nil { + row.ConfigStatusErr = err.Error() + } + rows = append(rows, row) + } + online := 0 + attention := 0 + for _, d := range devices { + if d.Online { + online++ + } else { + attention++ + } + } + failedTasks := 0 + if u.tasks != nil { + for _, t := range u.tasks.ListTasks() { + if t.Status == models.TaskFailed { + failedTasks++ + } + } + } + if selectedIDs == nil { + selectedIDs = selectedIDsFromQuery(r.URL.Query()["selected"]) + } + selectedIDs = filterSelectedDeviceIDs(devices, selectedIDs) + data := PageData{ + Title: "设备", + Devices: devices, + DeviceRows: rows, + DeviceCount: len(devices), + OnlineCount: online, + OfflineCount: len(devices) - online, + RunningTaskCount: 0, + FailedTaskCount: failedTasks, + FoundCount: attention, + SelectedDeviceIDs: selectedIDs, + SelectedQuery: selectedQueryString(selectedIDs), + } + if errMsg != "" { + data.Error = errMsg + } + return data +} + func previewResultFromJSON(raw string) *service.ConfigPreviewResult { raw = strings.TrimSpace(raw) if raw == "" { diff --git a/internal/web/ui/templates/devices.html b/internal/web/ui/templates/devices.html index f06246d..f180cee 100644 --- a/internal/web/ui/templates/devices.html +++ b/internal/web/ui/templates/devices.html @@ -41,88 +41,112 @@ -
| 设备 | -状态 | -当前配置 | -操作 | -|
|---|---|---|---|---|
|
-
- {{icon "device"}}
-
- {{if .Device.DeviceName}}{{.Device.DeviceName}}{{else}}{{.Device.DeviceID}}{{end}}
- |
+ + + | +|||
|
+
+
+ 还没有设备
+ 当前后台还没有发现或录入任何设备。
+ |
+ ||||