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
@@ -244,8 +244,10 @@ func (c *client) ScanStatus(ctx context.Context) (scanStatusDTO, error) {
|
||||
type registryQuery struct {
|
||||
Q string
|
||||
LastResult string
|
||||
Run int64 // only addresses with a result in this run
|
||||
Run int64 // only addresses with a result in this run; the results shown are the run's
|
||||
Subnet string // only addresses inside this CIDR
|
||||
Direction string // egress|ingress — only checks of this direction
|
||||
Protocol string // icmp|tcp|ssh|https|tls — only checks of this protocol
|
||||
Limit int
|
||||
Offset int
|
||||
}
|
||||
@@ -255,11 +257,20 @@ type registryQuery struct {
|
||||
// history — survives an address being deleted from the queue and later
|
||||
// re-added) plus the total number of rows matching the filter.
|
||||
func (c *client) ListRegistryPage(ctx context.Context, q registryQuery) (registryPage, error) {
|
||||
v := url.Values{}
|
||||
v := q.filter()
|
||||
v.Set("limit", strconv.Itoa(clampLimit(q.Limit)))
|
||||
if q.Offset > 0 {
|
||||
v.Set("offset", strconv.Itoa(q.Offset))
|
||||
}
|
||||
var out registryPage
|
||||
err := c.do(ctx, http.MethodGet, "/api/v1/admin/registry?"+v.Encode(), nil, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// filter is the filter part of the query (everything but the page) as the
|
||||
// parameters GET /admin/registry and its breakdown endpoints take.
|
||||
func (q registryQuery) filter() url.Values {
|
||||
v := url.Values{}
|
||||
if q.Q != "" {
|
||||
v.Set("q", q.Q)
|
||||
}
|
||||
@@ -272,11 +283,52 @@ func (c *client) ListRegistryPage(ctx context.Context, q registryQuery) (registr
|
||||
if q.Subnet != "" {
|
||||
v.Set("subnet", q.Subnet)
|
||||
}
|
||||
var out registryPage
|
||||
err := c.do(ctx, http.MethodGet, "/api/v1/admin/registry?"+v.Encode(), nil, &out)
|
||||
if q.Direction != "" {
|
||||
v.Set("direction", q.Direction)
|
||||
}
|
||||
if q.Protocol != "" {
|
||||
v.Set("protocol", q.Protocol)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// registryBreakdownPath is the path of GET /admin/registry/breakdown, or of its
|
||||
// list of one row (key) when list is set, as a CSV file when csv is.
|
||||
func registryBreakdownPath(q registryQuery, key string, list, csv bool) string {
|
||||
v := q.filter()
|
||||
p := "/api/v1/admin/registry/breakdown"
|
||||
if list {
|
||||
p += "/list"
|
||||
v.Set("key", key)
|
||||
if csv {
|
||||
v.Set("format", "csv")
|
||||
}
|
||||
}
|
||||
return p + "?" + v.Encode()
|
||||
}
|
||||
|
||||
// GetRegistryBreakdown returns the checks of the query's direction and protocol
|
||||
// per target or site, over the addresses the query selects.
|
||||
func (c *client) GetRegistryBreakdown(ctx context.Context, q registryQuery) (registryBreakdown, error) {
|
||||
var out registryBreakdown
|
||||
err := c.do(ctx, http.MethodGet, registryBreakdownPath(q, "", false, false), nil, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// GetRegistryBreakdownList returns the table (JSON) of the checks behind one
|
||||
// row of the breakdown.
|
||||
func (c *client) GetRegistryBreakdownList(ctx context.Context, q registryQuery, key string) (json.RawMessage, error) {
|
||||
var out json.RawMessage
|
||||
err := c.do(ctx, http.MethodGet, registryBreakdownPath(q, key, true, false), nil, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// GetRegistryBreakdownCSV returns the CSV file of that table, with the file
|
||||
// name control-api proposed.
|
||||
func (c *client) GetRegistryBreakdownCSV(ctx context.Context, q registryQuery, key string) ([]byte, string, error) {
|
||||
return c.getCSV(ctx, registryBreakdownPath(q, key, true, true))
|
||||
}
|
||||
|
||||
// GetRegistryHistory returns one address's registry record plus its full
|
||||
// retained check history across every cycle still kept.
|
||||
func (c *client) GetRegistryHistory(ctx context.Context, ip string) (registryHistoryResponse, error) {
|
||||
@@ -423,19 +475,27 @@ func (c *client) ListAnalyticsRuns(ctx context.Context) ([]analyticsRun, error)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// GetAnalyticsReport returns the analytics of one finished run as the raw
|
||||
// JSON control-api computed; the page's script reads it as it is.
|
||||
func (c *client) GetAnalyticsReport(ctx context.Context, runID int64) (json.RawMessage, error) {
|
||||
// GetAnalyticsReport returns the analytics of one finished run (of the
|
||||
// addresses inside subnet, unless it is empty) as the raw JSON control-api
|
||||
// computed; the page's script reads it as it is.
|
||||
func (c *client) GetAnalyticsReport(ctx context.Context, runID int64, subnet string) (json.RawMessage, error) {
|
||||
var out json.RawMessage
|
||||
err := c.do(ctx, http.MethodGet, "/api/v1/admin/analytics/runs/"+strconv.FormatInt(runID, 10), nil, &out)
|
||||
p := "/api/v1/admin/analytics/runs/" + strconv.FormatInt(runID, 10)
|
||||
if subnet != "" {
|
||||
p += "?" + url.Values{"subnet": {subnet}}.Encode()
|
||||
}
|
||||
err := c.do(ctx, http.MethodGet, p, nil, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func analyticsListPath(runID int64, kind, class string, csv bool) string {
|
||||
func analyticsListPath(runID int64, kind, class, subnet string, csv bool) string {
|
||||
v := url.Values{}
|
||||
if class != "" {
|
||||
v.Set("class", class)
|
||||
}
|
||||
if subnet != "" {
|
||||
v.Set("subnet", subnet)
|
||||
}
|
||||
if csv {
|
||||
v.Set("format", "csv")
|
||||
}
|
||||
@@ -446,17 +506,18 @@ func analyticsListPath(runID int64, kind, class string, csv bool) string {
|
||||
return p
|
||||
}
|
||||
|
||||
// GetAnalyticsList returns one address table (JSON) of a run.
|
||||
func (c *client) GetAnalyticsList(ctx context.Context, runID int64, kind, class string) (json.RawMessage, error) {
|
||||
// GetAnalyticsList returns one address table (JSON) of a run, narrowed to
|
||||
// subnet unless it is empty.
|
||||
func (c *client) GetAnalyticsList(ctx context.Context, runID int64, kind, class, subnet string) (json.RawMessage, error) {
|
||||
var out json.RawMessage
|
||||
err := c.do(ctx, http.MethodGet, analyticsListPath(runID, kind, class, false), nil, &out)
|
||||
err := c.do(ctx, http.MethodGet, analyticsListPath(runID, kind, class, subnet, false), nil, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// GetAnalyticsListCSV returns the CSV file of one address table, with the
|
||||
// file name control-api proposed.
|
||||
func (c *client) GetAnalyticsListCSV(ctx context.Context, runID int64, kind, class string) ([]byte, string, error) {
|
||||
return c.getCSV(ctx, analyticsListPath(runID, kind, class, true))
|
||||
func (c *client) GetAnalyticsListCSV(ctx context.Context, runID int64, kind, class, subnet string) ([]byte, string, error) {
|
||||
return c.getCSV(ctx, analyticsListPath(runID, kind, class, subnet, true))
|
||||
}
|
||||
|
||||
func (c *client) getCSV(ctx context.Context, path string) ([]byte, string, error) {
|
||||
|
||||
Reference in new issue
Block a user