2026-09-23 09:52:01 +03:00
{{define "registry_page"}}
<!doctype html>
< html lang = "ru" >
2026-10-06 14:23:48 +03:00
< head > {{template "html_head" .}}
< link rel = "stylesheet" href = "/static/analytics.css" >
</ head >
2026-09-23 09:52:01 +03:00
< 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 "registry_content" .}}
</ main >
</ div >
</ div >
2026-10-06 14:23:48 +03:00
{{/* The list behind a row of the chart opens in the analytics dialog. */}}
< div class = "an" > {{template "analytics_dialog"}}</ div >
< script src = "/static/analytics-dialog.js" ></ script >
< script src = "/static/registry.js" ></ script >
2026-09-23 09:52:01 +03:00
</ body >
</ html >
{{end}}
{{define "registry_content"}}
< div class = "topbar" >< h1 > Реестр IP</ h1 ></ div >
< p class = "muted" style = "margin-bottom:16px" > Все адреса, когда-либо поставленные на проверку — накопленная статистика
сохраняется здесь даже после удаления адреса из < a href = "/ips" > очереди</ a > и не теряется при повторном добавлении.
Глубина хранимой истории на адрес настраивается на < a href = "/settings" > странице настроек</ a > .</ p >
2026-09-23 11:58:53 +03:00
< form id = "registry-filter" class = "panel" onsubmit = "return false" style = "margin-bottom:16px" >
< div class = "panel-body field-row" >
< div class = "field" style = "flex:1 1 260px" >
< label for = "registry-q" > Поиск по IP</ label >
< input type = "search" id = "registry-q" name = "q" value = "{{.Query}}" placeholder = "203.0.113.10"
hx-get = "/registry" hx-select = "#registry-table-wrap" hx-target = "#registry-table-wrap" hx-swap = "outerHTML"
hx-include = "#registry-filter" hx-trigger = "input changed delay:300ms"
hx-replace-url = "true" hx-sync = "#registry-table-wrap:queue last" >
</ div >
2026-10-06 14:23:48 +03:00
< div class = "field" style = "flex:1 1 300px" >
< label for = "registry-run" > Запуск</ label >
< select id = "registry-run" name = "run"
hx-get = "/registry" hx-select = "#registry-table-wrap" hx-target = "#registry-table-wrap" hx-swap = "outerHTML"
hx-include = "#registry-filter" hx-trigger = "change"
hx-replace-url = "true" hx-sync = "#registry-table-wrap:queue last" >
< option value = "" > Последний цикл (по умолчанию)</ option >
{{range .Runs}}< option value = "{{.ID}}" {{ if . Selected }} selected {{ end }} > {{.Label}}</ option >
{{end}}
</ select >
</ div >
< div class = "field" style = "flex:1 1 200px" >
< label for = "registry-subnet" > Подсеть</ label >
< select id = "registry-subnet" name = "subnet" {{ if not . SubnetOptions }} disabled {{ end }}
hx-get = "/registry" hx-select = "#registry-table-wrap" hx-target = "#registry-table-wrap" hx-swap = "outerHTML"
hx-include = "#registry-filter" hx-trigger = "change"
hx-replace-url = "true" hx-sync = "#registry-table-wrap:queue last" >
< option value = "" > Все подсети</ option >
{{range .SubnetOptions}}< option value = "{{.CIDR}}" {{ if . Selected }} selected {{ end }} > {{.Text}}</ option >
{{end}}
</ select >
{{if not .SubnetOptions}}< span class = "muted" style = "font-size:12px" > Подсети не настроены — < a href = "/settings" > добавить в настройках</ a ></ span > {{end}}
</ div >
< div class = "field" >
< label for = "registry-direction" > Направление</ label >
< select id = "registry-direction" name = "direction"
hx-get = "/registry" hx-select = "#registry-table-wrap" hx-target = "#registry-table-wrap" hx-swap = "outerHTML"
hx-include = "#registry-filter" hx-trigger = "change"
hx-replace-url = "true" hx-sync = "#registry-table-wrap:queue last" >
< 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 = "field" >
< label for = "registry-protocol" > Протокол</ label >
< select id = "registry-protocol" name = "protocol"
hx-get = "/registry" hx-select = "#registry-table-wrap" hx-target = "#registry-table-wrap" hx-swap = "outerHTML"
hx-include = "#registry-filter" hx-trigger = "change"
hx-replace-url = "true" hx-sync = "#registry-table-wrap:queue last" >
< option value = "" > Все</ option >
{{$pr := .Protocol}}
{{range $p := .Protocols}}< option value = "{{$p}}" {{ if eq $ p $ pr }} selected {{ end }} > {{$p}}</ option >
{{end}}
</ select >
</ div >
2026-09-23 11:58:53 +03:00
< div class = "field" >
< label for = "registry-status" > Статус</ label >
< select id = "registry-status" name = "status"
hx-get = "/registry" hx-select = "#registry-table-wrap" hx-target = "#registry-table-wrap" hx-swap = "outerHTML"
hx-include = "#registry-filter" hx-trigger = "change"
hx-replace-url = "true" hx-sync = "#registry-table-wrap:queue last" >
< option value = "" > Все</ option >
< option value = "pass" {{ if eq . StatusFilter " pass "}} selected {{ end }} > pass</ option >
< option value = "partial" {{ if eq . StatusFilter " partial "}} selected {{ end }} > partial</ option >
< option value = "fail" {{ if eq . StatusFilter " fail "}} selected {{ end }} > fail</ option >
< option value = "cancelled" {{ if eq . StatusFilter " cancelled "}} selected {{ end }} > cancelled</ option >
</ select >
</ div >
2026-10-01 19:31:11 +03:00
< div class = "field" style = "flex:0 0 120px" >
< label for = "registry-per-page" > На странице</ label >
< select id = "registry-per-page" name = "per_page"
hx-get = "/registry" hx-select = "#registry-table-wrap" hx-target = "#registry-table-wrap" hx-swap = "outerHTML"
hx-include = "#registry-filter" hx-trigger = "change"
hx-replace-url = "true" hx-sync = "#registry-table-wrap:queue last" >
{{$pp := .PerPage}}
{{range .PerPageOptions}}< option value = "{{.}}" {{ if eq . $ pp }} selected {{ end }} > {{.}}</ option >
{{end}}
</ select >
</ div >
2026-09-23 11:58:53 +03:00
</ div >
2026-10-06 14:23:48 +03:00
< div class = "panel-body muted" style = "padding-top:0;font-size:12.5px" >
Запуск задаёт срез: результат берётся по циклу адреса в этом запуске, а статус — по вердикту запуска.
Направление и протокол оставляют только такие проверки, и статус тогда считается по ним, а не по общему вердикту.
tls проверяется только на входе (Ingress).
< a href = "/registry" style = "margin-left:10px" > сбросить фильтры</ a >
</ div >
2026-09-23 11:58:53 +03:00
</ form >
< div id = "registry-table-wrap" >
{{template "registry_table" .}}
</ div >
{{end}}
2026-10-03 17:59:52 +03:00
{{define "registry_level"}}
< div class = "level" >
< span class = "level-name" > {{.Name}}</ span >
{{if .L.Total}}< span class = "stat stat-{{.L.Class}}" > {{.L.OK}} из {{.L.Total}}</ span >
< span class = "level-types" > {{range .L.ByType}}< span class = "chip chip-{{.Class}}" > {{.Type}} {{.OK}} из {{.Total}}</ span > {{end}}</ span >
{{else}}< span class = "stat stat-none" > —</ span > {{end}}
</ div >
{{end}}
2026-10-06 14:23:48 +03:00
{{define "registry_breakdown"}}
{{if .Hint}}< p class = "muted" style = "margin-bottom:12px" > {{.Hint}}</ p >
{{else}}
< div class = "an bd-wrap" >
< section class = "an-panel" id = "registry-breakdown" aria-labelledby = "registry-breakdown-h" data-qs = "{{.QS}}" data-scope = "{{.Scope}}" data-slice = "{{.Slice}}" >
< h2 id = "registry-breakdown-h" > {{.Title}} · {{.Scope}}</ h2 >
{{if .Err}}< p class = "an-note" > {{.Err}}</ p >
{{else if not .Rows}}< p class = "an-note" > Для этого сочетания проверок нет.</ p >
{{else}}
< div class = "an-rows" >
{{range .Rows}}< button type = "button" class = "an-bar-row" data-bd-key = "{{.Key}}" data-bd-label = "{{.Label}}" aria-haspopup = "dialog" data-tip = "{{.Tip}}" aria-label = "{{.Tip}}" >< span class = "an-n" > {{.Label}}</ span >< div class = "an-track" >< div class = "an-fill" style = "width:{{.Width}}%" ></ div ></ div >< span class = "an-num" > {{.Text}}</ span ></ button >
{{end}}
</ div >
< p class = "an-note" > Адресов под фильтром: {{.Addresses}}. Срез: {{.Slice}}. Строки идут от большего числа успешных проверок к меньшему. Считаются проверки, а не адреса: у tcp и tls на ingress у адреса может быть по проверке на каждую площадку и порт (22, 443). Строка открывает список адресов.</ p >
{{end}}
</ section >
</ div >
{{end}}
{{end}}
2026-09-23 11:58:53 +03:00
{{define "registry_table"}}
2026-10-06 14:23:48 +03:00
{{with .Breakdown}}{{template "registry_breakdown" .}}{{end}}
{{if or .Items .Run}}< p class = "muted" style = "margin-bottom:8px" > Найдено адресов: {{.Total}}{{if .Run}} · < a href = "{{.AnalyticsURL}}" > к аналитике запуска {{.Run}}</ a > {{end}}</ p > {{end}}
2026-09-23 09:52:01 +03:00
{{if .Items}}
< div class = "panel" >
< div class = "table-scroll" >
< table >
2026-10-06 14:23:48 +03:00
< thead >< tr >< th > Адрес</ th >< th > Впервые замечен</ th >< th > Последний раз замечен</ th >< th > Циклов</ th >< th > {{if .Run}}Результат в запуске {{.Run}}{{else}}Последний результат{{end}}</ th >< th > Сейчас в очереди</ th ></ tr ></ thead >
2026-09-23 09:52:01 +03:00
< tbody >
{{range .Items}}
< tr >
< td class = "addr" data-label = "Адрес" >< a href = "/registry/{{.IPAddress}}" > {{.IPAddress}}</ a ></ td >
< td data-label = "Впервые замечен" > {{fmtTime .FirstSeenAt}}</ td >
< td data-label = "Последний раз замечен" > {{fmtTime .LastSeenAt}}</ td >
< td class = "num" data-label = "Циклов" > {{.TotalCycles}}</ td >
2026-10-06 14:23:48 +03:00
< td data-label = "{{if $.Run}}Результат в запуске {{$.Run}}{{else}}Последний результат{{end}}" >
2026-09-23 09:52:01 +03:00
{{if eq .LastResult "pass"}}< span class = "pill pill-success" > pass</ span >
2026-09-23 11:03:56 +03:00
{{else if eq .LastResult "partial"}}< span class = "pill pill-warning" > partial</ span >
2026-09-23 09:52:01 +03:00
{{else if eq .LastResult "fail"}}< span class = "pill pill-danger" > fail</ span >
2026-09-23 11:03:56 +03:00
{{else if eq .LastResult "cancelled"}}< span class = "pill pill-cancel" > cancelled</ span >
2026-09-23 09:52:01 +03:00
{{else}}< span class = "pill pill-neutral" > —</ span > {{end}}
2026-10-03 17:59:52 +03:00
{{if .LastCycleID}}< div class = "levels" title = "Считаются записанные проверки цикла {{.LastCycleID}}; вердикт учитывает ещё и недостающие результаты." >
2026-10-06 14:23:48 +03:00
{{if or (not $.Scoped) .Egress.Total}}{{template "registry_level" dict "Name" "Egress" "L" .Egress}}{{end}}
{{if or (not $.Scoped) .Ingress.Total}}{{template "registry_level" dict "Name" "Ingress" "L" .Ingress}}{{end}}
2026-10-03 17:59:52 +03:00
</ div > {{end}}
2026-09-23 09:52:01 +03:00
</ td >
< td data-label = "Сейчас в очереди" >
{{if .InQueue}}< span class = "pill pill-info" > {{.CurrentState}}</ span > {{else}}< span class = "pill pill-neutral" > нет</ span > {{end}}
</ td >
</ tr >
{{end}}
</ tbody >
</ table >
</ div >
2026-10-01 19:31:11 +03:00
{{template "pager" .Pager}}
2026-09-23 09:52:01 +03:00
</ div >
2026-10-06 14:23:48 +03:00
{{else if or .Query .StatusFilter .Run .Subnet .Direction .Protocol}}< p class = "muted" > Ничего не найдено по текущему фильтру.</ p >
2026-09-23 09:52:01 +03:00
{{else}}< p class = "muted" > Реестр пуст — ни один адрес ещё не ставился на проверку.</ p > {{end}}
{{end}}