CI / test (push) Successful in 1m53s
- Comptes en base (PBKDF2), rôles, désactivation qui tue la session, audit ; secours environnement pour l'opérateur ; liaison draco par utilisateur ; page /profile. - Espaces de travail : connexions, membres, sélection de projets ; board/notes/épingles/journal scopés par espace ; sélecteur d'espace, badge connexion. - Panneaux /admin (utilisateurs, connexions, espaces, paramètres — l'environnement amorce, le panneau gouverne). - /archives : archive locale réversible, archivage forge confirmé, purge de l'état local, export Markdown, file « à archiver ? » avec archivage en lot. - Correctif session : les comptes membres résolus par la base (la session n'est plus limitée à l'opérateur d'environnement). - 231 tests verts (12 nouveaux au palier 19).
104 lines
5.8 KiB
HTML
104 lines
5.8 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="fr" data-theme="dark">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<meta name="csrf-token" content="{{ csrf_token }}">
|
||
<title>Administration — Kanbante</title>
|
||
<link rel="stylesheet" href="/static/css/style.css?v={{ version }}">
|
||
</head>
|
||
<body>
|
||
{% include "_header.html" %}
|
||
|
||
<main class="focus-main">
|
||
<div class="page-head">
|
||
<h1>🛠️ Administration</h1>
|
||
<p class="page-sub">Utilisateurs, vue d'ensemble et journal d'administration.</p>
|
||
</div>
|
||
{% if msg %}<p class="chip-ok">✅ {{ msg }}</p>{% endif %}
|
||
{% if err %}<p class="chip-warn">⚠️ {{ err }}</p>{% endif %}
|
||
|
||
<section class="briefing-card">
|
||
<h2>👥 Utilisateurs ({{ users|length }})</h2>
|
||
<table class="proj-table">
|
||
<thead>
|
||
<tr><th>Utilisateur</th><th>Rôle</th><th>État</th><th>Draco</th><th>Dernière connexion</th><th>Actions</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for u in users %}
|
||
<tr>
|
||
<td><strong>{{ u.username }}</strong>{% if u.display_name %} <span class="muted">({{ u.display_name }})</span>{% endif %}{% if u.must_change_password %} <span class="chip-warn">mot de passe à changer</span>{% endif %}</td>
|
||
<td>
|
||
<form method="post" action="/admin/users/{{ u.id }}/role" class="inline-form">
|
||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||
<select name="role" onchange="this.form.submit()" {% if current_user.id == u.id %}disabled{% endif %}>
|
||
<option value="member"{% if u.role == 'member' %} selected{% endif %}>Membre</option>
|
||
<option value="admin"{% if u.role == 'admin' %} selected{% endif %}>Admin</option>
|
||
</select>
|
||
</form>
|
||
</td>
|
||
<td>{% if u.active %}🟢 Actif{% else %}🔴 Désactivé{% endif %}</td>
|
||
<td>{% if u.draco_sub %}🔗 lié{% else %}—{% endif %}</td>
|
||
<td>{{ u.last_login_at or '—' }}</td>
|
||
<td class="admin-actions">
|
||
<form method="post" action="/admin/users/{{ u.id }}/toggle" class="inline-form">
|
||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||
<button type="submit" class="btn-mini" {% if current_user.id == u.id %}disabled title="Impossible sur soi-même"{% endif %}>{% if u.active %}Désactiver{% else %}Activer{% endif %}</button>
|
||
</form>
|
||
<form method="post" action="/admin/users/{{ u.id }}/reset" class="inline-form" onsubmit="var p=this.querySelector('input[name=password]'); if(!p.value){p.focus(); return false;}">
|
||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||
<input type="password" name="password" placeholder="Nouveau mot de passe" autocomplete="new-password">
|
||
<button type="submit" class="btn-mini">Réinitialiser</button>
|
||
</form>
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
|
||
<h3>➕ Nouvel utilisateur</h3>
|
||
<form method="post" action="/admin/users" class="admin-form">
|
||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||
<label>Nom d'utilisateur <input type="text" name="username" required autocomplete="off"></label>
|
||
<label>Nom d'affichage <input type="text" name="display_name" autocomplete="off"></label>
|
||
<label>Mot de passe temporaire <input type="password" name="password" required autocomplete="new-password"></label>
|
||
<label>Rôle
|
||
<select name="role">
|
||
<option value="member">Membre</option>
|
||
<option value="admin">Admin</option>
|
||
</select>
|
||
</label>
|
||
<button type="submit" class="btn-save btn-inline">Créer</button>
|
||
</form>
|
||
<p class="notes-hint">L'utilisateur rejoint automatiquement l'espace Principal comme membre et devra changer son mot de passe temporaire (rappel affiché ici et dans son profil).</p>
|
||
</section>
|
||
|
||
<section class="briefing-card">
|
||
<h2>🔭 Vue d'ensemble</h2>
|
||
<p>
|
||
<a href="/admin/connections">🔌 Connexions ({{ connections|length }})</a> ·
|
||
<a href="/admin/workspaces">🧭 Espaces ({{ workspaces|length }})</a> ·
|
||
<a href="/admin/settings">🎛️ Paramètres</a> ·
|
||
<a href="/archives">🗄️ Archives</a>
|
||
</p>
|
||
</section>
|
||
|
||
<section class="briefing-card">
|
||
<h2>📜 Journal d'administration</h2>
|
||
{% if audit %}
|
||
<table class="proj-table">
|
||
<thead><tr><th>Quand</th><th>Qui</th><th>Action</th><th>Détails</th></tr></thead>
|
||
<tbody>
|
||
{% for a in audit %}
|
||
<tr><td>{{ a.created_at }}</td><td>{{ a.username or '—' }}</td><td><code>{{ a.action }}</code></td><td>{{ a.details }}</td></tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
{% else %}
|
||
<p class="muted">Aucune action d'administration journalisée pour l'instant.</p>
|
||
{% endif %}
|
||
</section>
|
||
</main>
|
||
</body>
|
||
</html>
|