Analytics: make the pass, partial and fail indicators clickable
The three verdict cards on /analytics now open the same dialog as the https/ssh
cards, with the addresses of the run that got this verdict (CSV and copy
included). New list kinds verdict_pass, verdict_partial and verdict_fail in
GET /admin/analytics/runs/{id}/lists/{kind}: address, subnet, validator,
egress and ingress "ok of all", checks stored of expected; partial adds the
reason, the same names as the "Why partial" block. Cancelled addresses are not
listed; the row count equals summary.pass/partial/fail. The fail card stays
inert at zero. addr.incomplete() is shared by the list and the report.
Docs, plan and summary in docs/changes/.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e95b5eb7d5
commit
2f038f8362
15 files changed
+294
-14
No files matched your search
@@ -16,6 +16,10 @@ const (
|
||||
ListIngressSSHAny = "ingress_ssh_any"
|
||||
ListIngressSSHAll = "ingress_ssh_all"
|
||||
ListError = "error"
|
||||
// Addresses by the verdict the system gave them at aggregation.
|
||||
ListVerdictPass = "verdict_" + db.ResultPass
|
||||
ListVerdictPartial = "verdict_" + db.ResultPartial
|
||||
ListVerdictFail = "verdict_" + db.ResultFail
|
||||
)
|
||||
|
||||
// List is a table of addresses behind one indicator or one error class.
|
||||
@@ -73,6 +77,22 @@ func (an *Analysis) List(kind, class string) (*List, error) {
|
||||
l.Rows = append(l.Rows, []string{a.res.IPAddress, a.subnet, strings.Join(sites, ", "), strings.Join(errs, ", ")})
|
||||
}
|
||||
}
|
||||
case ListVerdictPass, ListVerdictPartial, ListVerdictFail:
|
||||
verdict := strings.TrimPrefix(kind, "verdict_")
|
||||
l.Columns = []string{"Адрес", "Подсеть", "Валидатор", "Egress", "Ingress", "Проверок в цикле"}
|
||||
if kind == ListVerdictPartial {
|
||||
l.Columns = append(l.Columns, "Причина")
|
||||
}
|
||||
for _, a := range an.sorted() {
|
||||
if a.res.Verdict != verdict {
|
||||
continue
|
||||
}
|
||||
row := []string{a.res.IPAddress, a.subnet, orDash(ShortValidator(a.https.validator)), okOf(a.egress), okOf(a.ingress), a.checksCell()}
|
||||
if kind == ListVerdictPartial {
|
||||
row = append(row, reasonName(a.egress.ok < a.egress.n, a.ingress.ok < a.ingress.n, a.incomplete()))
|
||||
}
|
||||
l.Rows = append(l.Rows, row)
|
||||
}
|
||||
case ListError:
|
||||
if class == "" {
|
||||
return nil, ErrUnknownList("error without class")
|
||||
@@ -98,6 +118,29 @@ func (an *Analysis) List(kind, class string) (*List, error) {
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// checksCell is "stored из expected", or just stored when expected is unknown.
|
||||
func (a *addr) checksCell() string {
|
||||
if a.res.ExpectedChecks < 0 {
|
||||
return fmt.Sprint(a.stored)
|
||||
}
|
||||
return fmt.Sprintf("%d из %d", a.stored, a.res.ExpectedChecks)
|
||||
}
|
||||
|
||||
// okOf is "successful из all" of one level, "—" when it has no checks.
|
||||
func okOf(t typeStat) string {
|
||||
if t.n == 0 {
|
||||
return "—"
|
||||
}
|
||||
return fmt.Sprintf("%d из %d", t.ok, t.n)
|
||||
}
|
||||
|
||||
func orDash(s string) string {
|
||||
if s == "" {
|
||||
return "—"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// sorted returns the non-cancelled addresses in numeric address order.
|
||||
func (an *Analysis) sorted() []*addr {
|
||||
out := make([]*addr, 0, len(an.addrs))
|
||||
|
||||
Reference in new issue
Block a user