Files
cloud-ip-validator/internal/dashboard/static/analytics-dialog.js
T

107 lines
4.8 KiB
JavaScript
Raw Normal View History

2026-10-04 10:26:37 +03:00
/* The address-list dialog of the analytics pages (markup: the "analytics_dialog"
template) and the tooltip that goes with it. A page loads a list with
AnalyticsDialog.load(url, title), then shows it with AnalyticsDialog.fill(m);
copying and the CSV download work on the list shown last. */
(function () {
'use strict';
function $(id) { return document.getElementById(id); }
function fmt(n) { return Math.round(n).toLocaleString('ru-RU'); }
function esc(s) {
return String(s).replace(/[&<>"']/g, function (c) {
return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
});
}
function tipAttr(t) { return 'data-tip="' + esc(t) + '"'; }
var cur = null; // the list shown in the dialog
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 open() {
var d = $('an-dlg');
if (d.open) return;
if (d.showModal) d.showModal(); else d.setAttribute('open', '');
}
/* m: title, note, scope ("Запуск" by default), runLabel, cols, rows, hints
(column index -> text), cellHints (column index -> function of the value),
dist (HTML above the table), csvURL (the file behind "Скачать CSV"). */
function fill(m) {
cur = m;
$('an-dlg-title').textContent = m.title + ' · ' + fmt(m.rows.length);
$('an-dlg-note').textContent = (m.scope || 'Запуск') + ': ' + m.runLabel + '. ' + m.note;
$('an-dlg-dist').hidden = !m.dist;
$('an-dlg-dist').innerHTML = m.dist || '';
var hint = m.hints || {};
$('an-dlg-tbl').innerHTML = '<thead><tr>' + m.cols.map(function (c, i) {
return hint[i] ? '<th ' + tipAttr(hint[i]) + ' tabindex="0">' + esc(c) + '<span class="an-hint" aria-hidden="true">ⓘ</span></th>' : '<th>' + esc(c) + '</th>';
}).join('') + '</tr></thead><tbody>' + m.rows.map(function (x) {
return '<tr>' + x.map(function (v, i) {
var h = m.cellHints && m.cellHints[i] ? m.cellHints[i](v) : '';
return '<td class="' + (i < 2 ? 'an-a' : '') + '"' + (h ? ' ' + tipAttr(h) : '') + '>' + esc(v) + '</td>';
}).join('') + '</tr>';
}).join('') + '</tbody>';
$('an-dlg-msg').textContent = '';
}
/* Opens the dialog with the title and "Загрузка…", fetches the list as JSON;
resolves with it, or with null (and a message in the dialog) on a failure. */
function load(url, title) {
$('an-dlg-title').textContent = title;
$('an-dlg-note').textContent = 'Загрузка…';
$('an-dlg-dist').hidden = true;
$('an-dlg-tbl').innerHTML = '';
$('an-dlg-msg').textContent = '';
cur = null;
open();
return fetch(url, { headers: { Accept: 'application/json' }, credentials: 'same-origin' })
.then(function (r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
.then(function (l) { return l; })
.catch(function (e) {
$('an-dlg-note').textContent = 'Не удалось загрузить список: ' + e.message;
return null;
});
}
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 = cur.csvURL;
}
$('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; });
window.AnalyticsDialog = { $: $, fmt: fmt, esc: esc, tipAttr: tipAttr, load: load, fill: fill };
})();