diff --git a/bin/SHA256SUMS b/bin/SHA256SUMS index 60988eb..ce77107 100644 --- a/bin/SHA256SUMS +++ b/bin/SHA256SUMS @@ -1,4 +1,4 @@ dcb137ae6a3f6a59465f2772215d48f98fd303dd1c266645b20ec70194ab7915 control-api b7a6068db1d095ae7b73cbd9b7273d6a1629ae017e9c3c9601e103432894a247 validator-agent be8e9576c6fbe5e5798b5d4b758a5617b63bea6abe80e326998d10583edbbf5c prober -cec482fb92246210108261cfc02c10ef9829ce3c7d7c174cf935976baa42fd2c admin-dashboard +61d1df7aa84528e8fd9b5e700ac6a9b77e45763b5c5d938913dbce25d0ad9940 admin-dashboard diff --git a/bin/admin-dashboard b/bin/admin-dashboard index 061bcd0..84f43f2 100755 Binary files a/bin/admin-dashboard and b/bin/admin-dashboard differ diff --git a/internal/dashboard/handlers_ips.go b/internal/dashboard/handlers_ips.go index 13764e3..53ff12c 100644 --- a/internal/dashboard/handlers_ips.go +++ b/internal/dashboard/handlers_ips.go @@ -9,7 +9,6 @@ type ipsPageData struct { PageData Items []ipQueueItem FIPSettleSeconds int - PollSeconds int } type ipDetailData struct { @@ -23,20 +22,12 @@ func (s *Server) handleIPsPage(w http.ResponseWriter, r *http.Request) { if err == nil { err = settingsErr } - data := ipsPageData{Items: items, FIPSettleSeconds: settings.FIPSettleSeconds, PollSeconds: s.Cfg.OverviewPollIntervalS} + data := ipsPageData{Items: items, FIPSettleSeconds: settings.FIPSettleSeconds} data.ActiveNav = "ips" data.Banner = bannerFor(err) s.renderPage(w, "ips_page", data) } -// handleIPsFragment re-renders just the ips_table fragment, for the -// periodic auto-refresh poll (see ips_content's hx-trigger="every Ns") — -// identical to a mutating action's post-update render, just with no -// actionErr of its own. -func (s *Server) handleIPsFragment(w http.ResponseWriter, r *http.Request) { - s.renderIPsTable(w, r, nil) -} - func (s *Server) handleIPDetail(w http.ResponseWriter, r *http.Request) { ip := r.PathValue("ip") detail, err := s.CA.GetIP(r.Context(), ip) diff --git a/internal/dashboard/handlers_test.go b/internal/dashboard/handlers_test.go index ffefa86..63d1205 100644 --- a/internal/dashboard/handlers_test.go +++ b/internal/dashboard/handlers_test.go @@ -155,42 +155,24 @@ func TestIPsDeleteSelected(t *testing.T) { } } -func TestIPsPagePollsForFragment(t *testing.T) { +// TestIPsRecheckSelectedClearsCheckboxes proves a successful bulk recheck +// re-renders the table with plain, unchecked checkboxes (no hx-preserve), +// so a user sees their selection cleared once the action has been applied +// rather than it silently staying checked. +func TestIPsRecheckSelectedClearsCheckboxes(t *testing.T) { fake, caURL := newFakeControlAPI(t) now := time.Now() fake.ips = []ipQueueItem{ - {IPAddress: "1.1.1.1", State: "done", OverallResult: "pass", AggregatedAt: &now, UpdatedAt: now, CreatedAt: now}, + {IPAddress: "9.9.9.9", State: "done", OverallResult: "pass", AttemptNumber: 1, AggregatedAt: &now, UpdatedAt: now, CreatedAt: now}, } ts := newTestServer(t, caURL) - body := get(t, ts, "/ips") - if !strings.Contains(body, `hx-get="/ips/fragment"`) || !strings.Contains(body, `hx-trigger="every 5s"`) { - t.Fatalf("expected ips-table-wrap to auto-poll every 5s, got:\n%s", body) + body := postForm(t, ts, "POST", "/ips/recheck", map[string][]string{"addresses": {"9.9.9.9"}}) + if strings.Contains(body, "hx-preserve") { + t.Fatalf("expected no hx-preserve on checkboxes after recheck, got:\n%s", body) } - // hx-sync must coordinate the poll with every action that also swaps - // #ips-table-wrap (bulk buttons, per-row buttons, the add-address - // form) so a slow mutation and a poll tick can never race and let a - // stale response clobber a just-applied action (recheck-selected - // appearing to silently fail with many rows selected) — 1 wrapper div - // + 1 add-address form + 3 bulk buttons + 2 per-row buttons (one - // terminal row: "Перепроверить" + "Удалить") = 7. - if got := strings.Count(body, `hx-sync="#ips-table-wrap:queue last"`); got != 7 { - t.Fatalf("expected 7 elements to declare hx-sync, got %d in:\n%s", got, body) - } -} - -func TestIPsFragment(t *testing.T) { - fake, caURL := newFakeControlAPI(t) - now := time.Now() - fake.ips = []ipQueueItem{ - {IPAddress: "1.1.1.1", State: "queued", UpdatedAt: now, CreatedAt: now}, - {IPAddress: "2.2.2.2", State: "checking", UpdatedAt: now, CreatedAt: now}, - } - ts := newTestServer(t, caURL) - - body := get(t, ts, "/ips/fragment") - if !strings.Contains(body, "1.1.1.1") || !strings.Contains(body, "2.2.2.2") { - t.Fatalf("expected both addresses in fragment, got:\n%s", body) + if !strings.Contains(body, ``) { + t.Fatalf("expected a plain, unchecked checkbox for 9.9.9.9 after recheck, got:\n%s", body) } } diff --git a/internal/dashboard/routes.go b/internal/dashboard/routes.go index dffdbb9..8c9f05d 100644 --- a/internal/dashboard/routes.go +++ b/internal/dashboard/routes.go @@ -9,7 +9,6 @@ func (s *Server) routes(mux *http.ServeMux) { mux.HandleFunc("GET /overview/fragment", s.handleOverviewFragment) mux.HandleFunc("GET /ips", s.handleIPsPage) - mux.HandleFunc("GET /ips/fragment", s.handleIPsFragment) mux.HandleFunc("GET /ips/{ip}", s.handleIPDetail) mux.HandleFunc("POST /ips", s.handleIPsSubmit) mux.HandleFunc("POST /ips/{ip}/recheck", s.handleIPRecheck) diff --git a/internal/dashboard/templates/ips.html b/internal/dashboard/templates/ips.html index 1dcb4fd..2a20843 100644 --- a/internal/dashboard/templates/ips.html +++ b/internal/dashboard/templates/ips.html @@ -38,7 +38,7 @@ -
+
{{template "ips_table" .}}
{{end}} @@ -59,7 +59,7 @@ {{$b := ipBadge .State .OverallResult .FIPAssociatedAt $.FIPSettleSeconds}} {{$terminal := or (eq .State "done") (eq .State "failed") (eq .State "occupied")}} - + {{.IPAddress}} {{$b.Label}} {{deref .OwnerValidatorID}}