Files
cloud-ip-validator/internal/dashboard/templates/analytics.html
T
ayurishchevandClaude Sonnet 5.5 ded196ec8d 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>
2026-10-06 14:23:48 +03:00

165 lines
8.1 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{{define "analytics_page"}}
<!doctype html>
<html lang="ru">
<head>{{template "html_head" .}}
<link rel="stylesheet" href="/static/analytics.css">
</head>
<body>
<div class="bg-grid"></div>
<input type="checkbox" id="nav-toggle" class="nav-toggle">
<div class="shell">
{{template "sidebar_nav" .}}
<div>
{{template "topbar_mobile" .}}
<main class="main">
<div id="error-banner">{{template "banner_inner" .Banner}}</div>
{{template "analytics_content" .}}
</main>
</div>
</div>
{{if .HasRun}}
<script type="application/json" id="analytics-data">{{.DataJSON}}</script>
<script src="/static/analytics-dialog.js"></script>
<script src="/static/registry.js"></script>
<script src="/static/analytics.js"></script>
{{end}}
</body>
</html>
{{end}}
{{define "analytics_content"}}
<div class="an">
<div class="an-head">
<h1 class="an-title">Аналитика</h1>
<p class="an-sub">Один выбранный запуск. Данные других запусков на странице не участвуют.</p>
</div>
{{if not .Runs}}
<section class="an-panel">
<p class="an-note">Запусков проверки пока нет. Они появляются, когда адреса ставятся в очередь на странице <a href="/ips">«Очередь IP»</a> или запускается автоматический цикл.</p>
</section>
{{else}}
<section class="an-panel" aria-label="Выбор запуска и фильтры">
<form id="an-filter" action="/analytics" method="get" onsubmit="return false">
{{/* a change goes to the page of the new filter; empty values stay out of the address */}}
<script>function anGo(f) { var q = new URLSearchParams(); new FormData(f).forEach(function (v, k) { if (v) q.set(k, v); }); location.href = '/analytics?' + q; }</script>
<div class="an-runbar">
<label for="an-run">Запуск</label>
{{if .PrevURL}}<a class="an-btn" href="{{.PrevURL}}" aria-label="Предыдущий запуск">◀</a>{{else}}<span class="an-btn" aria-disabled="true" style="opacity:.4">◀</span>{{end}}
<select id="an-run" name="run" onchange="if (this.value) anGo(this.form)">
{{range .Runs}}<option value="{{.ID}}"{{if .Selected}} selected{{end}}{{if .Disabled}} disabled{{end}}>{{.Label}}</option>
{{end}}
</select>
{{if .NextURL}}<a class="an-btn" href="{{.NextURL}}" aria-label="Следующий запуск">▶</a>{{else}}<span class="an-btn" aria-disabled="true" style="opacity:.4">▶</span>{{end}}
{{if .HasRun}}<a class="an-btn" href="{{.CompareURL}}">Сравнить с другим запуском</a>{{end}}
</div>
{{if .HasRun}}
<div class="an-fields">
<div class="an-field">
<label for="an-subnet">Подсеть</label>
<select id="an-subnet" name="subnet" onchange="anGo(this.form)"{{if not .SubnetOptions}} disabled{{end}}>
<option value="">Все подсети</option>
{{range .SubnetOptions}}<option value="{{.CIDR}}"{{if .Selected}} selected{{end}}>{{.Text}}</option>
{{end}}
</select>
{{if not .SubnetOptions}}<span class="an-note">Подсети не настроены — <a href="/settings">добавить в настройках</a></span>{{end}}
</div>
<div class="an-field">
<label for="an-direction">Направление</label>
<select id="an-direction" name="direction" onchange="anGo(this.form)">
<option value="">Все</option>
<option value="egress"{{if eq .Direction "egress"}} selected{{end}}>Egress</option>
<option value="ingress"{{if eq .Direction "ingress"}} selected{{end}}>Ingress</option>
</select>
</div>
<div class="an-field">
<label for="an-protocol">Протокол</label>
<select id="an-protocol" name="protocol" onchange="anGo(this.form)">
<option value="">Все</option>
{{$pr := .Protocol}}
{{range $p := .Protocols}}<option value="{{$p}}"{{if eq $p $pr}} selected{{end}}>{{$p}}</option>
{{end}}
</select>
</div>
{{if .Filtered}}<a class="an-btn an-reset" href="/analytics?run={{.RunID}}">сбросить фильтры</a>{{end}}
</div>
{{end}}
</form>
{{if .HasRun}}<p class="an-note" id="an-runnote"></p>
<p class="an-note">Подсеть пересчитывает все блоки страницы по её адресам. Направление и протокол задают фокус: скрывают блоки другого уровня и выбирают тип проверки, а вердикты и «Качество данных» остаются по всем проверкам запуска. С направлением и протоколом сразу показывается чарт.{{if .Breakdown}} Чарт считает проверки цикла запуска, в том числе пришедшие после вердикта, а плитки ниже берут вердикты запуска, поэтому числа могут расходиться.{{end}}</p>
{{else}}<p class="an-note">Завершённых запусков пока нет: данные появятся, когда все адреса запуска получат итог.</p>{{end}}
</section>
{{end}}
{{if .HasRun}}
{{with .Breakdown}}{{template "registry_breakdown" .}}{{end}}
<p class="an-note" id="an-empty" hidden></p>
<div class="an-body" id="an-body">
<div class="an-kpis" id="an-kpis"></div>
<div class="an-cols">
<section class="an-panel" aria-labelledby="an-h-reasons">
<h2 id="an-h-reasons">Почему partial</h2>
<div class="an-rows" id="an-reasons"></div>
<p class="an-note">Адрес считается один раз, по главной причине. Всё, где есть egress, учитывается как egress.</p>
</section>
<section class="an-panel" aria-labelledby="an-h-dq">
<h2 id="an-h-dq">Качество данных</h2>
<div class="an-dq" id="an-dq"></div>
<p class="an-note">Вердикт ставится при агрегации. Результаты, пришедшие позже, остаются в этом же запуске, но в вердикт не входят.</p>
</section>
</div>
<section class="an-panel" aria-labelledby="an-h-sub">
<div class="an-head">
<h2 id="an-h-sub">Подсети</h2>
<div class="an-runbar">
<div class="an-tabs" role="group" aria-label="Сортировка подсетей">
<button type="button" data-sort="worst" aria-pressed="true">хуже всего</button>
<button type="button" data-sort="size" aria-pressed="false">больше адресов</button>
</div>
<button class="an-btn" id="an-allsub" type="button"></button>
</div>
</div>
<div class="an-scroll"><table id="an-subtbl"></table></div>
<p class="an-note">Строка ведёт в «Реестр» с фильтром по запуску и подсети.</p>
</section>
<section class="an-panel" id="an-sec-eg" aria-labelledby="an-h-eg">
<div class="an-head">
<h2 id="an-h-eg">Egress по целям</h2>
<div class="an-tabs" role="group" aria-label="Тип проверки" id="an-types"></div>
</div>
<div class="an-rows" id="an-targets"></div>
<div id="an-matrixwrap">
<div class="an-head"><h2 style="margin-top:6px">Подсеть × цель</h2>
<div class="an-legend" id="an-hlegend"></div></div>
<div class="an-scroll"><table class="an-heat" id="an-matrix"></table></div>
</div>
<p class="an-note" id="an-matrixnote"></p>
</section>
<div class="an-cols" id="an-sec-in">
<section class="an-panel" aria-labelledby="an-h-in">
<h2 id="an-h-in">Ingress по площадкам</h2>
<div class="an-scroll"><table id="an-sites"></table></div>
</section>
<section class="an-panel" aria-labelledby="an-h-err">
<h2 id="an-h-err">Классы ошибок ingress</h2>
<div class="an-rows" id="an-errs"></div>
</section>
</div>
<section class="an-panel" id="an-sec-val" aria-labelledby="an-h-val">
<h2 id="an-h-val">Валидаторы: доля провалов egress https</h2>
<div class="an-vals" id="an-vals" role="img" aria-label="Доля проваленных https-проверок по валидаторам"></div>
<div class="an-vlab"><span id="an-vfirst"></span><span id="an-vavg"></span><span id="an-vlast"></span></div>
<p class="an-note">Ровная полоса значит: проблема зависит от подсети адреса, а не от валидатора.</p>
</section>
</div>
{{template "analytics_dialog"}}
{{end}}
</div>
{{end}}