Fixed log mobile view, plot, privacy banner
Build and Publish / Build and Publish Docker Image (push) Successful in 1m42s

This commit is contained in:
ab
2026-09-20 21:00:03 +03:00
parent 9587622285
commit 6449d8b59a
9 changed files with 295 additions and 47 deletions
+103 -44
View File
@@ -25,7 +25,8 @@
"visitor": "{{ t.analytics_log_visitor }}",
"landing_view": "{{ t.analytics_ev_landing_view }}",
"portal_open": "{{ t.analytics_ev_portal_open }}",
"media_view": "{{ t.analytics_ev_media_view }}"
"media_view": "{{ t.analytics_ev_media_view }}",
"media_prefix": "{{ t.analytics_log_media_prefix }}"
}'>
<div class="analytics-presets">
<button type="button" class="button is-small preset-btn" data-days="7">{{ t.analytics_preset_7 }}</button>
@@ -52,14 +53,6 @@
<div class="analytics-kpis" id="anKpis"></div>
<div class="analytics-metric-toggles">
<span class="toggles-label">{{ t.analytics_metrics_label }}:</span>
<label><input type="checkbox" class="metric-toggle" data-key="landing_views"> {{ t.analytics_metric_landing_views }}</label>
<label><input type="checkbox" class="metric-toggle" data-key="landing_unique" checked> {{ t.analytics_metric_landing_unique }}</label>
<label><input type="checkbox" class="metric-toggle" data-key="portal_opens" checked> {{ t.analytics_metric_portal_opens }}</label>
<label><input type="checkbox" class="metric-toggle" data-key="media_views" checked> {{ t.analytics_metric_media_views }}</label>
</div>
<div class="analytics-chart-wrap">
<div class="analytics-chart-box">
<canvas id="anChart"></canvas>
@@ -86,11 +79,19 @@
<div class="analytics-log-card">
<div class="analytics-log-head">
<h2>{{ t.analytics_log_title }}</h2>
<div class="analytics-metric-toggles analytics-log-filters">
<span class="toggles-label">{{ t.analytics_log_show }}:</span>
<label><input type="checkbox" class="log-type-toggle" data-type="landing_view"> {{ t.analytics_ev_landing_view }}</label>
<label><input type="checkbox" class="log-type-toggle" data-type="portal_open" checked> {{ t.analytics_ev_portal_open }}</label>
<label><input type="checkbox" class="log-type-toggle" data-type="media_view" checked> {{ t.analytics_ev_media_view }}</label>
<div class="analytics-log-filters">
<label class="log-chip" title="{{ t.analytics_ev_landing_view }}">
<input type="checkbox" class="log-type-toggle" data-type="landing_view">
<span>🌐 {{ t.analytics_ev_landing_short }}</span>
</label>
<label class="log-chip" title="{{ t.analytics_ev_portal_open }}">
<input type="checkbox" class="log-type-toggle" data-type="portal_open" checked>
<span>👤 {{ t.analytics_ev_portal_short }}</span>
</label>
<label class="log-chip" title="{{ t.analytics_ev_media_view }}">
<input type="checkbox" class="log-type-toggle" data-type="media_view" checked>
<span>🖼️ {{ t.analytics_ev_media_short }}</span>
</label>
</div>
</div>
<div class="analytics-log" id="anLog"></div>
@@ -127,7 +128,14 @@
.analytics-log-card { background:#fff; border-radius:12px; padding:16px; box-shadow:0 1px 4px rgba(0,0,0,.06); margin-top:20px; }
.analytics-log-card h2 { font-size:1rem; margin:0; }
.analytics-log-head { display:flex; flex-wrap:wrap; gap:10px 18px; align-items:center; justify-content:space-between; margin-bottom:10px; }
.analytics-log-filters { margin-bottom:0; }
.analytics-log-filters { display:flex; flex-wrap:wrap; gap:6px; }
.log-chip { display:inline-flex; align-items:center; gap:5px; padding:4px 11px; border-radius:999px; border:1px solid var(--admin-border,#e0e0e6); background:#f5f5f7; color:#8a8a95; font-size:.8rem; line-height:1.3; cursor:pointer; user-select:none; white-space:nowrap; transition:background .12s,color .12s,border-color .12s; }
.log-chip input { position:absolute; opacity:0; width:0; height:0; pointer-events:none; }
.log-chip:has(input:checked) { background:var(--admin-accent-soft,#eef0fb); border-color:#c9c6ee; color:#5b4fc4; font-weight:600; }
@media (max-width:600px) {
.analytics-log-head { gap:8px; margin-bottom:8px; }
.log-chip { padding:5px 12px; font-size:.82rem; }
}
.analytics-log { height:460px; overflow-y:auto; overflow-x:hidden; border:1px solid #f0f0f0; border-radius:8px; }
.an-log-row { display:grid; grid-template-columns:40px 1fr; gap:10px; align-items:start; padding:11px 12px; border-bottom:1px solid #f2f2f4; font-size:.85rem; }
.an-log-row:last-child { border-bottom:none; }
@@ -136,7 +144,10 @@
.an-log-main { min-width:0; }
.an-log-head { display:flex; flex-wrap:wrap; gap:2px 8px; align-items:baseline; }
.an-log-event { font-weight:650; }
.an-log-client { color:#7c6ed4; font-weight:600; }
.an-log-client { color:#7c6ed4; font-weight:600; text-decoration:none; }
.an-log-client:hover { text-decoration:underline; }
.an-log-num { color:#7c6ed4; font-weight:600; text-decoration:none; }
a.an-log-num:hover { text-decoration:underline; }
.an-log-time { color:#9a9aa6; font-size:.75rem; white-space:nowrap; margin-left:auto; }
.an-log-meta { color:#8a8a95; font-size:.76rem; margin-top:4px; display:flex; flex-direction:column; gap:2px; }
.an-log-meta .an-log-line { display:flex; flex-wrap:wrap; gap:4px 12px; }
@@ -157,7 +168,7 @@
var L = JSON.parse(controls.getAttribute('data-labels'));
var META = [
{ key: 'landing_views', color: '#7c6ed4' },
{ key: 'landing_views', color: '#7c6ed4', hidden: true },
{ key: 'landing_unique', color: '#00b496' },
{ key: 'portal_opens', color: '#ff8c26' },
{ key: 'media_views', color: '#ff5287' }
@@ -212,6 +223,23 @@
function metricLabel(key) { return L[key] || key; }
var MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
// Turns an ISO bucket label into a short display date:
// "2026-04-11" -> "11 Apr", "2026-04" -> "Apr 2026".
function formatLabel(raw) {
if (!raw) { return raw; }
var parts = String(raw).split('-');
if (parts.length === 3) {
return parseInt(parts[2], 10) + ' ' + MONTHS[parseInt(parts[1], 10) - 1];
}
if (parts.length === 2) {
return MONTHS[parseInt(parts[1], 10) - 1] + ' ' + parts[0];
}
return raw;
}
function hexToRgba(hex, a) {
var n = parseInt(hex.slice(1), 16);
return 'rgba(' + ((n >> 16) & 255) + ',' + ((n >> 8) & 255) + ',' + (n & 255) + ',' + a + ')';
@@ -239,10 +267,6 @@
function initChart() {
var ctx = document.getElementById('anChart').getContext('2d');
var enabled = {};
document.querySelectorAll('.metric-toggle').forEach(function (cb) {
enabled[cb.getAttribute('data-key')] = cb.checked;
});
var datasets = META.map(function (m) {
return {
label: metricLabel(m.key),
@@ -253,7 +277,7 @@
pointRadius: 2,
tension: 0.25,
fill: true,
hidden: enabled[m.key] === false,
hidden: !!m.hidden,
_key: m.key
};
});
@@ -265,8 +289,39 @@
maintainAspectRatio: false,
animation: false,
interaction: { mode: 'index', intersect: false },
plugins: { legend: { display: false } },
scales: { y: { beginAtZero: true, ticks: { precision: 0 } } }
plugins: {
legend: {
display: true,
position: 'top',
labels: {
usePointStyle: true,
pointStyle: 'rectRounded',
boxWidth: 12,
boxHeight: 12,
padding: 12,
font: { size: 12 }
}
},
tooltip: {
callbacks: {
title: function (items) {
return items.length ? formatLabel(items[0].label) : '';
}
}
}
},
scales: {
x: {
ticks: {
autoSkip: true,
maxRotation: 0,
callback: function (value) {
return formatLabel(this.getLabelForValue(value));
}
}
},
y: { beginAtZero: true, ticks: { precision: 0 } }
}
},
plugins: [messageOverlay]
});
@@ -401,15 +456,6 @@
fromInput.addEventListener('change', load);
toInput.addEventListener('change', load);
granSelect.addEventListener('change', load);
document.querySelectorAll('.metric-toggle').forEach(function (cb) {
cb.addEventListener('change', function () {
if (!chart) { return; }
chart.data.datasets.forEach(function (ds) {
if (ds._key === cb.getAttribute('data-key')) { ds.hidden = !cb.checked; }
});
chart.update();
});
});
// --- Event log (newest first, infinite scroll of older events) ---------
var logEl = document.getElementById('anLog');
@@ -487,16 +533,37 @@
var head = document.createElement('div');
head.className = 'an-log-head';
var ev = document.createElement('span');
ev.className = 'an-log-event';
ev.textContent = L[e.event_type] || e.event_type;
head.appendChild(ev);
if (e.event_type === 'media_view') {
ev.textContent = L.media_prefix;
head.appendChild(ev);
if (e.media && e.media.media_id != null) {
var num = document.createElement(e.client_id != null ? 'a' : 'span');
num.className = 'an-log-num';
num.textContent = '№' + e.media.media_id;
if (e.client_id != null) { num.href = '/admin/media?client_id=' + e.client_id; }
head.appendChild(num);
}
} else {
ev.textContent = L[e.event_type] || e.event_type;
head.appendChild(ev);
}
if (e.client_name) {
var cl = document.createElement('span');
var cl;
if (e.client_id != null) {
cl = document.createElement('a');
cl.href = '/admin/clients/' + e.client_id + '/edit';
} else {
cl = document.createElement('span');
}
cl.className = 'an-log-client';
cl.textContent = e.client_name;
head.appendChild(cl);
}
var tm = document.createElement('span');
tm.className = 'an-log-time';
tm.textContent = e.created_at;
@@ -512,14 +579,6 @@
if (ua) { line1.appendChild(metaSpan(ua)); }
if (line1.childNodes.length) { meta.appendChild(line1); }
if (e.path && e.path !== '/') {
var line2 = metaLine();
var code = document.createElement('code');
code.textContent = e.path;
line2.appendChild(code);
meta.appendChild(line2);
}
if (e.referer) {
var line3 = metaLine();
var ref = document.createElement('span');