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:
1 parent
068c10ea1c
commit
ded196ec8d
40 files changed
+2545
-188
No files matched your search
@@ -1,7 +1,9 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -167,3 +169,102 @@ func TestRegistryLevelsLatestCycleAndManyAddresses(t *testing.T) {
|
||||
t.Errorf("after delete: %+v", s)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRegistryPageSlice covers the run / level / protocol filters of
|
||||
// ListRegistryPage together with the status and the subnet. Run 1 holds three
|
||||
// addresses (cycle 1), run 2 only a re-check of .1 (cycle 2) whose result
|
||||
// differs from run 1.
|
||||
func TestRegistryPageSlice(t *testing.T) {
|
||||
d, ctx := newTestDB(t)
|
||||
const a1, a2, a3 = "10.0.0.1", "10.0.0.2", "10.0.1.1"
|
||||
submit(t, d, RunManual, a1, a2, a3)
|
||||
for _, c := range []struct {
|
||||
addr, source, typ string
|
||||
ok bool
|
||||
}{
|
||||
{a1, SourceEgress, "https", true}, {a1, SourceEgress, "icmp", true},
|
||||
{a1, InboundSource(1), "tcp-22", true}, {a1, InboundSource(1), "tcp-443", false}, {a1, InboundSource(1), "tls-443", false},
|
||||
{a2, SourceEgress, "https", false},
|
||||
{a2, InboundSource(1), "tcp-443", true}, {a2, InboundSource(1), "tls-443", true}, {a2, InboundSource(1), "ssh", true},
|
||||
{a3, InboundSource(1), "icmp", true},
|
||||
} {
|
||||
addCheck(t, d, c.addr, c.source, c.typ, c.addr, c.ok)
|
||||
}
|
||||
finish(t, d, a1, ResultPartial, -1)
|
||||
finish(t, d, a2, ResultPartial, -1)
|
||||
finish(t, d, a3, ResultPass, -1)
|
||||
run1 := runs(t, d)[0].ID
|
||||
|
||||
submit(t, d, RunManual, a1)
|
||||
addCheck(t, d, a1, SourceEgress, "https", a1, false)
|
||||
addCheck(t, d, a1, InboundSource(1), "tcp-443", a1, true)
|
||||
addCheck(t, d, a1, InboundSource(1), "tls-443", a1, true)
|
||||
finish(t, d, a1, ResultPartial, -1)
|
||||
run2 := runs(t, d)[0].ID
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
f RegistryFilter
|
||||
want []string
|
||||
}{
|
||||
{"no filter", RegistryFilter{}, []string{a1, a2, a3}},
|
||||
{"run 1", RegistryFilter{RunID: run1}, []string{a1, a2, a3}},
|
||||
{"run 2", RegistryFilter{RunID: run2}, []string{a1}},
|
||||
{"unknown run", RegistryFilter{RunID: 999}, nil},
|
||||
{"egress, newest cycle", RegistryFilter{Level: LevelEgress}, []string{a1, a2}},
|
||||
{"ingress, newest cycle", RegistryFilter{Level: LevelIngress}, []string{a1, a2, a3}},
|
||||
{"verdict of the run", RegistryFilter{RunID: run1, LastResult: ResultPartial}, []string{a1, a2}},
|
||||
{"cancelled in the run", RegistryFilter{RunID: run1, LastResult: ResultCancelled}, nil},
|
||||
{"tcp-22 and tcp-443 are tcp", RegistryFilter{RunID: run1, Family: "tcp"}, []string{a1, a2}},
|
||||
{"tcp partial", RegistryFilter{RunID: run1, Family: "tcp", LastResult: ResultPartial}, []string{a1}},
|
||||
{"tcp pass", RegistryFilter{RunID: run1, Family: "tcp", LastResult: ResultPass}, []string{a2}},
|
||||
{"tcp fail", RegistryFilter{RunID: run1, Family: "tcp", LastResult: ResultFail}, nil},
|
||||
{"tls fail in run 1", RegistryFilter{RunID: run1, Family: "tls", LastResult: ResultFail}, []string{a1}},
|
||||
{"tls pass in run 2", RegistryFilter{RunID: run2, Family: "tls", LastResult: ResultPass}, []string{a1}},
|
||||
{"tls on egress is empty", RegistryFilter{RunID: run1, Level: LevelEgress, Family: "tls"}, nil},
|
||||
{"egress status is of egress checks", RegistryFilter{RunID: run1, Level: LevelEgress, LastResult: ResultPass}, []string{a1}},
|
||||
{"egress fail", RegistryFilter{RunID: run1, Level: LevelEgress, LastResult: ResultFail}, []string{a2}},
|
||||
{"cancelled has no scope", RegistryFilter{Level: LevelEgress, LastResult: ResultCancelled}, nil},
|
||||
{"icmp on ingress", RegistryFilter{RunID: run1, Level: LevelIngress, Family: "icmp"}, []string{a3}},
|
||||
{"subnet, run and level", RegistryFilter{RunID: run1, Level: LevelIngress, Subnet: "10.0.0.0/24"}, []string{a1, a2}},
|
||||
{"subnet with no checks of the level", RegistryFilter{RunID: run1, Level: LevelEgress, Subnet: "10.0.1.0/24"}, nil},
|
||||
{"query and level", RegistryFilter{Query: ".2", Level: LevelEgress}, []string{a2}},
|
||||
} {
|
||||
page, total, err := d.ListRegistryPage(ctx, tc.f, 50, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: %v", tc.name, err)
|
||||
}
|
||||
var got []string
|
||||
for _, s := range page {
|
||||
got = append(got, s.IPAddress)
|
||||
}
|
||||
sort.Strings(got)
|
||||
if total != len(tc.want) || !reflect.DeepEqual(got, tc.want) {
|
||||
t.Errorf("%s: total=%d got=%v, want %v", tc.name, total, got, tc.want)
|
||||
}
|
||||
}
|
||||
|
||||
// The same address shows its own result in each run, narrowed to the scope.
|
||||
for run, want := range map[int64]struct {
|
||||
cycle int
|
||||
result LevelResult
|
||||
}{
|
||||
run1: {1, LevelResult{Total: 1, OK: 0, ByType: []TypeStat{{"tls", 1, 0}}}},
|
||||
run2: {2, LevelResult{Total: 1, OK: 1, ByType: []TypeStat{{"tls", 1, 1}}}},
|
||||
} {
|
||||
page, _, err := d.ListRegistryPage(ctx, RegistryFilter{RunID: run, Query: a1, Family: "tls"}, 10, 0)
|
||||
if err != nil || len(page) != 1 {
|
||||
t.Fatalf("run %d: %v %+v", run, err, page)
|
||||
}
|
||||
s := page[0]
|
||||
if s.LastCycleID != want.cycle || s.LastResult != ResultPartial || s.Egress.Total != 0 || !reflect.DeepEqual(s.Ingress, want.result) {
|
||||
t.Errorf("run %d: cycle=%d result=%q egress=%+v ingress=%+v", run, s.LastCycleID, s.LastResult, s.Egress, s.Ingress)
|
||||
}
|
||||
}
|
||||
|
||||
for _, f := range []RegistryFilter{{Level: "sideways"}, {Family: "dns"}} {
|
||||
if _, _, err := d.ListRegistryPage(ctx, f, 10, 0); !errors.Is(err, ErrValidation) {
|
||||
t.Errorf("%+v: %v", f, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user