patchcenter/app/templates/users_add.html
Admin MPCZ 8479d7280e Users/Contacts: workflow profils + LDAP + sync iTop + etat aligne
- Users: 4 profils (admin/coordinator/operator/viewer) remplacent la matrix
- /users/add: picker contacts iTop (plus de creation libre)
- /me/change-password: flow force_password_change
- LDAP: service + section settings + option login
- Sync iTop contacts: filtre par teams (SecOps/iPOP/Externe/DSI/Admin DSI)
- Auto-desactivation users si contact inactif
- etat: alignement sur enum iTop (production/implementation/stock/obsolete)
- Menu: Contacts dans Administration, Serveurs en groupe repliable
- Audit bases: demo/prod via JWT mode

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 18:50:43 +02:00

123 lines
5.9 KiB
HTML

{% extends 'base.html' %}
{% block title %}Ajouter un utilisateur{% endblock %}
{% block content %}
<div class="flex justify-between items-center mb-4">
<div>
<a href="/users" class="text-xs text-gray-500 hover:text-gray-300">&larr; Retour utilisateurs</a>
<h2 class="text-xl font-bold text-cyber-accent">Ajouter un utilisateur</h2>
<p class="text-xs text-gray-500 mt-1">Choisir un contact iTop puis définir son profil PatchCenter</p>
</div>
</div>
<!-- Info -->
<div class="card p-3 mb-4 text-xs text-gray-400" style="background:#111827">
<b class="text-cyber-accent">Périmètre :</b> seuls les contacts synchronisés depuis iTop (Teams SecOps, iPOP, Externe, DSI, Admin DSI) apparaissent ici.
Si la personne n'est pas dans la liste, elle doit d'abord être créée dans iTop par un admin DSI.
</div>
<!-- Filtres -->
<div class="card p-3 mb-4">
<form method="GET" class="flex gap-2 items-center flex-wrap">
<input type="text" name="search" value="{{ search }}" placeholder="Rechercher nom ou email..." class="text-xs py-1 px-2" style="width:250px">
<select name="team" class="text-xs py-1 px-2" style="width:150px">
<option value="">Toutes teams</option>
{% for t in teams %}
<option value="{{ t.team }}" {% if team_filter == t.team %}selected{% endif %}>{{ t.team }}</option>
{% endfor %}
</select>
<button type="submit" class="btn-primary px-3 py-1 text-xs">Filtrer</button>
<a href="/users/add" class="text-xs text-gray-400 hover:text-cyber-accent">Reset</a>
<span class="text-xs text-gray-500 ml-auto">{{ contacts|length }} contact{{ 's' if contacts|length > 1 else '' }}</span>
</form>
</div>
{% if not contacts %}
<div class="card p-6 text-center text-gray-500">
<p>Aucun contact disponible pour créer un utilisateur.</p>
<p class="text-xs mt-2">Lancer une synchro depuis iTop dans <a href="/referentiel" class="text-cyber-accent">Référentiel</a>.</p>
</div>
{% else %}
<!-- Tableau contacts -->
<form method="POST" action="/users/add">
<div class="card overflow-hidden mb-4">
<table class="w-full table-cyber text-xs">
<thead><tr>
<th class="p-2 w-8"></th>
<th class="p-2 text-left">Nom</th>
<th class="p-2 text-left">Email</th>
<th class="p-2">Team iTop</th>
<th class="p-2 text-left">Fonction</th>
<th class="p-2 text-left">Téléphone</th>
</tr></thead>
<tbody>
{% for c in contacts %}
<tr class="border-t border-cyber-border/30 hover:bg-cyber-hover cursor-pointer" onclick="selectContact({{ c.id }}, '{{ c.name|e }}', '{{ c.team|default('') }}')">
<td class="p-2 text-center">
<input type="radio" name="contact_id" value="{{ c.id }}" id="c{{ c.id }}">
</td>
<td class="p-2"><label for="c{{ c.id }}" class="cursor-pointer font-mono">{{ c.name }}</label></td>
<td class="p-2 text-gray-400">{{ c.email }}</td>
<td class="p-2 text-center">{% if c.team %}<span class="badge badge-gray">{{ c.team }}</span>{% else %}—{% endif %}</td>
<td class="p-2 text-gray-400">{{ c.function or '' }}</td>
<td class="p-2 text-gray-400">{{ c.telephone or '' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Formulaire de création -->
<div class="card p-4">
<h3 class="text-sm font-bold text-cyber-accent mb-3">Paramètres du compte <span id="selected-name" class="text-gray-400 font-normal ml-2"></span></h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<div>
<label class="text-xs text-gray-500 block mb-1">Profil (rôle PatchCenter)</label>
<select name="role" id="role-select" class="w-full text-sm">
{% for r in roles %}
<option value="{{ r }}">{{ profile_labels[r] }} — {{ profile_descriptions[r][:60] }}...</option>
{% endfor %}
</select>
</div>
<div>
<label class="text-xs text-gray-500 block mb-1">Type d'authentification</label>
<select name="auth_type" class="w-full text-sm">
<option value="local">Local (mot de passe stocké)</option>
<option value="ldap">LDAP/AD</option>
</select>
</div>
<div>
<label class="text-xs text-gray-500 block mb-1">Nom d'utilisateur (optionnel, dérivé de l'email sinon)</label>
<input type="text" name="username" class="w-full text-sm" placeholder="ex: jean.dupont">
</div>
<div>
<label class="text-xs text-gray-500 block mb-1">Mot de passe initial (optionnel)</label>
<input type="password" name="password" class="w-full text-sm" placeholder="Laisser vide pour générer">
</div>
</div>
<div class="mt-3">
<label class="text-xs text-gray-400 flex items-center gap-2">
<input type="checkbox" name="force_change" checked> Forcer le changement de mot de passe au 1er login
</label>
</div>
<div class="mt-4 flex gap-2">
<button type="submit" class="btn-primary px-4 py-2 text-sm" id="submit-btn" disabled>Créer l'utilisateur</button>
<a href="/users" class="text-xs text-gray-500 hover:text-cyber-accent self-center ml-2">Annuler</a>
</div>
</div>
</form>
{% endif %}
<script>
function selectContact(id, name, team) {
document.getElementById('c' + id).checked = true;
document.getElementById('selected-name').textContent = '— ' + name + (team ? ' (' + team + ')' : '');
document.getElementById('submit-btn').disabled = false;
// Auto-sélection du profil suggéré selon team
var roleSel = document.getElementById('role-select');
if (team === 'SecOps') roleSel.value = 'operator';
else if (team === 'iPOP') roleSel.value = 'coordinator';
else if (team === 'Externe') roleSel.value = 'viewer';
}
</script>
{% endblock %}