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 @@
|
||||
/* Analytics page: renders the report of one finished run (embedded as JSON in
|
||||
#analytics-data) and opens the address lists behind the indicators and the
|
||||
error classes in the shared dialog (analytics-dialog.js). No other run's data
|
||||
is on the page. */
|
||||
is on the page. The report is already narrowed to the chosen subnet; the
|
||||
direction and protocol only focus the page (blocks of the other level are
|
||||
hidden, the type tab and the sites column follow the protocol). */
|
||||
(function () {
|
||||
'use strict';
|
||||
var D = JSON.parse(document.getElementById('analytics-data').textContent);
|
||||
@@ -14,6 +16,7 @@
|
||||
function vshort(id) { var n = vnum(id); return n === null ? id : 'v' + n; }
|
||||
|
||||
var state = { sort: 'worst', all: false, type: R.targets.types.indexOf('https') >= 0 ? 'https' : (R.targets.types[0] || '') };
|
||||
if (R.targets.types.indexOf(M.protocol) >= 0) state.type = M.protocol;
|
||||
|
||||
/* ---- indicators ---- */
|
||||
function renderKpis() {
|
||||
@@ -124,11 +127,12 @@
|
||||
}
|
||||
|
||||
function renderSites() {
|
||||
var T = R.sites;
|
||||
$('an-sites').innerHTML = '<thead><tr><th>Площадка</th>' + T.types.map(function (t) { return '<th class="an-r">' + esc(t) + '</th>'; }).join('') + '</tr></thead><tbody>' +
|
||||
var T = R.sites, only = T.types.indexOf(M.protocol); // with a protocol only its column stays
|
||||
function shown(x, i) { return only < 0 || i === only; }
|
||||
$('an-sites').innerHTML = '<thead><tr><th>Площадка</th>' + T.types.filter(shown).map(function (t) { return '<th class="an-r">' + esc(t) + '</th>'; }).join('') + '</tr></thead><tbody>' +
|
||||
T.rows.map(function (row) {
|
||||
return '<tr><td class="an-a">' + esc(row.site) + '</td>' + row.stats.map(function (st, i) {
|
||||
return '<td class="an-r" ' + tipAttr(row.site + ' · ' + T.types[i] + ': успешно ' + fmt(st.ok) + ' из ' + fmt(st.total)) + '><span class="an-num">' + pct1(st.total - st.ok, st.total) + '%</span> <span class="an-note">провал</span></td>';
|
||||
return shown(st, i) ? '<td class="an-r" ' + tipAttr(row.site + ' · ' + T.types[i] + ': успешно ' + fmt(st.ok) + ' из ' + fmt(st.total)) + '><span class="an-num">' + pct1(st.total - st.ok, st.total) + '%</span> <span class="an-note">провал</span></td>' : '';
|
||||
}).join('') + '</tr>';
|
||||
}).join('') + '</tbody>';
|
||||
}
|
||||
@@ -206,14 +210,14 @@
|
||||
};
|
||||
|
||||
function listURL(base, kind, cls) {
|
||||
return base + encodeURIComponent(kind) + '?run=' + M.run_id + (cls ? '&class=' + encodeURIComponent(cls) : '');
|
||||
return base + encodeURIComponent(kind) + '?run=' + M.run_id + (cls ? '&class=' + encodeURIComponent(cls) : '') + (M.subnet ? '&subnet=' + encodeURIComponent(M.subnet) : '');
|
||||
}
|
||||
|
||||
function loadList(kind, cls, meta) { return A.load(listURL(M.list_url, kind, cls), meta.title); }
|
||||
|
||||
function runLabel() {
|
||||
var o = document.getElementById('an-run');
|
||||
return o && o.selectedOptions[0] ? o.selectedOptions[0].textContent : 'запуск ' + M.run_id;
|
||||
return (o && o.selectedOptions[0] ? o.selectedOptions[0].textContent : 'запуск ' + M.run_id) + (M.subnet ? ' · подсеть ' + M.subnet : '');
|
||||
}
|
||||
|
||||
function openIndicator(kind) {
|
||||
@@ -250,6 +254,12 @@
|
||||
});
|
||||
}
|
||||
|
||||
/* ---- focus: direction and protocol ---- */
|
||||
function applyFocus() {
|
||||
$('an-sec-in').hidden = M.direction === 'egress';
|
||||
$('an-sec-eg').hidden = $('an-sec-val').hidden = M.direction === 'ingress';
|
||||
}
|
||||
|
||||
/* ---- wiring ---- */
|
||||
function renderAll() {
|
||||
renderSubnets();
|
||||
@@ -258,8 +268,15 @@
|
||||
}
|
||||
|
||||
$('an-runnote').textContent = 'Тип: ' + M.kind + '. Начало ' + M.start + ', завершён ' + M.end + ', длительность ' + M.duration + '.' +
|
||||
(M.rechecked ? ' Перепроверено внутри запуска: ' + M.rechecked + ' адр. (берётся последний цикл).' : '');
|
||||
renderKpis(); renderReasons(); renderQuality(); renderErrors(); renderSites(); renderValidators(); renderAll();
|
||||
(M.rechecked ? ' Перепроверено внутри запуска: ' + M.rechecked + ' адр. (берётся последний цикл).' : '') +
|
||||
(R.scope ? ' Подсеть ' + R.scope.subnet + ': ' + fmt(S.addresses) + ' адр. из ' + fmt(R.scope.run_addresses) + ' в запуске.' : '');
|
||||
if (R.scope && !S.addresses) {
|
||||
$('an-empty').textContent = 'В запуске нет адресов этой подсети.';
|
||||
$('an-empty').hidden = false;
|
||||
$('an-body').hidden = true;
|
||||
return;
|
||||
}
|
||||
renderKpis(); renderReasons(); renderQuality(); renderErrors(); renderSites(); renderValidators(); renderAll(); applyFocus();
|
||||
|
||||
$('an-kpis').addEventListener('click', function (e) { var b = e.target.closest('[data-list]'); if (b) openIndicator(b.dataset.list); });
|
||||
$('an-errs').addEventListener('click', function (e) { var b = e.target.closest('[data-cls]'); if (b) openError(b.dataset.cls); });
|
||||
|
||||
Reference in new issue
Block a user