Keep candidate apply action visible
This commit is contained in:
parent
5d3948250d
commit
ec419e87c3
@ -73,6 +73,7 @@ type ConfigStatusView struct {
|
||||
Sha256 string `json:"sha256"`
|
||||
Size int64 `json:"size"`
|
||||
Metadata ConfigStatusMetadata `json:"metadata"`
|
||||
Candidate *ConfigStatusLastGoodFile `json:"candidate"`
|
||||
MediaServer ConfigStatusMediaServer `json:"media_server"`
|
||||
LastGood *ConfigStatusLastGoodFile `json:"last_good"`
|
||||
}
|
||||
@ -898,6 +899,7 @@ func (u *UI) actionDeviceConfigCandidate(w http.ResponseWriter, r *http.Request)
|
||||
u.render(w, r, "config_preview", data)
|
||||
return
|
||||
}
|
||||
data.ConfigPreview = previewResultFromJSON(raw)
|
||||
body, code, err := u.agent.Do("PUT", dev.IP, dev.AgentPort, "/v1/config/candidate", []byte(raw))
|
||||
data.Message = fmt.Sprintf("PUT /v1/config/candidate -> %d", code)
|
||||
data.RawText = prettyJSON(body)
|
||||
@ -915,6 +917,10 @@ func (u *UI) actionDeviceConfigCandidateApply(w http.ResponseWriter, r *http.Req
|
||||
return
|
||||
}
|
||||
data := u.configPreviewPageData(dev)
|
||||
raw := strings.TrimSpace(r.FormValue("json"))
|
||||
if raw != "" {
|
||||
data.ConfigPreview = previewResultFromJSON(raw)
|
||||
}
|
||||
body, code, err := u.agent.Do("POST", dev.IP, dev.AgentPort, "/v1/config/candidate/apply", []byte(`{}`))
|
||||
data.Message = fmt.Sprintf("POST /v1/config/candidate/apply -> %d", code)
|
||||
data.RawText = prettyJSON(body)
|
||||
@ -930,6 +936,11 @@ func (u *UI) configPreviewPageData(dev *models.Device) PageData {
|
||||
if err != nil {
|
||||
data.Error = err.Error()
|
||||
}
|
||||
status, _, statusErr := u.loadConfigStatus(dev)
|
||||
data.ConfigStatus = status
|
||||
if statusErr != nil {
|
||||
data.ConfigStatusErr = statusErr.Error()
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
@ -944,6 +955,23 @@ func cleanFormList(values []string) []string {
|
||||
return out
|
||||
}
|
||||
|
||||
func previewResultFromJSON(raw string) *service.ConfigPreviewResult {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return nil
|
||||
}
|
||||
var doc map[string]any
|
||||
if err := json.Unmarshal([]byte(raw), &doc); err != nil {
|
||||
return nil
|
||||
}
|
||||
metadata, _ := doc["metadata"].(map[string]any)
|
||||
return &service.ConfigPreviewResult{
|
||||
JSON: raw,
|
||||
Metadata: metadata,
|
||||
Size: len([]byte(raw)),
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UI) actionDeviceConfigUIPlan(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, "id")
|
||||
dev, ok := u.findDevice(id)
|
||||
|
||||
@ -54,7 +54,13 @@
|
||||
<div class="actions" style="margin-top:12px">
|
||||
<button type="submit">生成预览</button>
|
||||
<button type="button" disabled>上传为候选配置</button>
|
||||
{{if and .ConfigStatus .ConfigStatus.Candidate .ConfigStatus.Candidate.Exists}}
|
||||
<form method="post" action="/ui/devices/{{.Device.DeviceID}}/config-candidate/apply" style="display:inline">
|
||||
<button type="submit">应用候选配置</button>
|
||||
</form>
|
||||
{{else}}
|
||||
<button type="button" disabled>应用候选配置</button>
|
||||
{{end}}
|
||||
<a class="btn ghost" href="/ui/devices/{{.Device.DeviceID}}">查看当前运行配置</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@ -4,6 +4,7 @@ import (
|
||||
"3588AdminBackend/internal/config"
|
||||
"3588AdminBackend/internal/models"
|
||||
"3588AdminBackend/internal/service"
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@ -12,6 +13,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
func TestUI_ActionDevicesBatchAction_RedirectsToTask(t *testing.T) {
|
||||
@ -340,6 +343,101 @@ func TestUI_ConfigPreviewPageShowsTemplateProfileOverlayForm(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUI_ConfigPreviewPageKeepsApplyActionAfterUploadResult(t *testing.T) {
|
||||
ui := newTestUI(t)
|
||||
req := httptest.NewRequest(http.MethodGet, "/ui/devices/edge-01/config-preview", nil)
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
ui.render(rr, req, "config_preview", PageData{
|
||||
Title: "配置预览",
|
||||
Device: &models.Device{DeviceID: "edge-01", DeviceName: "入口识别节点", IP: "127.0.0.1", AgentPort: 9100},
|
||||
ConfigStatus: &ConfigStatusView{
|
||||
Candidate: &ConfigStatusLastGoodFile{Exists: true, Path: "/opt/rk3588-media-server/etc/media-server.json.candidate.json"},
|
||||
},
|
||||
ConfigPreview: &service.ConfigPreviewResult{
|
||||
JSON: `{"templates":{"tpl":{"nodes":[],"edges":[]}},"instances":[],"metadata":{"config_id":"preview_edge-01","config_version":"v1"}}`,
|
||||
Metadata: map[string]any{
|
||||
"config_id": "preview_edge-01",
|
||||
"config_version": "v1",
|
||||
"template": "workshop_face_shoe_alarm",
|
||||
"profile": "local_3588_test",
|
||||
},
|
||||
Size: 123,
|
||||
},
|
||||
RawText: `{"ok":true}`,
|
||||
})
|
||||
|
||||
body := rr.Body.String()
|
||||
for _, want := range []string{
|
||||
"上传结果",
|
||||
"上传为候选配置",
|
||||
"应用候选配置",
|
||||
`formaction="/ui/devices/edge-01/config-candidate/apply"`,
|
||||
} {
|
||||
if !strings.Contains(body, want) {
|
||||
t.Fatalf("expected config preview upload result HTML to contain %q, got:\n%s", want, body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUI_ActionDeviceConfigCandidateKeepsPreviewApplyAction(t *testing.T) {
|
||||
agentServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodGet && r.URL.Path == "/v1/config/status" {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"ok":true,"candidate":{"exists":true,"path":"/opt/rk3588-media-server/etc/media-server.json.candidate.json"}}`))
|
||||
return
|
||||
}
|
||||
if r.Method != http.MethodPut || r.URL.Path != "/v1/config/candidate" {
|
||||
t.Fatalf("unexpected request %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"ok":true}`))
|
||||
}))
|
||||
defer agentServer.Close()
|
||||
|
||||
host, portText, err := net.SplitHostPort(strings.TrimPrefix(agentServer.URL, "http://"))
|
||||
if err != nil {
|
||||
t.Fatalf("parse test server address: %v", err)
|
||||
}
|
||||
port, err := strconv.Atoi(portText)
|
||||
if err != nil {
|
||||
t.Fatalf("parse test server port: %v", err)
|
||||
}
|
||||
|
||||
cfg := &config.Config{Concurrency: 1, OfflineAfterMs: 1000000}
|
||||
agent := service.NewAgentClient(cfg)
|
||||
reg := service.NewRegistryService(cfg, agent)
|
||||
reg.UpdateDevice(&models.Device{DeviceID: "edge-01", DeviceName: "入口识别节点", IP: host, AgentPort: port, MediaPort: 9000, Online: true})
|
||||
tasks := service.NewTaskService(cfg, agent, reg)
|
||||
ui, err := NewUI(nil, reg, agent, tasks, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("NewUI: %v", err)
|
||||
}
|
||||
|
||||
form := url.Values{}
|
||||
form.Set("json", `{"templates":{"tpl":{"nodes":[],"edges":[]}},"instances":[],"metadata":{"config_id":"preview_edge-01","config_version":"v1","template":"workshop_face_shoe_alarm","profile":"local_3588_test"}}`)
|
||||
req := httptest.NewRequest(http.MethodPost, "/ui/devices/edge-01/config-candidate", strings.NewReader(form.Encode()))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.SetPathValue("id", "edge-01")
|
||||
rctx := chi.NewRouteContext()
|
||||
rctx.URLParams.Add("id", "edge-01")
|
||||
req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
ui.actionDeviceConfigCandidate(rr, req)
|
||||
|
||||
body := rr.Body.String()
|
||||
for _, want := range []string{
|
||||
"上传结果",
|
||||
"应用候选配置",
|
||||
`formaction="/ui/devices/edge-01/config-candidate/apply"`,
|
||||
} {
|
||||
if !strings.Contains(body, want) {
|
||||
t.Fatalf("expected actionDeviceConfigCandidate HTML to contain %q, got:\n%s", want, body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUI_ModelDeploymentPageRendersDeviceActions(t *testing.T) {
|
||||
ui := newTestUI(t)
|
||||
req := httptest.NewRequest(http.MethodGet, "/ui/models", nil)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user