- Nginx: headers HSTS/X-Frame/nosniff/CSP, rate limit login 5r/m - CSP: self only, unsafe-inline (Tailwind JIT), object-src none, pas de CDN externe - Assets locaux: Tailwind/HTMX/Alpine.js téléchargés dans /static/js/ - ACL réseau: table allowed_networks administrable depuis Settings - Fichier /etc/nginx/patchcenter_acl.conf régénéré auto depuis la base - PostgreSQL: logs connexion/déconnexion, requêtes lentes >1s, max 50 conn - REVOKE CREATE pour user patchcenter, role readonly créé - SSH: clé only, 3 tentatives, pas de TCP forwarding - Backup toutes les 30min, rétention 3 jours - Application 100% hors ligne (aucune dépendance internet côté navigateur) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
468 lines
28 KiB
HTML
468 lines
28 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-gray">En attente</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 %}
|
|
|
|
</div>
|
|
{% endblock %}
|