From 17240ac7bdc46874b14435bb485dcf47fbd10a34 Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Mon, 20 Apr 2026 00:44:54 +0800 Subject: [PATCH] Promote device batch service actions in overview --- internal/web/ui.go | 49 +++++++++++- internal/web/ui/assets/style.css | 11 +++ internal/web/ui/templates/devices.html | 4 +- internal/web/ui/templates/task.html | 96 +++++++++++++++++------- internal/web/ui_test.go | 100 ++++++++++++++++++++++++- 5 files changed, 230 insertions(+), 30 deletions(-) diff --git a/internal/web/ui.go b/internal/web/ui.go index 3336ccc..fcf8ce2 100644 --- a/internal/web/ui.go +++ b/internal/web/ui.go @@ -58,6 +58,7 @@ type PageData struct { SelectedVersion string Tasks []models.Task Task *models.Task + TaskDeviceRows []TaskDeviceRow Templates []service.Template Template *service.Template SelectedDeviceIDs []string @@ -81,6 +82,13 @@ type DeviceOverviewRow struct { ConfigStatusErr string } +type TaskDeviceRow struct { + Device *models.Device + Status models.TaskStatus + Progress float64 + Error string +} + type ConfigStatusView struct { OK bool `json:"ok"` ConfigPath string `json:"config_path"` @@ -399,7 +407,7 @@ func (u *UI) actionDiscoverySearch(w http.ResponseWriter, r *http.Request) { func (u *UI) actionDevicesBatchAction(w http.ResponseWriter, r *http.Request) { _ = r.ParseForm() action := strings.TrimSpace(r.FormValue("action")) - deviceIDs := r.Form["device_id"] + deviceIDs := filterSelectedDeviceIDs(u.registry.GetDevices(), r.Form["device_id"]) if len(deviceIDs) == 0 { u.render(w, r, "devices", u.deviceOverviewPageData(r, nil, "请先选择设备")) return @@ -701,6 +709,41 @@ func (u *UI) pageTasks(w http.ResponseWriter, r *http.Request) { u.render(w, r, "tasks", PageData{Title: "任务中心", Tasks: u.tasks.ListTasks(), Devices: u.registry.GetDevices()}) } +func (u *UI) taskPageData(task *models.Task) PageData { + data := PageData{Title: "任务详情", Task: task} + if task == nil { + return data + } + + devices := make(map[string]*models.Device) + if u.registry != nil { + for _, dev := range u.registry.GetDevices() { + if dev == nil { + continue + } + devices[dev.DeviceID] = dev + } + } + + rows := make([]TaskDeviceRow, 0, len(task.DeviceIDs)) + for _, did := range task.DeviceIDs { + row := TaskDeviceRow{} + if dev := devices[did]; dev != nil { + row.Device = dev + } else { + row.Device = &models.Device{DeviceID: did} + } + if ds := task.Devices[did]; ds != nil { + row.Status = ds.Status + row.Progress = ds.Progress + row.Error = ds.Error + } + rows = append(rows, row) + } + data.TaskDeviceRows = rows + return data +} + func (u *UI) actionCreateTask(w http.ResponseWriter, r *http.Request) { _ = r.ParseForm() typeStr := strings.TrimSpace(r.FormValue("type")) @@ -748,7 +791,9 @@ func (u *UI) pageTask(w http.ResponseWriter, r *http.Request) { http.NotFound(w, r) return } - u.render(w, r, "task", PageData{Title: "任务详情", Task: task, TaskID: id}) + data := u.taskPageData(task) + data.TaskID = id + u.render(w, r, "task", data) } func (u *UI) pageTemplates(w http.ResponseWriter, r *http.Request) { diff --git a/internal/web/ui/assets/style.css b/internal/web/ui/assets/style.css index c2f00ea..b64f6ee 100644 --- a/internal/web/ui/assets/style.css +++ b/internal/web/ui/assets/style.css @@ -104,9 +104,12 @@ tbody tr:hover{background:#f9fafb} .pill{display:inline-flex;align-items:center;padding:3px 8px;border-radius:999px;border:1px solid var(--border);background:#f3f4f6;color:#374151;font-size:11px;font-weight:600} .pill.ok{background:#ecfdf5;border-color:#bbf7d0;color:#166534} .pill.bad{background:#fef2f2;border-color:#fecaca;color:#991b1b} +.pill.run{background:#eff6ff;border-color:#bfdbfe;color:#1d4ed8} .pill.warn{background:#fffbeb;border-color:#fde68a;color:#92400e} .actions{display:flex;flex-wrap:wrap;gap:8px} +.actions.compact{gap:6px} +.actions.compact .btn,.actions.compact button{padding:5px 9px;font-size:11px} .btn .ui-icon{width:14px;height:14px} .stack{flex-direction:column;align-items:flex-start} .device-context-head{display:flex;align-items:center;gap:12px} @@ -151,6 +154,13 @@ pre{margin-top:12px;padding:12px;border-radius:8px;border:1px solid #1f2937;back .subnav a{display:inline-flex;align-items:center;padding:7px 10px;border:1px solid var(--border);border-radius:999px;background:#fff;color:#374151;font-size:12px;font-weight:500} .ui-icon{display:block;flex:0 0 auto} +.batch-toolbar{display:flex;align-items:flex-start;justify-content:space-between;gap:14px;padding:14px 16px;border:1px solid var(--border);border-radius:8px;background:var(--surface-soft);margin:0 0 12px} +.batch-toolbar-count{font-size:13px;font-weight:600} +.batch-toolbar .actions{justify-content:flex-end} +.batch-toolbar .actions .btn,.batch-toolbar .actions button{white-space:nowrap} +.select-cell{width:52px;text-align:center;vertical-align:middle} +.select-cell input[type=checkbox]{width:16px;height:16px;margin:0;accent-color:var(--primary)} + @media (max-width:1024px){ .app-shell{grid-template-columns:1fr} .sidebar{position:relative;height:auto} @@ -158,4 +168,5 @@ pre{margin-top:12px;padding:12px;border-radius:8px;border:1px solid #1f2937;back main{padding:18px} .stats,.detail-grid,.quad-grid,.control-grid,.summary-strip,.info-list,.field-grid{grid-template-columns:1fr} .hero-band{flex-direction:column;align-items:flex-start} + .batch-toolbar{flex-direction:column} } diff --git a/internal/web/ui/templates/devices.html b/internal/web/ui/templates/devices.html index f180cee..be9349f 100644 --- a/internal/web/ui/templates/devices.html +++ b/internal/web/ui/templates/devices.html @@ -48,7 +48,7 @@
| 节点标识 | 状态 | 进度 | 失败原因 | ||
|---|---|---|---|---|---|
| 设备 | 状态 | 进度 | 失败原因 | ||
| {{$id}} | + {{range .TaskDeviceRows}} +|||||
|
+
+
+ {{icon "device"}}
+
+
+ {{if .Device.DeviceName}}{{.Device.DeviceName}}{{else}}{{.Device.DeviceID}}{{end}}
+
+ |
- {{if eq $st.Status "success"}}成功 - {{else if eq $st.Status "failed"}}失败 - {{else if eq $st.Status "running"}}执行中 + {{if eq .Status "success"}}成功 + {{else if eq .Status "failed"}}失败 + {{else if eq .Status "running"}}执行中 {{else}}待执行{{end}} | -{{$st.Progress}} | -{{$st.Error}} | +{{.Progress}} | +{{.Error}} |