618 lines
22 KiB
HTML
618 lines
22 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Supervision - Utilisateurs{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<h4 class="mb-0"><i class="bi bi-people"></i> Utilisateurs Amadea</h4>
|
|
<span id="last-update" class="text-muted small"></span>
|
|
</div>
|
|
|
|
<div id="alert-zone"></div>
|
|
|
|
<!-- Offcanvas : détail d'un utilisateur sur 7 jours -->
|
|
<div class="offcanvas offcanvas-end" tabindex="-1" id="userHistoryCanvas" style="width:420px">
|
|
<div class="offcanvas-header border-bottom">
|
|
<h6 class="offcanvas-title mb-0">
|
|
<i class="bi bi-person-lines-fill"></i>
|
|
<span id="canvas-login" class="ms-1"></span>
|
|
</h6>
|
|
<button type="button" class="btn-close" data-bs-dismiss="offcanvas"></button>
|
|
</div>
|
|
<div class="offcanvas-body p-3">
|
|
<div class="btn-group btn-group-sm mb-3" role="group" id="canvas-period-btns">
|
|
<button type="button" class="btn btn-primary" data-days="7">7 jours</button>
|
|
<button type="button" class="btn btn-outline-primary" data-days="30">30 jours</button>
|
|
</div>
|
|
<div id="canvas-loading" class="text-center py-4">
|
|
<div class="spinner-border spinner-border-sm text-primary me-2"></div>
|
|
<span class="text-muted small">Chargement...</span>
|
|
</div>
|
|
<div id="canvas-content" class="d-none">
|
|
<p class="text-muted small mb-1" id="canvas-period-label">Actions par jour (7 derniers jours)</p>
|
|
<div style="display:flex;gap:4px;height:64px;align-items:flex-end" id="canvas-bars"></div>
|
|
<div style="display:flex;gap:4px;margin-top:2px" id="canvas-bar-labels"></div>
|
|
<table class="table table-sm mt-3 mb-0">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th class="small">Date</th>
|
|
<th class="small">Actions</th>
|
|
<th class="small">Erreurs</th>
|
|
<th class="small">Présence</th>
|
|
<th class="small">Actif</th>
|
|
<th class="small">Sessions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="canvas-tbody"></tbody>
|
|
</table>
|
|
</div>
|
|
<div id="canvas-no-data" class="d-none text-center text-muted py-4">
|
|
<i class="bi bi-calendar-x d-block fs-3 mb-2"></i>
|
|
Aucune donnée disponible sur 7 jours.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Graphique -->
|
|
<div class="card mb-3">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h6 class="mb-0"><i class="bi bi-bar-chart"></i> Utilisateurs actifs par heure</h6>
|
|
<select id="period-select" class="form-select form-select-sm" style="width: auto;">
|
|
<option value="today">Aujourd'hui</option>
|
|
<option value="7days">7 derniers jours</option>
|
|
<option value="30days">30 derniers jours</option>
|
|
</select>
|
|
</div>
|
|
<div class="card-body">
|
|
<div style="display: flex; gap: 6px;">
|
|
<div id="chart-yaxis"
|
|
style="display: flex; flex-direction: column; justify-content: space-between;
|
|
align-items: flex-end; width: 20px; height: 100px; flex-shrink: 0;">
|
|
</div>
|
|
<div style="flex: 1; min-width: 0;">
|
|
<div id="chart-container"
|
|
style="height: 100px; display: flex; align-items: flex-end; gap: 3px;">
|
|
</div>
|
|
<div id="chart-labels" style="display: flex; gap: 3px; margin-top: 4px;"></div>
|
|
</div>
|
|
</div>
|
|
<div id="chart-hint" class="d-none mt-2">
|
|
<small class="text-muted"><i class="bi bi-hand-index"></i> Cliquez sur une barre pour voir les utilisateurs de ce jour.</small>
|
|
</div>
|
|
<div id="chart-unavailable" class="d-none">
|
|
<div class="alert alert-info mb-0">
|
|
<i class="bi bi-info-circle"></i>
|
|
Les données historiques ne sont pas disponibles (fichiers archivés).
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tableau utilisateurs -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h6 class="mb-0" id="table-title"></h6>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<table class="table table-hover mb-0">
|
|
<thead>
|
|
<tr id="table-headers"></tr>
|
|
</thead>
|
|
<tbody id="users-tbody"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<style>
|
|
.badge-absent { background-color: #fd7e14; color: #fff; }
|
|
tr.row-clickable { cursor: pointer; }
|
|
tr.row-clickable:hover td { background-color: rgba(13,110,253,.05); }
|
|
</style>
|
|
<script>
|
|
var STATUS_CONFIG = {
|
|
actif: { badge: 'bg-success', label: 'ACTIF' },
|
|
inactif: { badge: 'bg-warning text-dark', label: 'INACTIF' },
|
|
absent: { badge: 'badge-absent', label: 'ABSENT' },
|
|
deconnecte: { badge: 'bg-secondary', label: 'DECONNECTE' },
|
|
};
|
|
|
|
var currentPeriod = 'today';
|
|
var selectedBarEl = null;
|
|
var lastTodayUsers = [];
|
|
var currentHourly = [];
|
|
var DAYS_FR = ['Dim','Lun','Mar','Mer','Jeu','Ven','Sam'];
|
|
var canvasCurrentLogin = '';
|
|
var canvasDays = 7;
|
|
|
|
/* --- Helpers DOM --- */
|
|
|
|
function el(tag, cls, text) {
|
|
var e = document.createElement(tag);
|
|
if (cls) e.className = cls;
|
|
if (text !== undefined) e.textContent = text;
|
|
return e;
|
|
}
|
|
|
|
function icon(name) {
|
|
var i = document.createElement('i');
|
|
i.className = 'bi bi-' + name;
|
|
return i;
|
|
}
|
|
|
|
/* --- Y-axis --- */
|
|
|
|
function renderYAxis(max) {
|
|
var yaxis = document.getElementById('chart-yaxis');
|
|
yaxis.textContent = '';
|
|
[max, Math.round(max / 2), 0].forEach(function(v) {
|
|
var d = el('div');
|
|
d.style.fontSize = '0.6rem';
|
|
d.style.color = '#6c757d';
|
|
d.style.lineHeight = '1';
|
|
d.textContent = v;
|
|
yaxis.appendChild(d);
|
|
});
|
|
}
|
|
|
|
/* --- Graphique générique --- */
|
|
|
|
function renderChart(data, options) {
|
|
var container = document.getElementById('chart-container');
|
|
var labelsEl = document.getElementById('chart-labels');
|
|
var unavail = document.getElementById('chart-unavailable');
|
|
|
|
container.style.display = 'flex';
|
|
labelsEl.style.display = 'flex';
|
|
unavail.classList.add('d-none');
|
|
container.textContent = '';
|
|
labelsEl.textContent = '';
|
|
selectedBarEl = null;
|
|
|
|
if (!data || data.length === 0) {
|
|
renderYAxis(0);
|
|
container.appendChild(el('span', 'text-muted small', 'Aucune donnée disponible.'));
|
|
return;
|
|
}
|
|
|
|
var max = 1;
|
|
data.forEach(function(d) { if ((d.count || 0) > max) max = d.count; });
|
|
renderYAxis(max);
|
|
|
|
data.forEach(function(item) {
|
|
var count = item.count || 0;
|
|
var heightPct = count > 0 ? Math.max((count / max) * 100, 6) : 0;
|
|
|
|
var bar = el('div');
|
|
bar.style.flex = '1';
|
|
bar.style.minWidth = '8px';
|
|
bar.style.height = heightPct + '%';
|
|
bar.style.background = '#0d6efd';
|
|
bar.style.borderRadius = '3px 3px 0 0';
|
|
bar.style.opacity = count > 0 ? '1' : '0.15';
|
|
bar.style.transition = 'height 0.3s';
|
|
var tooltip = '';
|
|
if (item.hour !== undefined) {
|
|
tooltip = item.hour + 'h : ' + count + ' utilisateur(s)';
|
|
} else {
|
|
tooltip = item.date + ' : ' + count + ' utilisateur(s)';
|
|
if (item.errors !== undefined && item.errors > 0) {
|
|
tooltip += ' — ' + item.errors + ' erreur(s)';
|
|
}
|
|
}
|
|
bar.title = tooltip;
|
|
|
|
if (options && options.onBarClick && item.date !== undefined) {
|
|
bar.style.cursor = 'pointer';
|
|
(function(ci, cb) {
|
|
cb.addEventListener('click', function() {
|
|
if (selectedBarEl) {
|
|
selectedBarEl.style.background = '#0d6efd';
|
|
selectedBarEl.style.outline = '';
|
|
}
|
|
cb.style.background = '#0a58ca';
|
|
cb.style.outline = '2px solid #0a3fa8';
|
|
selectedBarEl = cb;
|
|
options.onBarClick(ci);
|
|
});
|
|
})(item, bar);
|
|
}
|
|
container.appendChild(bar);
|
|
|
|
var lbl = el('div');
|
|
lbl.style.flex = '1';
|
|
lbl.style.minWidth = '8px';
|
|
lbl.style.textAlign = 'center';
|
|
lbl.style.fontSize = '0.6rem';
|
|
lbl.style.color = '#6c757d';
|
|
lbl.style.overflow = 'hidden';
|
|
if (item.hour !== undefined) {
|
|
lbl.textContent = item.hour % 3 === 0 ? item.hour + 'h' : '';
|
|
} else {
|
|
var d = new Date(item.date);
|
|
lbl.textContent = d.getUTCDate() + '/' + (d.getUTCMonth() + 1);
|
|
}
|
|
labelsEl.appendChild(lbl);
|
|
});
|
|
}
|
|
|
|
function renderPeakChart(list) {
|
|
var unavail = document.getElementById('chart-unavailable');
|
|
var container = document.getElementById('chart-container');
|
|
var labelsEl = document.getElementById('chart-labels');
|
|
var hint = document.getElementById('chart-hint');
|
|
|
|
if (list.every(function(d) { return d.count === null; })) {
|
|
container.style.display = 'none';
|
|
labelsEl.style.display = 'none';
|
|
hint.classList.add('d-none');
|
|
document.getElementById('chart-yaxis').textContent = '';
|
|
unavail.classList.remove('d-none');
|
|
return;
|
|
}
|
|
|
|
hint.classList.remove('d-none');
|
|
renderChart(
|
|
list.map(function(d) { return { date: d.date, count: d.count === null ? 0 : d.count, errors: d.errors || 0 }; }),
|
|
{ onBarClick: function(item) { loadDayUsers(item.date); } }
|
|
);
|
|
}
|
|
|
|
/* --- En-têtes du tableau --- */
|
|
|
|
function setTableMode(mode, dateLabel) {
|
|
var title = document.getElementById('table-title');
|
|
var thead = document.getElementById('table-headers');
|
|
title.textContent = '';
|
|
thead.textContent = '';
|
|
|
|
title.appendChild(icon('person-lines-fill'));
|
|
if (mode === 'today') {
|
|
title.appendChild(document.createTextNode(' Utilisateurs connectés aujourd\'hui'));
|
|
['Utilisateur','Statut','Dernière action','Actions (24h)','Erreurs','Présence / Actif','Depuis'].forEach(function(h) {
|
|
thead.appendChild(el('th', null, h));
|
|
});
|
|
} else {
|
|
title.appendChild(document.createTextNode(' Utilisateurs — ' + (dateLabel || '')));
|
|
['Utilisateur','Dernière utilisation','Actions','Erreurs','Présence / Actif','Sessions'].forEach(function(h) {
|
|
thead.appendChild(el('th', null, h));
|
|
});
|
|
}
|
|
}
|
|
|
|
/* --- Tableau aujourd'hui --- */
|
|
|
|
function renderTable(users) {
|
|
var tbody = document.getElementById('users-tbody');
|
|
tbody.textContent = '';
|
|
|
|
if (!users || users.length === 0) {
|
|
var tr = document.createElement('tr');
|
|
var td = el('td', 'text-center text-muted py-3', "Aucun utilisateur détecté aujourd'hui.");
|
|
td.colSpan = 7;
|
|
tr.appendChild(td);
|
|
tbody.appendChild(tr);
|
|
return;
|
|
}
|
|
|
|
users.forEach(function(u) {
|
|
var sc = STATUS_CONFIG[u.status] || { badge: 'bg-secondary', label: (u.status || '').toUpperCase() };
|
|
var tr = el('tr', 'row-clickable');
|
|
tr.addEventListener('click', function() { openUserHistory(u.login); });
|
|
|
|
// Utilisateur
|
|
var tdUser = document.createElement('td');
|
|
tdUser.appendChild(el('strong', null, u.login || ''));
|
|
if ((u.session_count || 1) > 1) {
|
|
tdUser.appendChild(el('small', 'text-muted d-block', u.session_count + ' sessions aujourd\'hui'));
|
|
}
|
|
tr.appendChild(tdUser);
|
|
|
|
// Statut
|
|
var tdStatus = document.createElement('td');
|
|
tdStatus.appendChild(el('span', 'badge ' + sc.badge, sc.label));
|
|
tr.appendChild(tdStatus);
|
|
|
|
// Dernière action
|
|
var tdAction = document.createElement('td');
|
|
tdAction.textContent = u.last_action_time || '—';
|
|
if (u.last_action_label) {
|
|
tdAction.appendChild(el('small', 'text-muted d-block', u.last_action_label));
|
|
}
|
|
tr.appendChild(tdAction);
|
|
|
|
// Actions (24h)
|
|
tr.appendChild(el('td', null, String(u.action_count_24h || 0)));
|
|
|
|
// Erreurs
|
|
var tdErr = document.createElement('td');
|
|
if ((u.error_count || 0) > 0) {
|
|
tdErr.appendChild(el('span', 'badge bg-danger', String(u.error_count)));
|
|
} else {
|
|
tdErr.textContent = '—';
|
|
}
|
|
tr.appendChild(tdErr);
|
|
|
|
// Présence / Actif
|
|
var tdPres = document.createElement('td');
|
|
if (u.presence_str) {
|
|
tdPres.textContent = u.presence_str;
|
|
if (u.active_time_str) {
|
|
tdPres.appendChild(el('small', 'text-muted d-block', u.active_time_str + ' actif'));
|
|
}
|
|
} else {
|
|
tdPres.textContent = '—';
|
|
}
|
|
tr.appendChild(tdPres);
|
|
|
|
// Depuis
|
|
tr.appendChild(el('td', null, u.connected_since || '—'));
|
|
|
|
tbody.appendChild(tr);
|
|
});
|
|
}
|
|
|
|
/* --- Tableau jour historique --- */
|
|
|
|
function renderDayTable(users, dateStr) {
|
|
setTableMode('day', dateStr);
|
|
var tbody = document.getElementById('users-tbody');
|
|
tbody.textContent = '';
|
|
|
|
if (!users || users.length === 0) {
|
|
var tr = document.createElement('tr');
|
|
var td = el('td', 'text-center text-muted py-3', 'Aucun utilisateur détecté ce jour.');
|
|
td.colSpan = 6;
|
|
tr.appendChild(td);
|
|
tbody.appendChild(tr);
|
|
return;
|
|
}
|
|
|
|
users.forEach(function(u) {
|
|
var tr = el('tr', 'row-clickable');
|
|
tr.addEventListener('click', function() { openUserHistory(u.login); });
|
|
|
|
var tdUser = document.createElement('td');
|
|
tdUser.appendChild(el('strong', null, u.login || ''));
|
|
if (u.first_action_time) {
|
|
tdUser.appendChild(el('small', 'text-muted d-block', 'depuis ' + u.first_action_time));
|
|
}
|
|
tr.appendChild(tdUser);
|
|
|
|
var tdAct = document.createElement('td');
|
|
tdAct.textContent = u.last_action_time || '—';
|
|
if (u.last_action_label) {
|
|
tdAct.appendChild(el('small', 'text-muted d-block', u.last_action_label));
|
|
}
|
|
tr.appendChild(tdAct);
|
|
|
|
tr.appendChild(el('td', null, String(u.action_count || 0)));
|
|
|
|
// Erreurs
|
|
var tdErr2 = document.createElement('td');
|
|
if ((u.error_count || 0) > 0) {
|
|
tdErr2.appendChild(el('span', 'badge bg-danger', String(u.error_count)));
|
|
} else {
|
|
tdErr2.textContent = '—';
|
|
}
|
|
tr.appendChild(tdErr2);
|
|
|
|
var tdPres = document.createElement('td');
|
|
if (u.presence) {
|
|
tdPres.textContent = u.presence;
|
|
if (u.active_time) {
|
|
tdPres.appendChild(el('small', 'text-muted d-block', u.active_time + ' actif'));
|
|
}
|
|
} else {
|
|
tdPres.textContent = '—';
|
|
}
|
|
tr.appendChild(tdPres);
|
|
|
|
tr.appendChild(el('td', null, String(u.sessions || 1)));
|
|
tbody.appendChild(tr);
|
|
});
|
|
}
|
|
|
|
function loadDayUsers(date) {
|
|
fetch('/api/users/day/' + date)
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
if (data.error) { showAlert('warning', data.error); return; }
|
|
clearAlert();
|
|
renderDayTable(data.users || [], date);
|
|
})
|
|
.catch(function() {});
|
|
}
|
|
|
|
/* --- Offcanvas historique utilisateur --- */
|
|
|
|
function setCanvasDays(days) {
|
|
canvasDays = days;
|
|
var btns = document.querySelectorAll('#canvas-period-btns button');
|
|
btns.forEach(function(b) {
|
|
var active = parseInt(b.getAttribute('data-days')) === days;
|
|
b.className = active ? 'btn btn-sm btn-primary' : 'btn btn-sm btn-outline-primary';
|
|
});
|
|
document.getElementById('canvas-period-label').textContent =
|
|
'Actions par jour (' + days + ' derniers jours)';
|
|
}
|
|
|
|
function loadCanvasHistory(login, days) {
|
|
document.getElementById('canvas-loading').classList.remove('d-none');
|
|
document.getElementById('canvas-content').classList.add('d-none');
|
|
document.getElementById('canvas-no-data').classList.add('d-none');
|
|
|
|
fetch('/api/users/' + encodeURIComponent(login) + '/history?days=' + days)
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
document.getElementById('canvas-loading').classList.add('d-none');
|
|
var history = data.history || [];
|
|
if (!history.some(function(d) { return d.action_count !== null; })) {
|
|
document.getElementById('canvas-no-data').classList.remove('d-none');
|
|
return;
|
|
}
|
|
renderUserHistory(history);
|
|
document.getElementById('canvas-content').classList.remove('d-none');
|
|
})
|
|
.catch(function() {
|
|
document.getElementById('canvas-loading').classList.add('d-none');
|
|
document.getElementById('canvas-no-data').classList.remove('d-none');
|
|
});
|
|
}
|
|
|
|
function openUserHistory(login) {
|
|
canvasCurrentLogin = login;
|
|
document.getElementById('canvas-login').textContent = login;
|
|
setCanvasDays(canvasDays);
|
|
|
|
bootstrap.Offcanvas.getOrCreateInstance(
|
|
document.getElementById('userHistoryCanvas')
|
|
).show();
|
|
|
|
loadCanvasHistory(login, canvasDays);
|
|
}
|
|
|
|
document.getElementById('canvas-period-btns').addEventListener('click', function(e) {
|
|
var btn = e.target.closest('button[data-days]');
|
|
if (!btn || !canvasCurrentLogin) return;
|
|
var days = parseInt(btn.getAttribute('data-days'));
|
|
setCanvasDays(days);
|
|
loadCanvasHistory(canvasCurrentLogin, days);
|
|
});
|
|
|
|
function renderUserHistory(history) {
|
|
var barsEl = document.getElementById('canvas-bars');
|
|
var lblsEl = document.getElementById('canvas-bar-labels');
|
|
barsEl.textContent = '';
|
|
lblsEl.textContent = '';
|
|
|
|
var max = 1;
|
|
history.forEach(function(d) { if ((d.action_count || 0) > max) max = d.action_count; });
|
|
|
|
history.forEach(function(d) {
|
|
var count = d.action_count || 0;
|
|
var h = count > 0 ? Math.max(Math.round((count / max) * 64), 6) : 4;
|
|
|
|
var bar = el('div');
|
|
bar.style.flex = '1';
|
|
bar.style.height = h + 'px';
|
|
bar.style.background = count > 0 ? '#0d6efd' : '#e9ecef';
|
|
bar.style.borderRadius = '3px 3px 0 0';
|
|
bar.title = d.date + ' : ' + count + ' actions';
|
|
if ((d.error_count || 0) > 0) {
|
|
bar.title += ' — ' + d.error_count + ' erreur(s)';
|
|
}
|
|
barsEl.appendChild(bar);
|
|
|
|
var dt = new Date(d.date);
|
|
var lbl = el('div', null, DAYS_FR[dt.getUTCDay()] + ' ' + dt.getUTCDate());
|
|
lbl.style.flex = '1';
|
|
lbl.style.textAlign = 'center';
|
|
lbl.style.fontSize = '0.6rem';
|
|
lbl.style.color = '#6c757d';
|
|
lblsEl.appendChild(lbl);
|
|
});
|
|
|
|
var tbody = document.getElementById('canvas-tbody');
|
|
tbody.textContent = '';
|
|
history.forEach(function(d) {
|
|
var tr = document.createElement('tr');
|
|
var dt = new Date(d.date);
|
|
var dLbl = DAYS_FR[dt.getUTCDay()] + ' ' + dt.getUTCDate() + '/' + (dt.getUTCMonth() + 1);
|
|
if (!d.action_count) {
|
|
var td = el('td', 'text-muted small', dLbl + ' — aucun log');
|
|
td.colSpan = 6;
|
|
tr.appendChild(td);
|
|
} else {
|
|
tr.appendChild(el('td', 'small', dLbl));
|
|
tr.appendChild(el('td', 'small', String(d.action_count)));
|
|
var errTd = el('td', 'small');
|
|
if ((d.error_count || 0) > 0) {
|
|
errTd.appendChild(el('span', 'badge bg-danger', String(d.error_count)));
|
|
} else {
|
|
errTd.textContent = '—';
|
|
}
|
|
tr.appendChild(errTd);
|
|
tr.appendChild(el('td', 'small', d.presence || '—'));
|
|
tr.appendChild(el('td', 'small', d.active_time || '—'));
|
|
tr.appendChild(el('td', 'small', String(d.sessions || 1)));
|
|
}
|
|
tbody.appendChild(tr);
|
|
});
|
|
}
|
|
|
|
/* --- Alertes --- */
|
|
|
|
function showAlert(type, message) {
|
|
var zone = document.getElementById('alert-zone');
|
|
zone.textContent = '';
|
|
var div = el('div', 'alert alert-' + type);
|
|
div.appendChild(icon('exclamation-triangle'));
|
|
div.appendChild(document.createTextNode(' ' + message));
|
|
zone.appendChild(div);
|
|
}
|
|
|
|
function clearAlert() {
|
|
document.getElementById('alert-zone').textContent = '';
|
|
}
|
|
|
|
/* --- Refresh --- */
|
|
|
|
function refreshUsers() {
|
|
fetch('/api/users')
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
if (data.error) {
|
|
showAlert('warning', data.error);
|
|
if (currentPeriod === 'today') { renderTable([]); renderChart([]); }
|
|
return;
|
|
}
|
|
if (data.no_files) {
|
|
showAlert('info', "Aucun log disponible pour aujourd'hui.");
|
|
if (currentPeriod === 'today') { renderTable([]); renderChart([]); }
|
|
return;
|
|
}
|
|
clearAlert();
|
|
lastTodayUsers = data.users || [];
|
|
currentHourly = data.hourly || [];
|
|
if (currentPeriod === 'today') {
|
|
setTableMode('today');
|
|
renderTable(lastTodayUsers);
|
|
renderChart(currentHourly);
|
|
}
|
|
document.getElementById('last-update').textContent =
|
|
'Mis à jour : ' + new Date().toLocaleTimeString('fr-FR');
|
|
})
|
|
.catch(function() {});
|
|
}
|
|
|
|
document.getElementById('period-select').addEventListener('change', function() {
|
|
currentPeriod = this.value;
|
|
var hint = document.getElementById('chart-hint');
|
|
if (currentPeriod === 'today') {
|
|
hint.classList.add('d-none');
|
|
document.getElementById('chart-container').style.display = 'flex';
|
|
document.getElementById('chart-labels').style.display = 'flex';
|
|
document.getElementById('chart-unavailable').classList.add('d-none');
|
|
setTableMode('today');
|
|
renderTable(lastTodayUsers);
|
|
renderChart(currentHourly);
|
|
} else if (currentPeriod === '7days') {
|
|
fetch('/api/users/activity/weekly')
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) { renderPeakChart(data.weekly || []); })
|
|
.catch(function() {});
|
|
} else if (currentPeriod === '30days') {
|
|
fetch('/api/users/activity/monthly')
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) { renderPeakChart(data.monthly || []); })
|
|
.catch(function() {});
|
|
}
|
|
});
|
|
|
|
setTableMode('today');
|
|
refreshUsers();
|
|
setInterval(refreshUsers, 30000);
|
|
</script>
|
|
{% endblock %}
|