Registry and Analytics: run, subnet, direction and protocol filters, successes-by-target chart

Registry (/registry):
- filters by run (slice by the address's cycle in that run), subnet
  (drop-down of configured subnets), direction (egress/ingress) and
  protocol (icmp, tcp, ssh, https, tls); status in scope is computed over
  the narrowed checks
- chart "successful checks per target (egress) / site (ingress)" when both
  direction and protocol are chosen; a row opens the list of addresses
  (dialog, CSV)
- API: direction/protocol parameters and run in GET /admin/registry,
  GET /admin/registry/breakdown and /breakdown/list
- subnet filter passes ids as one JSON parameter (SQLite variable limit)

Analytics (/analytics):
- subnet filter recomputes the whole page over the addresses of the run
  inside the subnet; only their checks are read; cache per run and subnet
- direction and protocol focus the page; with both set the registry chart
  is shown
- subnet parameter in GET /admin/analytics/runs/{id} and lists (JSON, CSV)

Docs: plans and summaries in docs/changes, README, API, USAGE.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ayurishchevandClaude Sonnet 5.5 committed 2026-10-06 14:23:48 +03:00
1 parent 068c10ea1c
commit ded196ec8d
40 files changed
+2545 -188

No files matched your search

+25
View File
@@ -22,6 +22,9 @@ type Input struct {
Subnets []db.Subnet
SiteNames map[int]string // site index -> site id
Rechecked int // addresses with more than one cycle in the run
// Subnet narrows the report to the addresses of the run inside it, nested
// subnets included; the zero prefix means the whole run.
Subnet netip.Prefix
// Each feeds every check of the run's result cycles to fn.
Each func(fn func(db.RunCheck)) error
}
@@ -29,6 +32,7 @@ type Input struct {
// Report is the data of the analytics page for one run.
type Report struct {
Run RunInfo `json:"run"`
Scope *Scope `json:"scope,omitempty"` // set when the report is narrowed to a subnet
Summary Summary `json:"summary"`
Reasons []Reason `json:"reasons"`
Quality Quality `json:"quality"`
@@ -50,6 +54,14 @@ type RunInfo struct {
Rechecked int `json:"rechecked"`
}
// Scope says which part of the run a narrowed report covers: Addresses of
// Summary are those inside Subnet, RunAddresses all of the run (cancelled
// ones excluded in both).
type Scope struct {
Subnet string `json:"subnet"`
RunAddresses int `json:"run_addresses"`
}
type Summary struct {
Addresses int `json:"addresses"`
Pass int `json:"pass"`
@@ -176,7 +188,17 @@ func Compute(in Input) (*Analysis, error) {
byReg := make(map[int64]*addr, len(in.Results))
var addrs []*addr
subnetOf := newSubnetMatcher(in.Subnets)
runAddrs := 0
for _, r := range in.Results {
if r.Verdict != db.ResultCancelled {
runAddrs++
}
if in.Subnet.IsValid() {
// checks of an address outside the subnet find no entry in byReg below
if ip, err := netip.ParseAddr(r.IPAddress); err != nil || !in.Subnet.Contains(ip) {
continue
}
}
a := &addr{res: r, subnet: subnetOf(r.IPAddress), failedTargets: map[string]bool{}}
a.ssh.errs = map[string]bool{}
byReg[r.RegistryID] = a
@@ -279,6 +301,9 @@ func Compute(in Input) (*Analysis, error) {
rep := Report{Matrix: map[string][]MatrixRow{}}
rep.Run = RunInfo{ID: in.Run.ID, Kind: in.Run.Kind, State: in.Run.State, StartedAt: in.Run.StartedAt,
FinalizedAt: in.Run.FinalizedAt, Rechecked: in.Rechecked}
if in.Subnet.IsValid() {
rep.Scope = &Scope{Subnet: in.Subnet.Masked().String(), RunAddresses: runAddrs}
}
if in.Run.FinalizedAt != nil {
rep.Run.DurationSec = int(in.Run.FinalizedAt.Sub(in.Run.StartedAt).Seconds())
}