- 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>
542 lines
33 KiB
HTML
542 lines
33 KiB
HTML
{% extends 'base.html' %}
|
|
{% block title %}Settings{% endblock %}
|
|
{% block content %}
|
|
<h2 class="text-xl font-bold text-cyber-accent mb-6">Settings</h2>
|
|
|
|
{% if saved %}
|
|
<div class="mb-4 p-3 rounded bg-green-900/30 text-cyber-green text-sm">
|
|
Section "{{ saved }}" sauvegardee.
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% macro section_header(key, title, badge_text, badge_class, extra="") %}
|
|
<button @click="open = open === '{{ key }}' ? '' : '{{ key }}'" class="w-full flex items-center justify-between p-4 hover:bg-cyber-border/20 transition-colors">
|
|
<div class="flex items-center gap-3">
|
|
<span class="text-cyber-accent font-bold">{{ title }}</span>
|
|
<span class="badge {{ badge_class }}">{{ badge_text }}</span>
|
|
{% if extra %}<span class="text-xs text-gray-500">{{ extra }}</span>{% endif %}
|
|
</div>
|
|
<span class="text-gray-500 text-lg" x-text="open === '{{ key }}' ? '▼' : '▶'"></span>
|
|
</button>
|
|
{% endmacro %}
|
|
|
|
<div x-data="{ open: '{{ saved or '' }}' }" class="space-y-2">
|
|
|
|
<!-- Qualys API -->
|
|
{% if visible.qualys %}
|
|
<div class="card overflow-hidden">
|
|
{{ section_header("qualys", "Qualys API", "Connecte", "badge-green", q_tags|string + " tags / " + q_assets|string + " assets / " + q_linked|string + " lies") }}
|
|
<div x-show="open === 'qualys'" class="border-t border-cyber-border p-4">
|
|
<form method="POST" action="/settings/qualys" class="space-y-3">
|
|
<div>
|
|
<label class="text-xs text-gray-500">URL API</label>
|
|
<input type="text" name="qualys_url" value="{{ vals.qualys_url }}" placeholder="https://qualysapi.qualys.eu" class="w-full" {% if not editable.qualys %}disabled{% endif %}>
|
|
</div>
|
|
<div class="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label class="text-xs text-gray-500">Utilisateur</label>
|
|
<input type="text" name="qualys_user" value="{{ vals.qualys_user }}" class="w-full" {% if not editable.qualys %}disabled{% endif %}>
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Mot de passe</label>
|
|
<input type="password" name="qualys_pass" value="{{ vals.qualys_pass }}" class="w-full" {% if not editable.qualys %}disabled{% endif %}>
|
|
</div>
|
|
</div>
|
|
<div class="flex gap-3 items-end">
|
|
<div class="flex-1">
|
|
<label class="text-xs text-gray-500">Proxy</label>
|
|
<input type="text" name="qualys_proxy" value="{{ vals.qualys_proxy }}" placeholder="http://proxy.sanef.fr:8080" class="w-full font-mono text-xs" {% if not editable.qualys %}disabled{% endif %}>
|
|
</div>
|
|
<label class="flex items-center gap-2 text-xs text-gray-400 pb-1">
|
|
<input type="checkbox" name="qualys_bypass_proxy" value="true" {% if vals.qualys_bypass_proxy == 'true' %}checked{% endif %} {% if not editable.qualys %}disabled{% endif %}>
|
|
Bypass proxy (accès direct)
|
|
</label>
|
|
</div>
|
|
{% if editable.qualys %}<button type="submit" class="btn-primary px-4 py-2 text-sm">Sauvegarder</button>{% endif %}
|
|
</form>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- SSH Cle privee -->
|
|
{% if visible.ssh_key %}
|
|
<div class="card overflow-hidden">
|
|
{{ section_header("ssh_key", "SSH Cle privee", "ssh_key", "badge-green") }}
|
|
<div x-show="open === 'ssh_key'" class="border-t border-cyber-border p-4">
|
|
<form method="POST" action="/settings/ssh_key" class="space-y-3">
|
|
<div class="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label class="text-xs text-gray-500">User SSH par defaut</label>
|
|
<input type="text" name="ssh_key_default_user" value="{{ vals.ssh_key_default_user }}" placeholder="root" class="w-full" {% if not editable.ssh_key %}disabled{% endif %}>
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Port par defaut</label>
|
|
<input type="text" name="ssh_key_default_port" value="{{ vals.ssh_key_default_port }}" placeholder="22" class="w-full" {% if not editable.ssh_key %}disabled{% endif %}>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Cle privee (PEM)</label>
|
|
<textarea name="ssh_key_private_key" rows="4" class="w-full font-mono text-xs" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----" {% if not editable.ssh_key %}disabled{% endif %}>{{ vals.ssh_key_private_key }}</textarea>
|
|
</div>
|
|
<p class="text-xs text-gray-600">Surchargeable par serveur (ssh_user, ssh_port dans la fiche serveur).</p>
|
|
{% if editable.ssh_key %}<button type="submit" class="btn-primary px-4 py-2 text-sm">Sauvegarder</button>{% endif %}
|
|
</form>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- SSH Password -->
|
|
{% if visible.ssh_pwd %}
|
|
<div class="card overflow-hidden">
|
|
{{ section_header("ssh_pwd", "SSH Password", "ssh_pwd", "badge-yellow") }}
|
|
<div x-show="open === 'ssh_pwd'" class="border-t border-cyber-border p-4">
|
|
<form method="POST" action="/settings/ssh_pwd" class="space-y-3">
|
|
<div>
|
|
<label class="text-xs text-gray-500">User par defaut</label>
|
|
<input type="text" name="ssh_pwd_default_user" value="{{ vals.ssh_pwd_default_user }}" class="w-full" {% if not editable.ssh_pwd %}disabled{% endif %}>
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Password par defaut</label>
|
|
<input type="password" name="ssh_pwd_default_pass" value="{{ vals.ssh_pwd_default_pass }}" class="w-full" {% if not editable.ssh_pwd %}disabled{% endif %}>
|
|
</div>
|
|
<p class="text-xs text-gray-600">Pour les environnements recette sans cle SSH. Chaque operateur peut configurer son propre compte.</p>
|
|
{% if editable.ssh_pwd %}<button type="submit" class="btn-primary px-4 py-2 text-sm">Sauvegarder</button>{% endif %}
|
|
</form>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- SSH PSMP (CyberArk) -->
|
|
{% if visible.ssh_psmp %}
|
|
<div class="card overflow-hidden">
|
|
{{ section_header("ssh_psmp", "SSH PSMP — CyberArk", "ssh_psmp", "badge-yellow") }}
|
|
<div x-show="open === 'ssh_psmp'" class="border-t border-cyber-border p-4">
|
|
<form method="POST" action="/settings/ssh_psmp" class="space-y-3">
|
|
<div class="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label class="text-xs text-gray-500">Adresse PSMP</label>
|
|
<input type="text" name="psmp_host" value="{{ vals.psmp_host }}" placeholder="psmp.sanef.fr" class="w-full" {% if not editable.ssh_psmp %}disabled{% endif %}>
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Port PSMP</label>
|
|
<input type="text" name="psmp_port" value="{{ vals.psmp_port }}" placeholder="22" class="w-full" {% if not editable.ssh_psmp %}disabled{% endif %}>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Format user</label>
|
|
<input type="text" name="psmp_user_format" value="{{ vals.psmp_user_format }}" placeholder="{cybr_user}@{target_user}@{hostname}" class="w-full font-mono text-xs" {% if not editable.ssh_psmp %}disabled{% endif %}>
|
|
</div>
|
|
<div class="grid grid-cols-3 gap-3">
|
|
<div>
|
|
<label class="text-xs text-gray-500">Compte CyberArk</label>
|
|
<input type="text" name="psmp_cyberark_user" value="{{ vals.psmp_cyberark_user }}" placeholder="CYBP01336" class="w-full" {% if not editable.ssh_psmp %}disabled{% endif %}>
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Utilisateur cible</label>
|
|
<input type="text" name="psmp_target_user" value="{{ vals.psmp_target_user }}" placeholder="cybsecope" class="w-full" {% if not editable.ssh_psmp %}disabled{% endif %}>
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Safe par defaut</label>
|
|
<input type="text" name="psmp_default_safe" value="{{ vals.psmp_default_safe }}" class="w-full" {% if not editable.ssh_psmp %}disabled{% endif %}>
|
|
</div>
|
|
</div>
|
|
<p class="text-xs text-gray-600">Auth keyboard-interactive. Chaque operateur configure son propre compte CyberArk. MDP saisi en session.</p>
|
|
{% if editable.ssh_psmp %}<button type="submit" class="btn-primary px-4 py-2 text-sm">Sauvegarder</button>{% endif %}
|
|
</form>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- RDP PSM (CyberArk) -->
|
|
{% if visible.rdp_psm %}
|
|
<div class="card overflow-hidden">
|
|
{{ section_header("rdp_psm", "RDP PSM — CyberArk", "rdp_psm", "badge-blue") }}
|
|
<div x-show="open === 'rdp_psm'" class="border-t border-cyber-border p-4">
|
|
<form method="POST" action="/settings/rdp_psm" class="space-y-3">
|
|
<div>
|
|
<label class="text-xs text-gray-500">URL PVWA</label>
|
|
<input type="text" name="rdp_psm_pvwa_url" value="{{ vals.rdp_psm_pvwa_url }}" placeholder="https://pvwa.sanef.fr" class="w-full" {% if not editable.rdp_psm %}disabled{% endif %}>
|
|
</div>
|
|
<div class="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label class="text-xs text-gray-500">User PVWA</label>
|
|
<input type="text" name="rdp_psm_pvwa_user" value="{{ vals.rdp_psm_pvwa_user }}" class="w-full" {% if not editable.rdp_psm %}disabled{% endif %}>
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Password PVWA</label>
|
|
<input type="password" name="rdp_psm_pvwa_pass" value="{{ vals.rdp_psm_pvwa_pass }}" class="w-full" {% if not editable.rdp_psm %}disabled{% endif %}>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Connection Component</label>
|
|
<input type="text" name="rdp_psm_component" value="{{ vals.rdp_psm_component }}" placeholder="PSM-RDP" class="w-full" {% if not editable.rdp_psm %}disabled{% endif %}>
|
|
</div>
|
|
<p class="text-xs text-gray-600">Connexion RDP via token PVWA API. Production Windows uniquement.</p>
|
|
{% if editable.rdp_psm %}<button type="submit" class="btn-primary px-4 py-2 text-sm">Sauvegarder</button>{% endif %}
|
|
</form>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- RDP Password — disabled -->
|
|
|
|
<!-- vSphere / vCenters -->
|
|
{% if visible.vsphere %}
|
|
<div class="card overflow-hidden">
|
|
<button @click="open = open === 'vsphere' ? '' : 'vsphere'" class="w-full flex items-center justify-between p-4 hover:bg-cyber-border/20 transition-colors">
|
|
<div class="flex items-center gap-3">
|
|
<span class="text-cyber-accent font-bold">vSphere / vCenters</span>
|
|
<span class="badge badge-gray">Snapshots</span>
|
|
<span class="text-xs text-gray-500">{{ vcenters|selectattr('is_active')|list|length }} actif(s)</span>
|
|
</div>
|
|
<span class="text-gray-500 text-lg" x-text="open === 'vsphere' ? '▼' : '▶'"></span>
|
|
</button>
|
|
<div x-show="open === 'vsphere'" class="border-t border-cyber-border p-4 space-y-4">
|
|
{% if editable.vsphere %}
|
|
<form method="POST" action="/settings/vsphere" class="space-y-3">
|
|
<h4 class="text-xs text-cyber-accent font-bold uppercase">Credentials vSphere (communs)</h4>
|
|
<div class="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label class="text-xs text-gray-500">Utilisateur</label>
|
|
<input type="text" name="vsphere_user" value="{{ vals.vsphere_user }}" class="w-full">
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Mot de passe</label>
|
|
<input type="password" name="vsphere_pass" value="{{ vals.vsphere_pass }}" class="w-full">
|
|
</div>
|
|
</div>
|
|
<button type="submit" class="btn-primary px-4 py-2 text-sm">Sauvegarder credentials</button>
|
|
</form>
|
|
{% endif %}
|
|
|
|
<div>
|
|
<h4 class="text-xs text-cyber-accent font-bold uppercase mb-2">vCenters enregistres</h4>
|
|
<table class="w-full table-cyber text-sm">
|
|
<thead><tr>
|
|
<th class="text-left p-2">Nom</th>
|
|
<th class="text-left p-2">Endpoint</th>
|
|
<th class="p-2">Datacenter</th>
|
|
<th class="text-left p-2">Description</th>
|
|
<th class="p-2">Responsable</th>
|
|
<th class="p-2">Actif</th>
|
|
{% if editable.vsphere %}<th class="p-2">Action</th>{% endif %}
|
|
</tr></thead>
|
|
<tbody>
|
|
{% for vc in vcenters %}
|
|
<tr>
|
|
<td class="p-2">{{ vc.name }}</td>
|
|
<td class="p-2 font-mono text-xs text-cyber-accent">{{ vc.endpoint }}</td>
|
|
<td class="p-2 text-center text-xs">{{ vc.datacenter or '-' }}</td>
|
|
<td class="p-2 text-xs text-gray-400">{{ vc.description or '-' }}</td>
|
|
<td class="p-2 text-center text-xs">{{ vc.responsable or '-' }}</td>
|
|
<td class="p-2 text-center"><span class="badge {% if vc.is_active %}badge-green{% else %}badge-red{% endif %}">{{ 'Oui' if vc.is_active else 'Non' }}</span></td>
|
|
{% if editable.vsphere %}
|
|
<td class="p-2 text-center">
|
|
{% if vc.is_active %}
|
|
<form method="POST" action="/settings/vcenter/{{ vc.id }}/delete" style="display:inline">
|
|
<button type="submit" class="btn-sm bg-red-900/30 text-cyber-red" onclick="return confirm('Desactiver ce vCenter ?')">Desactiver</button>
|
|
</form>
|
|
{% endif %}
|
|
</td>
|
|
{% endif %}
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{% if editable.vsphere %}
|
|
<form method="POST" action="/settings/vcenter/add" class="space-y-3 pt-2 border-t border-cyber-border">
|
|
<h4 class="text-xs text-cyber-accent font-bold uppercase">Ajouter un vCenter</h4>
|
|
<div class="grid grid-cols-3 gap-3">
|
|
<div>
|
|
<label class="text-xs text-gray-500">Nom</label>
|
|
<input type="text" name="vc_name" placeholder="vCenter Senlis" class="w-full" required>
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Endpoint (FQDN)</label>
|
|
<input type="text" name="vc_endpoint" placeholder="vcenter01.sanef.groupe" class="w-full font-mono text-xs" required>
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Datacenter</label>
|
|
<input type="text" name="vc_datacenter" placeholder="DC-Senlis" class="w-full">
|
|
</div>
|
|
</div>
|
|
<div class="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label class="text-xs text-gray-500">Description</label>
|
|
<input type="text" name="vc_description" placeholder="Gestion + hors-prod" class="w-full">
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Responsable</label>
|
|
<input type="text" name="vc_responsable" class="w-full">
|
|
</div>
|
|
</div>
|
|
<button type="submit" class="btn-primary px-4 py-2 text-sm">Ajouter</button>
|
|
</form>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Sécurité -->
|
|
{% if visible.security %}
|
|
<div class="card overflow-hidden">
|
|
{{ section_header("security", "Sécurité", "Réseau", "badge-red") }}
|
|
<div x-show="open === 'security'" class="border-t border-cyber-border p-4 space-y-4">
|
|
<!-- Réseaux autorisés -->
|
|
<h4 class="text-xs text-cyber-accent font-bold uppercase">Réseaux autorisés (nginx ACL)</h4>
|
|
<table class="w-full table-cyber text-sm">
|
|
<thead><tr>
|
|
<th class="text-left p-2">CIDR</th>
|
|
<th class="text-left p-2">Description</th>
|
|
<th class="p-2">Actif</th>
|
|
{% if editable.security %}<th class="p-2">Actions</th>{% endif %}
|
|
</tr></thead>
|
|
<tbody>
|
|
{% for n in allowed_nets %}
|
|
<tr class="{% if not n.is_active %}opacity-40{% endif %}">
|
|
<td class="p-2 font-mono text-cyber-accent">{{ n.cidr }}</td>
|
|
<td class="p-2 text-xs text-gray-400">{{ n.description or '-' }}</td>
|
|
<td class="p-2 text-center"><span class="badge {% if n.is_active %}badge-green{% else %}badge-red{% endif %}">{{ 'Oui' if n.is_active else 'Non' }}</span></td>
|
|
{% if editable.security %}
|
|
<td class="p-2 text-center">
|
|
<div class="flex gap-1 justify-center">
|
|
<form method="POST" action="/settings/network/{{ n.id }}/toggle" style="display:inline">
|
|
<button class="btn-sm bg-cyber-border text-gray-400">{{ 'Désactiver' if n.is_active else 'Activer' }}</button>
|
|
</form>
|
|
<form method="POST" action="/settings/network/{{ n.id }}/delete" style="display:inline">
|
|
<button class="btn-sm bg-red-900/30 text-cyber-red" onclick="return confirm('Supprimer ce réseau ?')">Suppr</button>
|
|
</form>
|
|
</div>
|
|
</td>
|
|
{% endif %}
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
|
|
{% if editable.security %}
|
|
<form method="POST" action="/settings/network/add" class="flex gap-3 items-end mt-2">
|
|
<div>
|
|
<label class="text-xs text-gray-500">CIDR</label>
|
|
<input type="text" name="cidr" placeholder="10.0.0.0/24" class="text-xs py-1 px-2 w-40 font-mono" required>
|
|
</div>
|
|
<div class="flex-1">
|
|
<label class="text-xs text-gray-500">Description</label>
|
|
<input type="text" name="description" placeholder="VPN nomade" class="text-xs py-1 px-2 w-full">
|
|
</div>
|
|
<button type="submit" class="btn-primary px-3 py-1 text-sm">Ajouter</button>
|
|
</form>
|
|
{% endif %}
|
|
|
|
<p class="text-xs text-gray-600 mt-2">Le fichier <code>/etc/nginx/patchcenter_acl.conf</code> est régénéré automatiquement à chaque modification. Nginx est rechargé.</p>
|
|
|
|
<!-- Paramètres sécurité -->
|
|
<h4 class="text-xs text-cyber-accent font-bold uppercase mt-4">Paramètres</h4>
|
|
<form method="POST" action="/settings/security" class="space-y-3">
|
|
<div class="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label class="text-xs text-gray-500">Timeout session (minutes)</label>
|
|
<input type="number" name="security_session_timeout" value="{{ vals.security_session_timeout or '60' }}" class="w-full" {% if not editable.security %}disabled{% endif %}>
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Max tentatives login (rate limit/min)</label>
|
|
<input type="number" name="security_max_login_attempts" value="{{ vals.security_max_login_attempts or '5' }}" class="w-full" {% if not editable.security %}disabled{% endif %}>
|
|
</div>
|
|
</div>
|
|
{% if editable.security %}<button type="submit" class="btn-primary px-4 py-2 text-sm">Sauvegarder</button>{% endif %}
|
|
</form>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Splunk Remote Log -->
|
|
{% if visible.splunk %}
|
|
<div class="card overflow-hidden">
|
|
{{ section_header("splunk", "Splunk — Remote Log", "HEC", "badge-yellow") }}
|
|
<div x-show="open === 'splunk'" class="border-t border-cyber-border p-4">
|
|
<form method="POST" action="/settings/splunk" class="space-y-3">
|
|
<div>
|
|
<label class="text-xs text-gray-500">URL HEC (HTTP Event Collector)</label>
|
|
<input type="text" name="splunk_hec_url" value="{{ vals.splunk_hec_url }}" placeholder="https://splunk.sanef.fr:8088/services/collector" class="w-full" {% if not editable.splunk %}disabled{% endif %}>
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Token HEC</label>
|
|
<input type="password" name="splunk_hec_token" value="{{ vals.splunk_hec_token }}" class="w-full" {% if not editable.splunk %}disabled{% endif %}>
|
|
</div>
|
|
<div class="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label class="text-xs text-gray-500">Index</label>
|
|
<input type="text" name="splunk_index" value="{{ vals.splunk_index }}" placeholder="patchcenter" class="w-full" {% if not editable.splunk %}disabled{% endif %}>
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Sourcetype</label>
|
|
<input type="text" name="splunk_sourcetype" value="{{ vals.splunk_sourcetype }}" placeholder="patchcenter:audit" class="w-full" {% if not editable.splunk %}disabled{% endif %}>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Verifier SSL (true/false)</label>
|
|
<input type="text" name="splunk_verify_ssl" value="{{ vals.splunk_verify_ssl }}" placeholder="true" class="w-full" {% if not editable.splunk %}disabled{% endif %}>
|
|
</div>
|
|
<p class="text-xs text-gray-600">Envoie les evenements de patching vers Splunk via HEC.</p>
|
|
{% if editable.splunk %}<button type="submit" class="btn-primary px-4 py-2 text-sm">Sauvegarder</button>{% endif %}
|
|
</form>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Teams Notifications -->
|
|
{% if visible.teams %}
|
|
<div class="card overflow-hidden">
|
|
{{ section_header("teams", "Teams — Notifications", "Webhook + SharePoint", "badge-blue") }}
|
|
<div x-show="open === 'teams'" class="border-t border-cyber-border p-4">
|
|
<form method="POST" action="/settings/teams" class="space-y-3">
|
|
<h4 class="text-xs text-cyber-accent font-bold uppercase">Canal Teams (Webhook direct)</h4>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Webhook URL</label>
|
|
<input type="text" name="teams_webhook_url" value="{{ vals.teams_webhook_url }}" placeholder="https://outlook.office.com/webhook/..." class="w-full font-mono text-xs" {% if not editable.teams %}disabled{% endif %}>
|
|
</div>
|
|
<h4 class="text-xs text-cyber-accent font-bold uppercase mt-4">Conversation groupe (SharePoint + Power Automate)</h4>
|
|
<div>
|
|
<label class="text-xs text-gray-500">SharePoint Site URL</label>
|
|
<input type="text" name="teams_sp_site_url" value="{{ vals.teams_sp_site_url }}" placeholder="https://sanef.sharepoint.com/sites/SecOps" class="w-full font-mono text-xs" {% if not editable.teams %}disabled{% endif %}>
|
|
</div>
|
|
<div class="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label class="text-xs text-gray-500">Library</label>
|
|
<input type="text" name="teams_sp_library" value="{{ vals.teams_sp_library }}" placeholder="Documents partages" class="w-full" {% if not editable.teams %}disabled{% endif %}>
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Folder</label>
|
|
<input type="text" name="teams_sp_folder" value="{{ vals.teams_sp_folder }}" placeholder="PatchCenter" class="w-full" {% if not editable.teams %}disabled{% endif %}>
|
|
</div>
|
|
</div>
|
|
<div class="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label class="text-xs text-gray-500">Tenant ID (Azure AD)</label>
|
|
<input type="text" name="teams_sp_tenant_id" value="{{ vals.teams_sp_tenant_id }}" class="w-full font-mono text-xs" {% if not editable.teams %}disabled{% endif %}>
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">App Client ID</label>
|
|
<input type="text" name="teams_sp_client_id" value="{{ vals.teams_sp_client_id }}" class="w-full font-mono text-xs" {% if not editable.teams %}disabled{% endif %}>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">App Client Secret</label>
|
|
<input type="password" name="teams_sp_client_secret" value="{{ vals.teams_sp_client_secret }}" class="w-full" {% if not editable.teams %}disabled{% endif %}>
|
|
</div>
|
|
<p class="text-xs text-gray-600">Power Automate : depose JSON sur SharePoint → lit + poste dans la conversation → supprime.</p>
|
|
{% if editable.teams %}<button type="submit" class="btn-primary px-4 py-2 text-sm">Sauvegarder</button>{% endif %}
|
|
</form>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- iTop CMDB -->
|
|
{% if visible.itop %}
|
|
<div class="card overflow-hidden">
|
|
<button @click="open = open === 'itop' ? '' : 'itop'" class="w-full flex items-center justify-between p-4 hover:bg-cyber-border/20 transition-colors">
|
|
<div class="flex items-center gap-3">
|
|
<span class="text-gray-400 font-bold">iTop CMDB</span>
|
|
<span class="badge badge-green">Configuré</span>
|
|
</div>
|
|
<span class="text-gray-500 text-lg" x-text="open === 'itop' ? '▼' : '▶'"></span>
|
|
</button>
|
|
<div x-show="open === 'itop'" class="border-t border-cyber-border p-4">
|
|
<form method="POST" action="/settings/itop" class="space-y-3">
|
|
{% for key, label, is_secret in sections.itop %}
|
|
<div>
|
|
<label class="text-xs text-gray-500">{{ label }}</label>
|
|
<input type="{{ 'password' if is_secret else 'text' }}" name="{{ key }}" value="{{ vals[key] }}" class="w-full" {% if not editable.itop %}disabled{% endif %}>
|
|
</div>
|
|
{% endfor %}
|
|
<div class="text-xs text-gray-600 space-y-1 mt-2">
|
|
<div>- Import serveurs + metadata</div>
|
|
<div>- Sync responsables / referents</div>
|
|
<div>- Lien applications / clusters</div>
|
|
<div>- Enrichissement domaine / environnement</div>
|
|
</div>
|
|
{% if editable.itop %}<button type="submit" class="btn-primary px-4 py-2 text-sm">Sauvegarder</button>{% endif %}
|
|
</form>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- iTop Contacts (filtre des teams à synchroniser) -->
|
|
{% if visible.itop_contacts %}
|
|
<div class="card overflow-hidden">
|
|
<button @click="open = open === 'itop_contacts' ? '' : 'itop_contacts'" class="w-full flex items-center justify-between p-4 hover:bg-cyber-border/20 transition-colors">
|
|
<div class="flex items-center gap-3">
|
|
<span class="text-gray-400 font-bold">iTop Contacts — Périmètre</span>
|
|
<span class="badge badge-blue">Filtre des teams</span>
|
|
</div>
|
|
<span class="text-gray-500 text-lg" x-text="open === 'itop_contacts' ? '▼' : '▶'"></span>
|
|
</button>
|
|
<div x-show="open === 'itop_contacts'" class="border-t border-cyber-border p-4">
|
|
<form method="POST" action="/settings/itop_contacts" class="space-y-3">
|
|
{% for key, label, is_secret in sections.itop_contacts %}
|
|
<div>
|
|
<label class="text-xs text-gray-500">{{ label }}</label>
|
|
<input type="text" name="{{ key }}" value="{{ vals[key] }}" placeholder="SecOps, iPOP, Externe, DSI, Admin DSI" class="w-full font-mono text-xs" {% if not editable.itop_contacts %}disabled{% endif %}>
|
|
</div>
|
|
{% endfor %}
|
|
<div class="text-xs text-gray-600 mt-2">
|
|
Seuls les contacts appartenant à ces teams iTop seront synchronisés dans PatchCenter. Si vide, défaut : SecOps, iPOP, Externe, DSI, Admin DSI.
|
|
</div>
|
|
{% if editable.itop_contacts %}<button type="submit" class="btn-primary px-4 py-2 text-sm">Sauvegarder</button>{% endif %}
|
|
</form>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- LDAP/AD -->
|
|
{% if visible.ldap %}
|
|
<div class="card overflow-hidden">
|
|
<button @click="open = open === 'ldap' ? '' : 'ldap'" class="w-full flex items-center justify-between p-4 hover:bg-cyber-border/20 transition-colors">
|
|
<div class="flex items-center gap-3">
|
|
<span class="text-gray-400 font-bold">LDAP / Active Directory</span>
|
|
<span class="badge {% if vals.ldap_enabled == 'true' %}badge-green{% else %}badge-gray{% endif %}">{{ 'Activé' if vals.ldap_enabled == 'true' else 'Désactivé' }}</span>
|
|
<span class="text-xs text-gray-500">{{ vals.ldap_server or '' }}</span>
|
|
</div>
|
|
<span class="text-gray-500 text-lg" x-text="open === 'ldap' ? '▼' : '▶'"></span>
|
|
</button>
|
|
<div x-show="open === 'ldap'" class="border-t border-cyber-border p-4">
|
|
<form method="POST" action="/settings/ldap" class="space-y-3">
|
|
{% for key, label, is_secret in sections.ldap %}
|
|
<div>
|
|
<label class="text-xs text-gray-500">{{ label }}</label>
|
|
<input type="{{ 'password' if is_secret else 'text' }}" name="{{ key }}" value="{{ vals[key] }}" class="w-full" {% if not editable.ldap %}disabled{% endif %}>
|
|
</div>
|
|
{% endfor %}
|
|
<div class="text-xs text-gray-600 mt-2">
|
|
Une fois configuré et activé, le choix <b>Local / LDAP</b> apparaîtra sur la page de connexion. Les users peuvent aussi être forcés en LDAP via le champ "Auth" dans /users.
|
|
</div>
|
|
<div class="flex gap-2 items-center">
|
|
{% if editable.ldap %}<button type="submit" class="btn-primary px-4 py-2 text-sm">Sauvegarder</button>{% endif %}
|
|
<button type="button" onclick="testLdap()" class="btn-sm bg-cyber-border text-gray-300 px-4 py-2">Tester la connexion</button>
|
|
<span id="ldap-test-result" class="text-xs ml-2"></span>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
</div>
|
|
|
|
<script>
|
|
function testLdap() {
|
|
var out = document.getElementById('ldap-test-result');
|
|
out.textContent = 'Test en cours...';
|
|
out.className = 'text-xs ml-2 text-gray-400';
|
|
fetch('/settings/ldap/test', {method: 'POST', credentials: 'same-origin'})
|
|
.then(function(r){ return r.json(); })
|
|
.then(function(d){
|
|
if (d.ok) { out.textContent = '✓ ' + (d.msg || 'OK'); out.className = 'text-xs ml-2 text-cyber-green'; }
|
|
else { out.textContent = '✗ ' + (d.msg || 'Erreur'); out.className = 'text-xs ml-2 text-cyber-red'; }
|
|
})
|
|
.catch(function(e){ out.textContent = '✗ ' + e.message; out.className = 'text-xs ml-2 text-cyber-red'; });
|
|
}
|
|
</script>
|
|
{% endblock %}
|