mirror of
https://github.com/nfonteyne/octane-website.git
synced 2026-09-03 23:24:48 +02:00
update: dashboard page admin
This commit is contained in:
parent
a1ec2263a5
commit
1f9c290010
11 changed files with 248 additions and 2 deletions
24
public/admin.html
Normal file
24
public/admin.html
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<!doctype html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Administration — Octane</title>
|
||||
<script src="/js/theme-init.js"></script>
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<header class="app-header">
|
||||
<nav id="main-nav"></nav>
|
||||
</header>
|
||||
<main>
|
||||
<div id="error"></div>
|
||||
<div id="content">Chargement…</div>
|
||||
</main>
|
||||
|
||||
<script src="/js/api.js"></script>
|
||||
<script src="/js/render.js"></script>
|
||||
<script src="/js/nav.js"></script>
|
||||
<script src="/js/admin.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1294,3 +1294,32 @@ a.back-link:hover { color: var(--accent); }
|
|||
}
|
||||
|
||||
.vote-review-detail { margin-top: 0.75rem; }
|
||||
|
||||
/* ---------- Admin dashboard ---------- */
|
||||
|
||||
.admin-user-list { display: flex; flex-direction: column; gap: 0.6rem; }
|
||||
|
||||
.admin-user-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.75rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 0.6rem 0.85rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.admin-user-identity {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.admin-user-counts {
|
||||
display: flex;
|
||||
gap: 0.9rem;
|
||||
color: var(--muted);
|
||||
font-size: 0.88rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
|
|
|||
75
public/js/admin.js
Normal file
75
public/js/admin.js
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
function formatDate(dateStr) {
|
||||
const d = new Date(dateStr);
|
||||
return d.toLocaleDateString('fr-FR', { year: 'numeric', month: 'long', day: 'numeric' });
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
document.getElementById('error').innerHTML = `<div class="error-banner">${escapeHtml(message)}</div>`;
|
||||
}
|
||||
|
||||
function statTile(value, label) {
|
||||
return `
|
||||
<div class="stat-tile">
|
||||
<div class="stat-value">${value}</div>
|
||||
<div class="stat-label">${escapeHtml(label)}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function adminUserRowTemplate(u) {
|
||||
return `
|
||||
<div class="admin-user-row">
|
||||
<div class="admin-user-identity">
|
||||
${avatarHtml(u, 'avatar-sm')}
|
||||
<div>
|
||||
<div class="card-title">${escapeHtml(u.name)}${u.isAdmin ? ' <span class="badge">admin</span>' : ''}</div>
|
||||
<div class="card-subtitle">Membre depuis ${formatDate(u.createdAt)} · Dernière connexion ${formatDate(u.lastSeenAt)}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="admin-user-counts">
|
||||
<span title="Morceaux ajoutés">🎵 ${u.songsAdded}</span>
|
||||
<span title="Suggestions proposées">💡 ${u.suggestionsProposed}</span>
|
||||
<span title="Votes exprimés">✔ ${u.votesCast}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
(async function init() {
|
||||
const me = await initNav('admin');
|
||||
if (!me.isAdmin) {
|
||||
window.location.href = '/index.html';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const stats = await api.get('/api/admin/stats');
|
||||
const container = document.getElementById('content');
|
||||
container.innerHTML = `
|
||||
<h1>Administration</h1>
|
||||
<p class="page-subtitle">Vue d'ensemble de l'activité et des données de l'application.</p>
|
||||
|
||||
<h2>Vue d'ensemble</h2>
|
||||
<div class="stat-grid">
|
||||
${statTile(stats.userCount, 'Utilisateurs')}
|
||||
${statTile(stats.songs.totalSongs, 'Morceaux au répertoire')}
|
||||
${statTile(stats.songs.distinctArtists, 'Artistes différents')}
|
||||
${statTile(stats.songs.tutorialsAdded, 'Tutoriels ajoutés')}
|
||||
${statTile(stats.suggestions.pending, 'Suggestions en attente')}
|
||||
${statTile(stats.suggestions.approved, 'Suggestions approuvées')}
|
||||
${statTile(stats.suggestions.totalVotes, 'Votes exprimés')}
|
||||
${statTile(stats.concerts.pastConcerts, 'Concerts passés')}
|
||||
${statTile(stats.concerts.upcomingConcerts, 'Concerts à venir')}
|
||||
${statTile(stats.concerts.avgSongsPerConcert, 'Morceaux / concert (moy.)')}
|
||||
</div>
|
||||
|
||||
<h2>Activité des utilisateurs</h2>
|
||||
<p class="note">La date de dernière connexion est approximative (dernière synchronisation du profil, pas un journal de connexion précis).</p>
|
||||
<div class="panel admin-user-list">
|
||||
${stats.users.map(adminUserRowTemplate).join('')}
|
||||
</div>
|
||||
`;
|
||||
} catch (err) {
|
||||
showError(err.message);
|
||||
}
|
||||
})();
|
||||
|
|
@ -31,6 +31,7 @@ async function initNav(activePage) {
|
|||
<a href="/setlist.html" class="${activePage === 'setlist' ? 'active' : ''}">Prochain concert</a>
|
||||
<a href="/calendar.html" class="${activePage === 'calendar' ? 'active' : ''}">Disponibilités</a>
|
||||
<a href="/history.html" class="${activePage === 'history' ? 'active' : ''}">Historique</a>
|
||||
${me.isAdmin ? `<a href="/admin.html" class="${activePage === 'admin' ? 'active' : ''}">Administration</a>` : ''}
|
||||
</div>
|
||||
<div class="nav-user">
|
||||
${me.authentikAccountUrl ? `<a href="${escapeHtml(me.authentikAccountUrl)}" target="_blank" rel="noopener" title="Gérer mon compte Authentik (mot de passe, etc.)">Mon compte</a>` : ''}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue