- 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>
123 lines
5.9 KiB
HTML
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">← 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 %}
|