';
}).join('') + '';
$('an-matrixnote').textContent = 'Доля адресов partial подсети, провалившие ' + t + ' к цели. Строки — подсети с наибольшим числом partial; полный список включается кнопкой «показать все» выше.';
}
function renderSites() {
var T = R.sites;
$('an-sites').innerHTML = '
Площадка
' + T.types.map(function (t) { return '
' + esc(t) + '
'; }).join('') + '
' +
T.rows.map(function (row) {
return '
' + esc(row.site) + '
' + row.stats.map(function (st, i) {
return '
' + pct1(st.total - st.ok, st.total) + '%провал
';
}).join('') + '
';
}).join('') + '';
}
function renderErrors() {
var max = Math.max.apply(null, R.errors.map(function (e) { return e.count; }).concat([1]));
$('an-errs').innerHTML = R.errors.length ? R.errors.map(function (e) {
return '';
}).join('') : '
Проваленных ingress-проверок нет.
';
}
function renderValidators() {
var V = R.validators;
var f = V.map(function (v) { return v.total ? 1 - v.ok / v.total : 0; });
var avg = f.length ? f.reduce(function (a, b) { return a + b; }, 0) / f.length : 0;
$('an-vals').innerHTML = f.map(function (x, i) {
return '';
}).join('') + '';
$('an-vfirst').textContent = V.length ? 'валидатор ' + vshort(V[0].validator) : '';
$('an-vlast').textContent = V.length > 1 ? 'валидатор ' + vshort(V[V.length - 1].validator) : '';
$('an-vavg').textContent = V.length ? 'среднее ' + Math.round(avg * 100) + '% (шкала 0–100%)' : 'нет данных';
}
/* ---- address lists in the dialog ---- */
var VERDICT_HINT = 'Итог всего адреса за цикл, который система выставила при агрегации. pass: все проверки (egress и ingress), полученные к этому моменту, успешны. partial: часть проверок провалена или результатов не хватает. fail: все проверки провалены. Это оценка адреса, а не этой проверки.';
var STATUS_HINT = 'Состояние именно этой проверки (она всегда проваленная). «в вердикте»: результат пришёл до того, как система выставила вердикт адресу, и повлиял на него. «после вердикта»: результат пришёл позже, вердикт уже был выставлен и не пересчитывался. Поэтому у адреса с вердиктом pass может быть проваленная проверка.';
var VERDICT_VAL = {
pass: 'pass: к моменту вердикта все полученные проверки адреса были успешны. Эта проверка провалилась позже и в вердикт не вошла.',
partial: 'partial: часть проверок адреса провалена или результатов не хватило, вердикт не pass.',
fail: 'fail: все проверки адреса провалены.'
};
function statusVal(v) { return String(v).indexOf('после') >= 0 ? 'Результат пришёл после вердикта адреса и в него не вошёл.' : 'Результат пришёл до вердикта и учтён в нём.'; }
var LISTS = {
egress_https_any: {
title: 'Egress https: адреса с проваленными проверками',
note: 'Хотя бы одна egress-проверка https адреса провалена в последнем цикле запуска.',
hints: { 2: 'Валидатор, с которого шли egress-проверки адреса.', 3: 'Сколько https-проверок адреса провалено из всех записанных в цикле.' }
},
ingress_ssh_any: {
title: 'Ingress ssh: адреса с проваленными проверками',
note: 'ssh провален хотя бы с одной площадки в последнем цикле запуска.',
hints: { 2: 'Сколько площадок не смогли выполнить ssh-проверку адреса из всех, где она выполнялась.', 3: 'Площадки пробера, с которых ssh-проверка адреса провалена.', 4: 'Класс ошибки: таймаут, баннер «Not allowed», нет маршрута.' }
},
egress_https_all: {
title: 'Egress https: адреса, провалившие все проверки',
note: 'Все записанные egress-проверки https адреса провалены в последнем цикле запуска.',
hints: { 2: 'Валидатор, с которого шли egress-проверки адреса.', 3: 'Сколько https-проверок записано у адреса в цикле. Все они провалены. Меньше полного набора значит, что часть результатов не пришла.' }
},
ingress_ssh_all: {
title: 'Ingress ssh: адреса, провалившие все проверки',
note: 'ssh провален со всех площадок, на которых он проверялся, в последнем цикле запуска.',
hints: { 2: 'Площадки пробера, с которых ssh-проверка адреса провалена.', 3: 'Класс ошибки ssh-проверки: таймаут, баннер «Not allowed», нет маршрута.' }
}
};
var cur = null; // the list shown in the dialog
function listURL(base, kind, cls) {
return base + encodeURIComponent(kind) + '?run=' + M.run_id + (cls ? '&class=' + encodeURIComponent(cls) : '');
}
function csvText(cols, rows) {
function q(v) { return '"' + String(v).replace(/"/g, '""') + '"'; }
return [cols].concat(rows).map(function (r) { return r.map(q).join(','); }).join('\r\n') + '\r\n';
}
function openDialog() {
var d = $('an-dlg');
if (d.open) return;
if (d.showModal) d.showModal(); else d.setAttribute('open', '');
}
function fillDialog(m) {
cur = m;
$('an-dlg-title').textContent = m.title + ' · ' + fmt(m.rows.length);
$('an-dlg-note').textContent = 'Запуск: ' + m.runLabel + '. ' + m.note;
$('an-dlg-dist').hidden = !m.dist;
$('an-dlg-dist').innerHTML = m.dist || '';
var hint = m.hints || {};
$('an-dlg-tbl').innerHTML = '
';
fillDialog({
title: 'Ingress: ' + cls,
note: 'Проваленные проверки этого класса в последнем цикле каждого адреса. Валидатор — тот, к которому был привязан адрес в этом цикле. «После вердикта» — результат пришёл позже агрегации и в вердикт адреса не вошёл.',
cols: l.columns, rows: rows, runLabel: runLabel(), dist: dist, kind: 'error', cls: cls,
hints: { 3: 'Валидатор, к которому был привязан адрес в этом цикле (берётся из события привязки Floating IP).', 4: VERDICT_HINT, 5: STATUS_HINT },
cellHints: { 4: function (v) { return VERDICT_VAL[v] || ''; }, 5: statusVal }
});
});
}
function copyCsv() {
var msg = $('an-dlg-msg');
if (!cur) return;
var text = csvText(cur.cols, cur.rows);
var done = function () { msg.textContent = 'CSV скопирован в буфер обмена.'; };
var fail = function () { msg.textContent = 'Буфер обмена недоступен в этом окне.'; };
if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(text).then(done, fail); else fail();
}
function downloadCsv() {
if (!cur) return;
window.location.href = listURL(M.csv_url, cur.kind, cur.cls);
}
/* ---- wiring ---- */
function renderAll() {
renderSubnets();
renderTypes();
renderTargets();
}
$('an-runnote').textContent = 'Тип: ' + M.kind + '. Начало ' + M.start + ', завершён ' + M.end + ', длительность ' + M.duration + '.' +
(M.rechecked ? ' Перепроверено внутри запуска: ' + M.rechecked + ' адр. (берётся последний цикл).' : '');
renderKpis(); renderReasons(); renderQuality(); renderErrors(); renderSites(); renderValidators(); renderAll();
$('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); });
document.querySelectorAll('[data-sort]').forEach(function (b) {
b.addEventListener('click', function () {
state.sort = b.dataset.sort;
document.querySelectorAll('[data-sort]').forEach(function (x) { x.setAttribute('aria-pressed', x === b); });
renderSubnets();
});
});
$('an-types').addEventListener('click', function (e) {
var b = e.target.closest('[data-type]');
if (!b) return;
state.type = b.dataset.type;
renderTypes(); renderTargets();
});
$('an-allsub').addEventListener('click', function () { state.all = !state.all; renderSubnets(); renderTargets(); });
$('an-dlg-csv').addEventListener('click', downloadCsv);
$('an-dlg-copy').addEventListener('click', copyCsv);
$('an-dlg-close').addEventListener('click', function () { $('an-dlg').close(); });
$('an-dlg').addEventListener('click', function (e) { if (e.target === $('an-dlg')) $('an-dlg').close(); });
/* tooltip: moved into the open dialog, otherwise the modal's top layer covers it */
var tip = $('an-tip');
function tipHost() { var d = $('an-dlg'); return d && d.open ? d : document.body; }
document.addEventListener('mouseover', function (e) {
var t = e.target.closest('[data-tip]');
if (!t) return;
var h = tipHost();
if (tip.parentNode !== h) h.appendChild(tip);
tip.textContent = t.dataset.tip;
tip.style.opacity = 1;
});
document.addEventListener('mousemove', function (e) {
tip.style.left = Math.max(8, Math.min(e.clientX + 14, window.innerWidth - 376)) + 'px';
tip.style.top = (e.clientY + 16) + 'px';
});
document.addEventListener('mouseout', function (e) { if (e.target.closest('[data-tip]')) tip.style.opacity = 0; });
})();