- Permissions 100% depuis user_permissions (plus de hardcode) - Middleware injecte perms dans chaque requête - Créneaux auto: 09h-12h30 / 14h-16h45, pas 15min, hprod lun-mar, prod mer-jeu - Assignations par défaut: par domaine, app_type, zone, serveur (table default_assignments) - Auto-liaison app_group: même intervenant recette+prod - Audit Splunk: /var/log/patchcenter_audit.json (JSON one-line par event) - Login/logout/campagnes/prereqs loggés en base + fichier - Page erreur maintenance (500/404) avec contact SecOps - Accents français dans toute lUI - Operator affiché comme Intervenant - Session 1h, redirect / vers dashboard si connecté - Demo mode prereqs (DEMO_MODE=True) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
137 lines
8.1 KiB
HTML
137 lines
8.1 KiB
HTML
{% extends 'base.html' %}
|
|
{% block title %}Utilisateurs{% endblock %}
|
|
{% block content %}
|
|
<h2 class="text-xl font-bold text-cyber-accent mb-6">Utilisateurs & Permissions</h2>
|
|
|
|
{% if msg %}
|
|
<div class="mb-4 p-3 rounded text-sm {% if msg in ('forbidden','exists','exists_inactive','cant_self') %}bg-red-900/30 text-cyber-red{% else %}bg-green-900/30 text-cyber-green{% endif %}">
|
|
{% if msg == 'added' %}Utilisateur créé.{% elif msg == 'edited' %}Utilisateur modifié.{% elif msg == 'password_changed' %}Mot de passe modifié.{% elif msg == 'toggled' %}Statut modifié.{% elif msg == 'perms_saved' %}Permissions sauvegardées.{% elif msg == 'deleted' %}Utilisateur supprimé.{% elif msg == 'exists' %}Ce nom d'utilisateur existe déjà.{% elif msg == 'exists_inactive' %}Ce nom existe déjà (désactivé). Réactivez-le plutôt.{% elif msg == 'cant_self' %}Vous ne pouvez pas vous désactiver/supprimer vous-même.{% elif msg == 'forbidden' %}Action non autorisée.{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Liste utilisateurs -->
|
|
<div x-data="{ editing: '', editUser: null }" class="space-y-3">
|
|
{% for ud in users_data %}
|
|
<div class="card overflow-hidden">
|
|
<div class="flex items-center justify-between p-4 cursor-pointer hover:bg-cyber-border/20" @click="editing = editing === '{{ ud.user.id }}' ? '' : '{{ ud.user.id }}'">
|
|
<div class="flex items-center gap-3">
|
|
<span class="font-bold {% if ud.user.is_active %}text-cyber-accent{% else %}text-gray-600 line-through{% endif %}">{{ ud.user.username }}</span>
|
|
<span class="text-sm text-gray-400">{{ ud.user.display_name }}</span>
|
|
<span class="badge {% if ud.user.role == 'admin' %}badge-red{% elif ud.user.role == 'coordinator' %}badge-yellow{% elif ud.user.role == 'operator' %}badge-blue{% else %}badge-gray{% endif %}">{% if ud.user.role == "operator" %}intervenant{% else %}{{ ud.user.role }}{% endif %}</span>
|
|
<span class="badge {% if ud.user.is_active %}badge-green{% else %}badge-red{% endif %}">{{ 'Actif' if ud.user.is_active else 'Inactif' }}</span>
|
|
{% if ud.user.email %}<span class="text-xs text-gray-500">{{ ud.user.email }}</span>{% endif %}
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
{% for m in modules %}
|
|
{% if ud.perms.get(m) %}
|
|
<span class="text-xs px-1 rounded {% if ud.perms[m] == 'admin' %}bg-red-900/30 text-cyber-red{% elif ud.perms[m] == 'edit' %}bg-blue-900/30 text-cyber-accent{% else %}bg-gray-800 text-gray-500{% endif %}" title="{{ m }}:{{ ud.perms[m] }}">{{ m[:3] }}</span>
|
|
{% endif %}
|
|
{% endfor %}
|
|
<span class="text-gray-500 text-lg" x-text="editing === '{{ ud.user.id }}' ? '▼' : '▶'"></span>
|
|
</div>
|
|
</div>
|
|
|
|
<div x-show="editing === '{{ ud.user.id }}'" class="border-t border-cyber-border p-4 space-y-4">
|
|
{% if can_edit_users %}
|
|
<!-- Éditer infos user -->
|
|
<form method="POST" action="/users/{{ ud.user.id }}/edit" class="flex gap-3 items-end">
|
|
<div>
|
|
<label class="text-xs text-gray-500">Nom complet</label>
|
|
<input type="text" name="display_name" value="{{ ud.user.display_name }}" class="text-xs py-1 px-2 w-40">
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Email</label>
|
|
<input type="email" name="email" value="{{ ud.user.email or '' }}" class="text-xs py-1 px-2 w-44">
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Role</label>
|
|
<select name="role" class="text-xs py-1 px-2">
|
|
{% for r in ['admin','coordinator','operator','viewer'] %}
|
|
<option value="{{ r }}" {% if r == ud.user.role %}selected{% endif %}>{% if r == "operator" %}intervenant{% else %}{{ r }}{% endif %}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<button type="submit" class="btn-sm bg-cyber-accent text-black">Modifier</button>
|
|
</form>
|
|
|
|
<!-- Permissions par module -->
|
|
<form method="POST" action="/users/{{ ud.user.id }}/permissions">
|
|
<h4 class="text-xs text-cyber-accent font-bold uppercase mb-2">Permissions par module</h4>
|
|
<div class="grid grid-cols-8 gap-2">
|
|
{% for m in modules %}
|
|
<div>
|
|
<label class="text-xs text-gray-500 block mb-1">{{ m }}</label>
|
|
<select name="perm_{{ m }}" class="w-full text-xs py-1">
|
|
<option value="">—</option>
|
|
{% for l in levels %}
|
|
<option value="{{ l }}" {% if ud.perms.get(m) == l %}selected{% endif %}>{{ l }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
<button type="submit" class="btn-primary px-4 py-1 text-sm mt-2">Sauvegarder permissions</button>
|
|
</form>
|
|
|
|
<!-- Actions -->
|
|
<div class="flex gap-3 pt-2 border-t border-cyber-border items-center">
|
|
<form method="POST" action="/users/{{ ud.user.id }}/password" class="flex gap-2 items-center">
|
|
<input type="password" name="new_password" placeholder="Nouveau mot de passe" class="text-xs py-1 px-2 w-48">
|
|
<button type="submit" class="btn-sm bg-cyber-border text-cyber-accent">Changer MDP</button>
|
|
</form>
|
|
<form method="POST" action="/users/{{ ud.user.id }}/toggle">
|
|
<button type="submit" class="btn-sm {% if ud.user.is_active %}bg-red-900/30 text-cyber-red{% else %}bg-green-900/30 text-cyber-green{% endif %}">
|
|
{{ 'Désactiver' if ud.user.is_active else 'Activer' }}
|
|
</button>
|
|
</form>
|
|
<form method="POST" action="/users/{{ ud.user.id }}/delete">
|
|
<button type="submit" class="btn-sm bg-red-900/50 text-cyber-red" onclick="return confirm('SUPPRIMER définitivement {{ ud.user.username }} ?')">Supprimer</button>
|
|
</form>
|
|
</div>
|
|
{% else %}
|
|
<p class="text-xs text-gray-500">Permissions en lecture seule</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<!-- Ajouter un utilisateur -->
|
|
{% if can_edit_users %}
|
|
<div class="card p-5 mt-6">
|
|
<h3 class="text-sm font-bold text-cyber-accent mb-3">Ajouter un utilisateur</h3>
|
|
<form method="POST" action="/users/add" class="space-y-3">
|
|
<div class="grid grid-cols-4 gap-3">
|
|
<div>
|
|
<label class="text-xs text-gray-500">Username</label>
|
|
<input type="text" name="new_username" required class="w-full">
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Nom complet</label>
|
|
<input type="text" name="new_display_name" required class="w-full">
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Email</label>
|
|
<input type="email" name="new_email" class="w-full">
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Role</label>
|
|
<select name="new_role" class="w-full">
|
|
<option value="operator">intervenant</option>
|
|
<option value="coordinator">coordinator</option>
|
|
<option value="admin">admin</option>
|
|
<option value="viewer">viewer</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="w-64">
|
|
<label class="text-xs text-gray-500">Mot de passe</label>
|
|
<input type="password" name="new_password" required class="w-full">
|
|
</div>
|
|
<p class="text-xs text-gray-600">Permissions pre-remplies selon le role. Modifiables ensuite.</p>
|
|
<button type="submit" class="btn-primary px-4 py-2 text-sm">Créer</button>
|
|
</form>
|
|
</div>
|
|
{% endif %}
|
|
{% endblock %}
|