Files
NexusPBX/public/index.php
T

5367 lines
441 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
require_once __DIR__ . '/../src/Database.php';
require_once __DIR__ . '/../src/Auth.php';
// Autoloader PSR-4 automatique pour toutes les classes du namespace App
spl_autoload_register(function (string $class): void {
if (str_starts_with($class, 'App\\')) {
$file = __DIR__ . '/../src/' . str_replace('\\', '/', substr($class, 4)) . '.php';
if (file_exists($file)) {
require_once $file;
}
}
});
use App\Database;
use App\Auth;
Database::getConnection();
$isAuth = Auth::isAuthenticated();
$currentUser = Auth::getUsername();
$stmtSettings = Database::getConnection()->query('SELECT company_name, company_logo FROM settings WHERE id = 1 LIMIT 1');
$branding = $stmtSettings ? $stmtSettings->fetch(\PDO::FETCH_ASSOC) : null;
$companyName = trim($branding['company_name'] ?? '');
$companyLogo = trim($branding['company_logo'] ?? '');
$hasBranding = !empty($companyName) || !empty($companyLogo);
$appVerData = file_exists(__DIR__ . '/../version.json') ? json_decode((string)file_get_contents(__DIR__ . '/../version.json'), true) : [];
$appVersion = $appVerData['version'] ?? '1.5.0';
// Détection de l'environnement de développement : présence de .dev local ou branche git dev
$isDevEnv = file_exists(__DIR__ . '/../.dev')
|| (trim((string)@shell_exec('git -C ' . escapeshellarg(__DIR__) . ' rev-parse --abbrev-ref HEAD 2>/dev/null')) === 'dev');
?>
<!DOCTYPE html>
<html lang="fr" class="h-full">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="nexus-app-version" content="<?= htmlspecialchars($appVersion) ?>">
<title><?= $isDevEnv ? '🛠️ [DEV] ' : '' ?>NexusPBX — Supervision & Téléphonie IP • Powered by Asterisk</title>
<link rel="icon" type="image/png" href="/assets/favicon.png?v=<?= file_exists(__DIR__ . '/assets/favicon.png') ? filemtime(__DIR__ . '/assets/favicon.png') : 1 ?>">
<link rel="apple-touch-icon" href="/assets/logo.png?v=<?= file_exists(__DIR__ . '/assets/logo.png') ? filemtime(__DIR__ . '/assets/logo.png') : 1 ?>">
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
anthracite: {
950: '#1b1f27',
900: '#232832',
800: '#2c323f',
750: '#323947',
700: '#3d4657',
600: '#4f5b70',
}
},
fontFamily: {
sans: ['Inter', 'system-ui', '-apple-system', 'sans-serif'],
mono: ['JetBrains Mono', 'Fira Code', 'monospace'],
}
}
}
};
</script>
<style>
body { background-color: #232832; color: #f1f5f9; }
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: #1b1f27; }
::-webkit-scrollbar-thumb { background: #3d4657; border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: #4f5b70; }
/* ========================================================================== */
/* ALIGNEMENT & HARMONISATION PARFAITE : INPUTS ET SELECTS DE MÊME TAILLE */
/* ========================================================================== */
/* 1. Hauteur standardisée à 40px pour tous les champs de saisie et listes déroulantes */
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="submit"]):not([type="button"]):not([type="file"]):not([type="hidden"]),
select {
box-sizing: border-box !important;
height: 40px !important;
min-height: 40px !important;
max-height: 40px !important;
line-height: 20px !important;
font-size: 0.875rem !important; /* 14px / text-sm */
padding-top: 8px !important;
padding-bottom: 8px !important;
vertical-align: middle !important;
}
/* 2. Hauteur compacte à 34px pour les tableaux, DTMF, SDA et filtres denses */
input.text-xs,
select.text-xs,
.compact-field,
.ivr-digit,
.ivr-dest,
.trunk-did-cid,
.trunk-did-desc,
.trunk-did-dest,
.trunk-tc-start,
.trunk-tc-finish,
.trunk-tc-weekdays,
.trunk-tc-closed-dest,
#filter-sound-site,
#filter-sound-usage,
#filter-sound-search,
#inbound-routes-filter {
height: 34px !important;
min-height: 34px !important;
max-height: 34px !important;
line-height: 18px !important;
font-size: 0.75rem !important; /* 12px / text-xs */
padding-top: 6px !important;
padding-bottom: 6px !important;
}
/* 3. Habillage moderne des listes déroulantes (select) : flèche SVG centrée et suppression du widget OS */
select {
-webkit-appearance: none !important;
-moz-appearance: none !important;
appearance: none !important;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%2394a3b8' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e") !important;
background-position: right 0.75rem center !important;
background-repeat: no-repeat !important;
background-size: 1.15em 1.15em !important;
padding-right: 2.25rem !important;
cursor: pointer;
}
select option,
select optgroup {
background-color: #1b1f27 !important;
color: #f1f5f9 !important;
}
/* 4. Focus harmonisé */
input:focus,
select:focus,
textarea:focus {
outline: none !important;
border-color: #6366f1 !important;
box-shadow: 0 0 0 1px rgba(99, 102, 241, 0.4) !important;
}
/* 5. Layout flex garanti pour les écrans login et dashboard */
#login-screen:not(.hidden) {
display: flex !important;
}
#login-screen.hidden {
display: none !important;
}
#app-screen:not(.hidden) {
display: flex !important;
}
#app-screen.hidden {
display: none !important;
}
</style>
</head>
<body class="min-h-screen w-full bg-anthracite-900 text-slate-100 antialiased selection:bg-indigo-500 selection:text-white">
<!-- Conteneur de notifications Toast (AJAX) -->
<div id="toast-container" class="fixed bottom-5 right-5 z-50 flex flex-col gap-2.5 max-w-md w-full pointer-events-none px-4 sm:px-0"></div>
<!-- ÉCRAN DE CONNEXION -->
<div id="login-screen" class="<?= $isAuth ? 'hidden' : 'flex' ?> min-h-screen w-full items-center justify-center p-4 relative overflow-hidden bg-anthracite-950">
<!-- Arrière-plan personnalisé / Image de fond avec overlay sombre et léger flou -->
<div id="login-bg-layer" class="absolute inset-0 bg-cover bg-center bg-no-repeat" style="background-image: url('/assets/login-bg.jpg?v=<?= file_exists(__DIR__ . '/assets/login-bg.jpg') ? filemtime(__DIR__ . '/assets/login-bg.jpg') : 1 ?>');"></div>
<div class="absolute inset-0 bg-gradient-to-tr from-anthracite-950/70 via-anthracite-950/50 to-indigo-950/30 backdrop-blur-[1px]"></div>
<!-- Carte de connexion flottante avec effet Glassmorphism transparent et aéré -->
<div class="relative z-10 w-full max-w-md bg-anthracite-900/60 backdrop-blur-2xl border border-white/15 rounded-3xl shadow-2xl shadow-black/50 p-8 sm:p-9 transition-all">
<div class="flex items-center gap-4 mb-6">
<img src="/assets/logo.png?v=<?= file_exists(__DIR__ . '/assets/logo.png') ? filemtime(__DIR__ . '/assets/logo.png') : 1 ?>" alt="NexusPBX Logo" class="w-20 h-20 sm:w-24 sm:h-24 object-contain shrink-0 filter drop-shadow-xl">
<div>
<h1 class="text-2xl sm:text-3xl font-black text-white tracking-tight leading-none mb-1 flex items-center gap-2">
NexusPBX
<?php if ($isDevEnv): ?>
<span class="px-2 py-0.5 text-xs font-mono font-extrabold tracking-wider rounded bg-amber-500/20 text-amber-300 border border-amber-500/40">DEV</span>
<?php endif; ?>
</h1>
<p class="text-xs text-slate-300">Supervision & Téléphonie IP • Powered by Asterisk</p>
</div>
</div>
<!-- Aperçu Logo Entreprise sur l'écran de connexion (rendu serveur natif) -->
<div id="login-company-box" class="<?= $hasBranding ? 'flex' : 'hidden' ?> mb-5 p-3 rounded-xl bg-anthracite-950/45 border border-white/10 backdrop-blur-md items-center justify-center gap-3">
<img id="login-company-logo" src="<?= htmlspecialchars($companyLogo) ?>" alt="Logo Entreprise" class="max-h-10 max-w-[160px] object-contain <?= !empty($companyLogo) ? '' : 'hidden' ?>">
<span id="login-company-name" class="text-xs font-semibold text-slate-200 <?= !empty($companyName) ? '' : 'hidden' ?>"><?= htmlspecialchars($companyName) ?></span>
</div>
<form id="login-form" class="space-y-4">
<div>
<label class="block text-xs font-semibold uppercase tracking-wider text-slate-300 mb-1.5">Identifiant</label>
<input type="text" name="username" value="admin" required
class="w-full px-3.5 py-2.5 rounded-xl bg-anthracite-950/50 backdrop-blur-md border border-white/10 text-white placeholder-slate-400 focus:outline-none focus:border-indigo-400 focus:ring-2 focus:ring-indigo-500/20 text-sm transition">
</div>
<div>
<label class="block text-xs font-semibold uppercase tracking-wider text-slate-300 mb-1.5">Mot de passe</label>
<input type="password" name="password" placeholder="••••••••" required
class="w-full px-3.5 py-2.5 rounded-xl bg-anthracite-950/50 backdrop-blur-md border border-white/10 text-white placeholder-slate-400 focus:outline-none focus:border-indigo-400 focus:ring-2 focus:ring-indigo-500/20 text-sm transition">
</div>
<button type="submit"
class="w-full py-2.5 px-4 bg-gradient-to-r from-indigo-600 to-indigo-500 hover:from-indigo-500 hover:to-indigo-400 active:from-indigo-700 active:to-indigo-600 text-white font-semibold rounded-xl shadow-lg shadow-indigo-500/25 transition text-sm">
Se connecter au PBX
</button>
</form>
</div>
</div>
<!-- APPLICATION PRINCIPALE PLEIN ÉCRAN (SIDEBAR GAUCHE + CONTENU FLUIDE) -->
<div id="app-screen" class="<?= $isAuth ? 'flex' : 'hidden' ?> min-h-screen w-full bg-anthracite-900">
<!-- Overlay Mobile pour le menu latéral -->
<div id="sidebar-backdrop" onclick="toggleMobileSidebar(false)" class="fixed inset-0 z-30 bg-black/60 backdrop-blur-xs hidden lg:hidden"></div>
<!-- MENU LATÉRAL GAUCHE (SIDEBAR ANTHRACITE) -->
<aside id="sidebar" class="fixed lg:static inset-y-0 left-0 z-40 w-72 bg-anthracite-950 border-r border-anthracite-700 flex flex-col justify-between transform -translate-x-full lg:translate-x-0 transition-transform duration-200 ease-in-out shrink-0">
<div>
<!-- Logo NexusPBX & Espace Exploitant (Opéré par) -->
<div class="px-4 py-3.5 border-b border-anthracite-700/80 space-y-2.5">
<div class="flex items-center justify-between">
<div class="flex items-center gap-3">
<img src="/assets/logo.png?v=<?= file_exists(__DIR__ . '/assets/logo.png') ? filemtime(__DIR__ . '/assets/logo.png') : 1 ?>" alt="NexusPBX Logo" class="w-14 h-14 object-contain shrink-0 filter drop-shadow-md">
<div>
<div class="flex items-center gap-2">
<div class="font-black text-white text-2xl tracking-tight leading-none">
Nexus<span class="text-indigo-400">PBX</span>
</div>
<span id="app-version-badge" class="px-1.5 py-0.5 text-[10px] font-mono font-bold tracking-wider rounded bg-indigo-500/20 text-indigo-300 border border-indigo-500/30">v<?= htmlspecialchars($appVersion) ?></span>
<?php if ($isDevEnv): ?>
<span class="px-1.5 py-0.5 text-[10px] font-mono font-black tracking-wider rounded bg-amber-500/25 text-amber-300 border border-amber-500/50 shadow-xs shadow-amber-500/20 animate-pulse">DEV</span>
<?php endif; ?>
</div>
<div class="text-[10px] text-slate-400 font-medium tracking-wide mt-0.5">Powered by Asterisk</div>
</div>
</div>
<button type="button" onclick="toggleMobileSidebar(false)" class="lg:hidden text-slate-400 hover:text-white p-1">✕</button>
</div>
<!-- Espace Logo Exploitant (Affichage pur : modification uniquement dans Administration / Paramètres) -->
<div id="sidebar-company-slot" class="rounded-xl bg-anthracite-900/90 border border-anthracite-750/70 p-2.5 flex flex-col items-center justify-center min-h-[46px] select-none">
<!-- État rempli : Logo et/ou Nom de l'exploitant affiché -->
<div id="sidebar-company-filled" class="hidden w-full flex flex-col items-center justify-center gap-1 text-center">
<span class="text-[9px] font-bold uppercase tracking-widest text-slate-400">Opéré par</span>
<img id="sidebar-company-logo" src="" alt="Logo Exploitant" class="max-h-9 max-w-[190px] object-contain hidden">
<span id="sidebar-company-name" class="text-xs font-semibold text-slate-200 tracking-wide truncate max-w-[210px] hidden"></span>
</div>
<!-- État vide : Indicateur sobre et propre -->
<div id="sidebar-company-empty" class="w-full flex items-center justify-center gap-2 py-0.5 text-[11px] text-slate-400 font-medium">
<span class="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse"></span>
<span>Plateforme Opérationnelle</span>
</div>
</div>
</div>
<!-- Navigation principale -->
<div class="p-3.5 space-y-1">
<div class="px-3 py-1.5 text-[11px] font-bold uppercase tracking-wider text-slate-400">Navigation</div>
<!-- Bouton Accueil / Dashboard -->
<button type="button" data-tab="dashboard" onclick="switchTab('dashboard')"
class="tab-btn w-full flex items-center justify-between px-3.5 py-2.5 rounded-xl text-sm font-semibold bg-indigo-600 text-white shadow-md shadow-indigo-600/20 transition">
<span class="flex items-center gap-3">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/>
</svg>
<span>Accueil & Dashboard</span>
</span>
<span class="w-2 h-2 rounded-full bg-emerald-300 animate-pulse"></span>
</button>
<!-- Journal d'appels (CDR) -->
<button type="button" data-tab="cdr" onclick="switchTab('cdr')"
class="tab-btn w-full flex items-center justify-between px-3.5 py-2.5 rounded-xl text-sm font-medium text-slate-300 hover:bg-anthracite-800 hover:text-white transition">
<span class="flex items-center gap-3">
<svg class="w-5 h-5 text-sky-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<span>Journal d'appels (CDR)</span>
</span>
<span id="nav-badge-cdr" class="px-2 py-0.5 text-xs rounded-full bg-anthracite-800 text-sky-300 border border-anthracite-700 font-bold">CDR</span>
</button>
<div class="pt-3 px-3 py-1.5 text-[11px] font-bold uppercase tracking-wider text-slate-400">Téléphonie IP</div>
<!-- Postes SIP -->
<button type="button" data-tab="extensions" onclick="switchTab('extensions')"
class="tab-btn w-full flex items-center justify-between px-3.5 py-2.5 rounded-xl text-sm font-medium text-slate-300 hover:bg-anthracite-800 hover:text-white transition">
<span class="flex items-center gap-3">
<svg class="w-5 h-5 text-indigo-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 18h.01M8 21h8a2 2 0 002-2V5a2 2 0 00-2-2H8a2 2 0 00-2 2v14a2 2 0 002 2z"/>
</svg>
<span>Postes SIP</span>
</span>
<span id="nav-badge-extensions" class="px-2 py-0.5 text-xs rounded-full bg-anthracite-800 text-slate-200 border border-anthracite-700">0</span>
</button>
<!-- Groupes (Menu Accordéon) : Groupes de Sonnerie & Groupes d'Extensions -->
<div class="space-y-1">
<button type="button" id="btn-menu-groups" onclick="toggleGroupsMenu()"
class="w-full flex items-center justify-between px-3.5 py-2.5 rounded-xl text-sm font-medium text-slate-300 hover:bg-anthracite-800 hover:text-white transition">
<span class="flex items-center gap-3">
<svg class="w-5 h-5 text-cyan-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z"/>
</svg>
<span>Groupes</span>
</span>
<svg id="arrow-menu-groups" class="w-4 h-4 text-slate-400 transform transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</button>
<!-- Sous-menus Groupes de sonnerie & Groupes d'extensions -->
<div id="submenu-groups" class="ml-5 pl-3 border-l border-anthracite-700 space-y-1">
<button type="button" data-tab="groups" onclick="switchTab('groups')"
class="tab-btn w-full flex items-center justify-between px-3 py-1.5 rounded-lg text-xs font-medium text-slate-300 hover:bg-anthracite-800 hover:text-white transition">
<span>• Groupes de sonnerie</span>
<span id="nav-badge-groups" class="px-1.5 py-0.2 text-[10px] rounded-full bg-anthracite-800 text-slate-200 border border-anthracite-700 font-bold">0</span>
</button>
<button type="button" data-tab="extgroups" onclick="switchTab('extgroups')"
class="tab-btn w-full flex items-center justify-between px-3 py-1.5 rounded-lg text-xs font-medium text-slate-300 hover:bg-anthracite-800 hover:text-white transition">
<span>• Groupes d'extensions</span>
<span id="nav-badge-extgroups" class="px-1.5 py-0.2 text-[10px] rounded-full bg-anthracite-800 text-cyan-300 border border-anthracite-700 font-bold">0</span>
</button>
<button type="button" data-tab="paging" onclick="switchTab('paging')"
class="tab-btn w-full flex items-center justify-between px-3 py-1.5 rounded-lg text-xs font-medium text-slate-300 hover:bg-anthracite-800 hover:text-white transition">
<span>• Téléavertissement (Paging)</span>
<span id="nav-badge-paging" class="px-1.5 py-0.2 text-[10px] rounded-full bg-anthracite-800 text-purple-300 border border-anthracite-700 font-bold">0</span>
</button>
<button type="button" data-tab="phonebook" onclick="switchTab('phonebook')"
class="tab-btn w-full flex items-center justify-between px-3 py-1.5 rounded-lg text-xs font-medium text-slate-300 hover:bg-anthracite-800 hover:text-white transition">
<span>• Annuaire d'Entreprise</span>
<span id="nav-badge-phonebook" class="px-1.5 py-0.2 text-[10px] rounded-full bg-anthracite-800 text-teal-300 border border-anthracite-700 font-bold">0</span>
</button>
</div>
</div>
<!-- Opérateurs (Menu Accordéon) & Sous-menus Trunks / Routes Sortantes -->
<div class="space-y-1">
<button type="button" id="btn-menu-operators" onclick="toggleOperatorsMenu()"
class="w-full flex items-center justify-between px-3.5 py-2.5 rounded-xl text-sm font-medium text-slate-300 hover:bg-anthracite-800 hover:text-white transition">
<span class="flex items-center gap-3">
<svg class="w-5 h-5 text-emerald-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9"/>
</svg>
<span>Opérateurs</span>
</span>
<svg id="arrow-menu-operators" class="w-4 h-4 text-slate-400 transform transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</button>
<!-- Sous-menus Trunks & Routes Sortantes -->
<div id="submenu-operators" class="ml-5 pl-3 border-l border-anthracite-700 space-y-1">
<button type="button" data-tab="trunks" onclick="switchTab('trunks')"
class="tab-btn w-full flex items-center justify-between px-3 py-1.5 rounded-lg text-xs font-medium text-slate-300 hover:bg-anthracite-800 hover:text-white transition">
<span>• Connecteurs Trunks SIP</span>
<span id="nav-badge-trunks" class="px-1.5 py-0.2 text-[10px] rounded-full bg-anthracite-800 text-slate-200 border border-anthracite-700 font-bold">0</span>
</button>
<button type="button" data-tab="outbound" onclick="switchTab('outbound')"
class="tab-btn w-full flex items-center justify-between px-3 py-1.5 rounded-lg text-xs font-medium text-slate-300 hover:bg-anthracite-800 hover:text-white transition">
<span>• Routes Sortantes</span>
<span id="nav-badge-outbound" class="px-1.5 py-0.2 text-[10px] rounded-full bg-anthracite-800 text-emerald-300 border border-anthracite-700 font-bold">0</span>
</button>
</div>
</div>
<!-- Serveurs Vocaux IVR -->
<button type="button" data-tab="ivrs" onclick="switchTab('ivrs')"
class="tab-btn w-full flex items-center justify-between px-3.5 py-2.5 rounded-xl text-sm font-medium text-slate-300 hover:bg-anthracite-800 hover:text-white transition">
<span class="flex items-center gap-3">
<svg class="w-5 h-5 text-amber-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z"/>
</svg>
<span>Serveurs Vocaux (IVR)</span>
</span>
<span id="nav-badge-ivrs" class="px-2 py-0.5 text-xs rounded-full bg-anthracite-800 text-slate-200 border border-anthracite-700">0</span>
</button>
<!-- Fichiers sons -->
<button type="button" data-tab="sounds" onclick="switchTab('sounds')"
class="tab-btn w-full flex items-center justify-between px-3.5 py-2.5 rounded-xl text-sm font-medium text-slate-300 hover:bg-anthracite-800 hover:text-white transition">
<span class="flex items-center gap-3">
<svg class="w-5 h-5 text-amber-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.536 8.464a5 5 0 010 7.072m2.828-9.9a9 9 0 010 12.728M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z"/>
</svg>
<span>Fichiers sons</span>
</span>
<span id="nav-badge-sounds" class="px-2 py-0.5 text-xs rounded-full bg-anthracite-800 text-amber-300 border border-anthracite-700 font-bold">0</span>
</button>
<!-- Codes de Fonctions -->
<button type="button" data-tab="featurecodes" onclick="switchTab('featurecodes')"
class="tab-btn w-full flex items-center justify-between px-3.5 py-2.5 rounded-xl text-sm font-medium text-slate-300 hover:bg-anthracite-800 hover:text-white transition">
<span class="flex items-center gap-3">
<svg class="w-5 h-5 text-indigo-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 20l4-16m2 16l4-16M6 9h14M4 15h14"/>
</svg>
<span>Codes de Fonctions</span>
</span>
<span id="nav-badge-featurecodes" class="px-2 py-0.5 text-xs rounded-full bg-anthracite-800 text-indigo-300 border border-anthracite-700 font-bold">*</span>
</button>
<div class="pt-3 px-3 py-1.5 text-[11px] font-bold uppercase tracking-wider text-slate-400">Administration</div>
<!-- Paramètres SIP & NAT -->
<button type="button" data-tab="sipsettings" onclick="switchTab('sipsettings')"
class="tab-btn w-full flex items-center justify-between px-3.5 py-2.5 rounded-xl text-sm font-medium text-slate-300 hover:bg-anthracite-800 hover:text-white transition">
<span class="flex items-center gap-3">
<svg class="w-5 h-5 text-cyan-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9"/>
</svg>
<span>Paramètres SIP & NAT</span>
</span>
<span id="nav-badge-sipsettings" class="px-2 py-0.5 text-xs rounded-full bg-anthracite-800 text-cyan-300 border border-anthracite-700 font-bold">NAT</span>
</button>
<!-- Certificats SSL / TLS -->
<button type="button" data-tab="certificates" onclick="switchTab('certificates')"
class="tab-btn w-full flex items-center justify-between px-3.5 py-2.5 rounded-xl text-sm font-medium text-slate-300 hover:bg-anthracite-800 hover:text-white transition">
<span class="flex items-center gap-3">
<svg class="w-5 h-5 text-emerald-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"/>
</svg>
<span>Certificats SSL / TLS</span>
</span>
<span id="nav-badge-certificates" class="px-2 py-0.5 text-xs rounded-full bg-anthracite-800 text-emerald-300 border border-anthracite-700 font-bold">1</span>
</button>
<!-- Système (Menu Accordéon) : Paramètres & Fichiers .conf / Sauvegardes & Restauration -->
<div class="space-y-1">
<button type="button" id="btn-menu-system" onclick="toggleSystemMenu()"
class="w-full flex items-center justify-between px-3.5 py-2.5 rounded-xl text-sm font-medium text-slate-300 hover:bg-anthracite-800 hover:text-white transition">
<span class="flex items-center gap-3">
<svg class="w-5 h-5 text-purple-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
</svg>
<span>Système</span>
</span>
<svg id="arrow-menu-system" class="w-4 h-4 text-slate-400 transform transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</button>
<!-- Sous-menus Système -->
<div id="submenu-system" class="ml-5 pl-3 border-l border-anthracite-700 space-y-1">
<button type="button" data-tab="system" onclick="switchTab('system')"
class="tab-btn w-full flex items-center justify-between px-3 py-1.5 rounded-lg text-xs font-medium text-slate-300 hover:bg-anthracite-800 hover:text-white transition text-left">
<span class="truncate">• Paramètres & Fichiers .conf</span>
</button>
<button type="button" data-tab="backups" onclick="switchTab('backups')"
class="tab-btn w-full flex items-center justify-between px-3 py-1.5 rounded-lg text-xs font-medium text-slate-300 hover:bg-anthracite-800 hover:text-white transition text-left">
<span class="truncate">• Sauvegardes & Restauration</span>
<span id="nav-badge-backups" class="ml-2 shrink-0 px-1.5 py-0.2 text-[10px] rounded-full bg-anthracite-800 text-purple-300 border border-anthracite-700 font-bold">0</span>
</button>
<button type="button" data-tab="firewall" onclick="switchTab('firewall')"
class="tab-btn w-full flex items-center justify-between px-3 py-1.5 rounded-lg text-xs font-medium text-slate-300 hover:bg-anthracite-800 hover:text-white transition text-left">
<span class="truncate">• Pare-feu & Fail2ban</span>
<span id="nav-badge-firewall" class="ml-2 shrink-0 px-1.5 py-0.2 text-[10px] rounded-full bg-anthracite-800 text-emerald-300 border border-anthracite-700 font-bold">0</span>
</button>
<button type="button" data-tab="cluster" onclick="switchTab('cluster')"
class="tab-btn w-full flex items-center justify-between px-3 py-1.5 rounded-lg text-xs font-medium text-slate-300 hover:bg-anthracite-800 hover:text-white transition text-left">
<span class="truncate">• Haute Disponibilité (HA)</span>
<span id="nav-badge-cluster" class="ml-2 shrink-0 px-1.5 py-0.2 text-[10px] rounded-full bg-anthracite-800 text-cyan-300 border border-anthracite-700 font-bold">HA</span>
</button>
</div>
</div>
</div>
</div>
<!-- Pied de Sidebar : Infos Serveur Debian & Asterisk PJSIP -->
<div class="p-3.5 m-3.5 rounded-xl bg-anthracite-800 border border-anthracite-700 space-y-2 text-xs">
<div class="flex items-center justify-between pb-1.5 border-b border-anthracite-700/80">
<span id="sidebar-os-version" class="text-slate-200 font-bold">Debian 13</span>
<span id="sidebar-health-badge" class="px-2 py-0.5 rounded-full text-[10px] font-bold bg-emerald-500/20 text-emerald-300 border border-emerald-500/30">EN LIGNE</span>
</div>
<div class="space-y-1 text-[11px] text-slate-300">
<div class="flex justify-between items-center">
<span class="text-slate-400">Moteur :</span>
<span id="sidebar-ast-version" class="font-mono text-emerald-400 font-semibold">Asterisk 22 • PJSIP</span>
</div>
<div class="flex justify-between items-center">
<span class="text-slate-400">IP SIP :</span>
<code id="sidebar-ip" class="font-mono text-indigo-300 font-semibold">192.168.1.100</code>
</div>
<div class="flex justify-between items-center">
<span class="text-slate-400">Uptime OS :</span>
<span id="sidebar-uptime" class="font-mono text-slate-200">—</span>
</div>
<div class="flex justify-between items-center">
<span class="text-slate-400">Postes en ligne :</span>
<span id="sidebar-online-phones" class="font-mono text-emerald-400 font-bold">0</span>
</div>
</div>
</div>
</aside>
<!-- ZONE CENTRALE PLEINE LARGEUR -->
<div class="flex-1 flex flex-col min-w-0 w-full">
<!-- BARRE SUPÉRIEURE -->
<header class="sticky top-0 z-20 h-16 bg-anthracite-800/95 backdrop-blur border-b border-anthracite-700 px-4 sm:px-6 lg:px-8 flex items-center justify-between gap-4">
<div class="flex items-center gap-3">
<!-- Bouton Hamburger Mobile -->
<button type="button" onclick="toggleMobileSidebar(true)" class="lg:hidden p-2 rounded-lg bg-anthracite-750 text-slate-200 hover:text-white border border-anthracite-700">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
</svg>
</button>
<div>
<h2 id="page-header-title" class="text-base sm:text-lg font-bold text-white tracking-tight">Tableau de Bord & Supervision Temps Réel</h2>
<p id="last-applied-label" class="text-[11px] text-slate-300 hidden sm:block">Chargement de l'état système...</p>
</div>
</div>
<!-- Boutons d'action globaux -->
<div class="flex items-center gap-2.5">
<?php if ($isDevEnv): ?>
<div class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-xl text-xs font-bold bg-amber-500/15 text-amber-300 border border-amber-500/30">
<span class="w-2 h-2 rounded-full bg-amber-400 animate-pulse"></span>
<span class="hidden md:inline font-mono">SERVEUR</span> DEV
</div>
<?php endif; ?>
<!-- Badge Notification Mise à Jour Disponible -->
<button id="header-update-badge" type="button" onclick="openUpdatePanel()" style="display:none;"
class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-xl text-xs font-bold bg-amber-500/20 hover:bg-amber-500/30 text-amber-300 border border-amber-500/40 shadow-sm transition animate-pulse" title="Une mise à jour NexusPBX est disponible ! Cliquez pour voir les détails.">
<span>🚀</span>
<span id="header-update-text">Mise à jour dispo</span>
</button>
<button id="btn-test-ami" type="button"
class="inline-flex items-center gap-2 px-3.5 py-2 rounded-xl text-xs font-semibold bg-anthracite-750 hover:bg-anthracite-700 text-slate-100 border border-anthracite-600 transition">
<span id="ami-dot" class="w-2 h-2 rounded-full bg-emerald-400"></span>
<span class="hidden sm:inline">Tester AMI</span>
</button>
<button id="btn-apply-config" type="button"
class="inline-flex items-center gap-2 px-4 py-2 rounded-xl text-xs sm:text-sm font-semibold bg-emerald-600 hover:bg-emerald-500 active:bg-emerald-700 text-white shadow-lg shadow-emerald-600/25 transition">
<svg id="apply-icon" class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
</svg>
<span>Appliquer les configurations</span>
</button>
<button id="btn-logout" type="button" title="Se déconnecter (<?= htmlspecialchars($currentUser) ?>)"
class="p-2 rounded-xl bg-anthracite-750 hover:bg-red-500/20 hover:text-red-300 text-slate-300 border border-anthracite-600 transition">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/>
</svg>
</button>
</div>
</header>
<!-- CONTENU PRINCIPAL FLUIDE (100% LARGEUR DISPONIBLE) -->
<main class="flex-1 w-full p-4 sm:p-6 lg:p-8 space-y-6 overflow-x-hidden">
<!-- ========================================================= -->
<!-- ONGLET 0 : ACCUEIL & DASHBOARD SUPERVISION TEMPS RÉEL -->
<!-- ========================================================= -->
<section id="tab-dashboard" class="tab-panel space-y-6">
<!-- LIGNE 1 : 4 CARTES MÉTRIQUES SYSTÈME (CPU, RAM, RÉSEAU, HDD) -->
<div class="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-5">
<!-- Carte CPU -->
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl p-5 shadow-lg flex flex-col justify-between">
<div>
<div class="flex items-center justify-between">
<span class="text-xs font-bold uppercase tracking-wider text-slate-300">Processeur (CPU)</span>
<span class="p-2 rounded-lg bg-indigo-500/15 text-indigo-300 border border-indigo-500/30">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z"/>
</svg>
</span>
</div>
<div class="mt-3 flex items-baseline justify-between">
<div id="metric-cpu-pct" class="text-3xl font-extrabold text-white font-mono">0.0%</div>
<div id="metric-cpu-cores" class="text-xs text-indigo-300 font-medium">— cœurs</div>
</div>
<div id="metric-cpu-model" class="text-[11px] text-slate-400 truncate mt-1">Debian Linux</div>
</div>
<div class="mt-4">
<div class="w-full h-2.5 bg-anthracite-950 rounded-full overflow-hidden border border-anthracite-700">
<div id="bar-cpu" class="h-full bg-gradient-to-r from-indigo-500 to-cyan-400 transition-all duration-500" style="width: 5%"></div>
</div>
<div class="flex justify-between text-[11px] text-slate-300 mt-2 font-mono">
<span>Load Average :</span>
<span id="metric-cpu-load">0.00 / 0.00 / 0.00</span>
</div>
</div>
</div>
<!-- Carte Mémoire RAM -->
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl p-5 shadow-lg flex flex-col justify-between">
<div>
<div class="flex items-center justify-between">
<span class="text-xs font-bold uppercase tracking-wider text-slate-300">Mémoire Vive (RAM)</span>
<span class="p-2 rounded-lg bg-emerald-500/15 text-emerald-300 border border-emerald-500/30">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4"/>
</svg>
</span>
</div>
<div class="mt-3 flex items-baseline justify-between">
<div id="metric-ram-pct" class="text-3xl font-extrabold text-white font-mono">0.0%</div>
<div id="metric-ram-used" class="text-xs text-emerald-300 font-mono">0 / 0 Mo</div>
</div>
<div id="metric-ram-free" class="text-[11px] text-slate-400 mt-1">Disponible : — Mo</div>
</div>
<div class="mt-4">
<div class="w-full h-2.5 bg-anthracite-950 rounded-full overflow-hidden border border-anthracite-700">
<div id="bar-ram" class="h-full bg-gradient-to-r from-emerald-500 to-teal-400 transition-all duration-500" style="width: 10%"></div>
</div>
<div class="flex justify-between text-[11px] text-slate-300 mt-2 font-mono">
<span>Swap Debian :</span>
<span id="metric-swap">0 / 0 Mo</span>
</div>
</div>
</div>
<!-- Carte Réseau -->
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl p-5 shadow-lg flex flex-col justify-between">
<div>
<div class="flex items-center justify-between">
<span class="text-xs font-bold uppercase tracking-wider text-slate-300">Réseau SIP & RTP</span>
<span class="p-2 rounded-lg bg-cyan-500/15 text-cyan-300 border border-cyan-500/30">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
</svg>
</span>
</div>
<div class="mt-3 grid grid-cols-2 gap-2">
<div class="p-2 rounded-xl bg-anthracite-950 border border-anthracite-700">
<div class="text-[10px] text-cyan-300 uppercase font-bold">↓ RX (Entrant)</div>
<div id="metric-net-rx" class="text-lg font-extrabold text-white font-mono mt-0.5">0.0 Ko/s</div>
</div>
<div class="p-2 rounded-xl bg-anthracite-950 border border-anthracite-700">
<div class="text-[10px] text-indigo-300 uppercase font-bold">↑ TX (Sortant)</div>
<div id="metric-net-tx" class="text-lg font-extrabold text-white font-mono mt-0.5">0.0 Ko/s</div>
</div>
</div>
</div>
<div class="mt-3 pt-2 border-t border-anthracite-700/80 flex justify-between text-[11px] text-slate-300 font-mono">
<span id="metric-net-iface">ens18</span>
<span id="metric-net-totals">Total : ↓ 0 Mo • ↑ 0 Mo</span>
</div>
</div>
<!-- Carte Disque HDD / SSD -->
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl p-5 shadow-lg flex flex-col justify-between">
<div>
<div class="flex items-center justify-between">
<span class="text-xs font-bold uppercase tracking-wider text-slate-300">Stockage Disque (HDD)</span>
<span class="p-2 rounded-lg bg-amber-500/15 text-amber-300 border border-amber-500/30">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4"/>
</svg>
</span>
</div>
<div class="mt-3 flex items-baseline justify-between">
<div id="metric-disk-pct" class="text-3xl font-extrabold text-white font-mono">0.0%</div>
<div id="metric-disk-used" class="text-xs text-amber-300 font-mono">0 / 0 Go</div>
</div>
<div id="metric-disk-free" class="text-[11px] text-slate-400 mt-1">Espace libre sur / : — Go</div>
</div>
<div class="mt-4">
<div class="w-full h-2.5 bg-anthracite-950 rounded-full overflow-hidden border border-anthracite-700">
<div id="bar-disk" class="h-full bg-gradient-to-r from-amber-500 to-orange-400 transition-all duration-500" style="width: 15%"></div>
</div>
<div class="flex justify-between text-[11px] text-slate-300 mt-2 font-mono">
<span>Base SQLite :</span>
<span>/var/www/nexus-pbx</span>
</div>
</div>
</div>
</div>
<!-- LIGNE 2 : SUPERVISION SÉCURITÉ, PARE-FEU, FAIL2BAN & STATUT DES SERVICES -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Carte 1 : État Pare-feu & Fail2ban -->
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl p-5 shadow-lg flex flex-col justify-between">
<div>
<div class="flex items-center justify-between pb-3 border-b border-anthracite-700/80">
<div class="flex items-center gap-2.5">
<span class="p-2 rounded-xl bg-emerald-500/15 text-emerald-400 border border-emerald-500/30">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"/>
</svg>
</span>
<div>
<h3 class="text-sm font-bold text-white">Sécurité & Protection Active</h3>
<p class="text-[11px] text-slate-300">Pare-feu Nginx & Moteur Fail2ban</p>
</div>
</div>
<button type="button" onclick="switchTab('firewall')"
class="px-2.5 py-1 rounded-lg text-[11px] font-semibold bg-anthracite-950 hover:bg-anthracite-700 text-indigo-300 border border-anthracite-700 transition flex items-center gap-1">
Configurer
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
</button>
</div>
<div class="grid grid-cols-2 gap-3 mt-4">
<!-- Statut Pare-feu -->
<div class="p-3 rounded-xl bg-anthracite-950 border border-anthracite-700">
<div class="flex items-center justify-between">
<span class="text-[10px] text-slate-400 font-bold uppercase">Filtrage & Pare-feu</span>
<span id="dash-fw-dot" class="w-2 h-2 rounded-full bg-emerald-400 shadow-sm shadow-emerald-500/50"></span>
</div>
<div id="dash-fw-status" class="text-sm font-extrabold text-emerald-400 mt-1">Actif</div>
<div id="dash-fw-detail" class="text-[10px] text-slate-400 truncate mt-0.5">Port HTTPS : 8443</div>
</div>
<!-- Statut Fail2ban -->
<div class="p-3 rounded-xl bg-anthracite-950 border border-anthracite-700">
<div class="flex items-center justify-between">
<span class="text-[10px] text-slate-400 font-bold uppercase">Fail2ban IDS</span>
<span id="dash-f2b-dot" class="w-2 h-2 rounded-full bg-emerald-400 animate-pulse"></span>
</div>
<div id="dash-f2b-status" class="text-sm font-extrabold text-emerald-400 mt-1">Actif</div>
<div id="dash-f2b-detail" class="text-[10px] text-slate-400 truncate mt-0.5">3 prisons actives</div>
</div>
</div>
<!-- Nombre d'IPs bloquées -->
<div class="mt-3 p-3.5 rounded-xl bg-gradient-to-r from-anthracite-950 via-anthracite-900 to-anthracite-950 border border-anthracite-700 flex items-center justify-between">
<div>
<span class="text-xs text-slate-300 font-medium">Adresses IP bannies</span>
<div class="flex items-baseline gap-2 mt-0.5">
<span id="dash-banned-count" class="text-2xl font-black text-rose-400 font-mono">0</span>
<span class="text-[11px] text-slate-400">actuellement</span>
</div>
</div>
<div class="text-right">
<button type="button" onclick="openSecurityEventsModal()" id="dash-total-blocked-badge" title="Cliquer pour voir le détail des événements de sécurité"
class="cursor-pointer hover:scale-105 transition transform px-2.5 py-1 rounded-full text-[11px] font-bold bg-amber-500/15 hover:bg-amber-500/25 text-amber-300 border border-amber-500/30 font-mono flex items-center gap-1.5 ml-auto">
0 bloquées au total
</button>
<div id="dash-latest-banned" class="text-[10px] text-slate-400 mt-1 truncate max-w-[140px]">Aucun ban actif</div>
</div>
</div>
</div>
<div class="mt-4 pt-3 border-t border-anthracite-700/80 flex items-center justify-between text-xs text-slate-300">
<span class="flex items-center gap-1.5">
<i class="w-2 h-2 rounded-full bg-emerald-400"></i>
Protection intrusion
</span>
<span id="dash-client-ip" class="font-mono text-[11px] text-cyan-300">IP client : 127.0.0.1</span>
</div>
</div>
<!-- Carte 2 : Graphique Visuel des Menaces & Jails Fail2ban -->
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl p-5 shadow-lg flex flex-col justify-between">
<div>
<div class="flex items-center justify-between pb-3 border-b border-anthracite-700/80">
<div>
<h3 class="text-sm font-bold text-white">Répartition des Menaces (Jails)</h3>
<p class="text-[11px] text-slate-300">Tentatives d'intrusion interceptées par service</p>
</div>
<span class="px-2 py-0.5 rounded-full text-[10px] font-bold bg-indigo-500/20 text-indigo-300 border border-indigo-500/30">IDS / IPS</span>
</div>
<!-- Graphe / Barres de progression stylisées des Jails -->
<div class="space-y-3 mt-4">
<!-- Jail Asterisk SIP -->
<div>
<div class="flex items-center justify-between text-xs mb-1">
<span class="flex items-center gap-2 font-medium text-slate-200">
<i class="w-2.5 h-2.5 rounded-full bg-indigo-400"></i>
Téléphonie SIP (PJSIP)
</span>
<span id="dash-jail-ast-stats" class="text-[11px] font-mono text-indigo-300 font-bold">0 échecs • 0 ban</span>
</div>
<div class="w-full h-2 bg-anthracite-950 rounded-full overflow-hidden border border-anthracite-700">
<div id="dash-jail-ast-bar" class="h-full bg-gradient-to-r from-indigo-500 to-cyan-400 transition-all duration-500" style="width: 5%"></div>
</div>
</div>
<!-- Jail Nexus Web -->
<div>
<div class="flex items-center justify-between text-xs mb-1">
<span class="flex items-center gap-2 font-medium text-slate-200">
<i class="w-2.5 h-2.5 rounded-full bg-emerald-400"></i>
Portail Web & API Admin
</span>
<span id="dash-jail-web-stats" class="text-[11px] font-mono text-emerald-300 font-bold">0 échecs • 0 ban</span>
</div>
<div class="w-full h-2 bg-anthracite-950 rounded-full overflow-hidden border border-anthracite-700">
<div id="dash-jail-web-bar" class="h-full bg-gradient-to-r from-emerald-500 to-teal-400 transition-all duration-500" style="width: 5%"></div>
</div>
</div>
<!-- Jail SSHD -->
<div>
<div class="flex items-center justify-between text-xs mb-1">
<span class="flex items-center gap-2 font-medium text-slate-200">
<i class="w-2.5 h-2.5 rounded-full bg-amber-400"></i>
Console Système SSH
</span>
<span id="dash-jail-ssh-stats" class="text-[11px] font-mono text-amber-300 font-bold">0 échecs • 0 ban</span>
</div>
<div class="w-full h-2 bg-anthracite-950 rounded-full overflow-hidden border border-anthracite-700">
<div id="dash-jail-ssh-bar" class="h-full bg-gradient-to-r from-amber-500 to-orange-400 transition-all duration-500" style="width: 5%"></div>
</div>
</div>
</div>
</div>
<div class="mt-4 pt-3 border-t border-anthracite-700/80 flex items-center justify-between text-[11px] text-slate-300">
<span class="font-mono text-slate-400 truncate">/var/log/asterisk/security.log</span>
<button type="button" onclick="openSecurityEventsModal()" class="text-xs font-semibold text-indigo-400 hover:text-indigo-300 transition flex items-center gap-1">
<span>Voir les détails des attaques</span>
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
</button>
</div>
</div>
<!-- Carte 3 : Statut des Services Système Debian -->
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl p-5 shadow-lg flex flex-col justify-between">
<div>
<div class="flex items-center justify-between pb-3 border-b border-anthracite-700/80">
<div>
<h3 class="text-sm font-bold text-white">Statut des Services Système</h3>
<p class="text-[11px] text-slate-300">Surveillance temps réel des démons Debian</p>
</div>
<span id="dash-services-all-badge" class="px-2 py-0.5 rounded-full text-[10px] font-bold bg-emerald-500/20 text-emerald-300 border border-emerald-500/30">TOUS OPÉRATIONNELS</span>
</div>
<div id="dash-services-list" class="divide-y divide-anthracite-700/60 mt-2">
<!-- Rendu dynamique JS des 5 services -->
</div>
</div>
<div class="mt-3 pt-2.5 border-t border-anthracite-700/80 flex items-center justify-between text-xs text-slate-400">
<span>Moteur PBX : <b class="text-slate-200">Asterisk 22</b></span>
<span>Distro : <b class="text-slate-200">Debian Linux</b></span>
</div>
</div>
</div>
<!-- LIGNE 3 : GRAPHES TEMPS RÉEL (CPU/RAM + TRAFIC RÉSEAU + JAUGE STOCKAGE/PBX) -->
<div class="grid grid-cols-1 xl:grid-cols-3 gap-6">
<!-- Graphe 1 : Historique Temps Réel CPU & Mémoire (%) -->
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl p-5 shadow-lg flex flex-col justify-between">
<div class="flex items-center justify-between mb-3">
<div>
<h3 class="text-sm font-bold text-white">Historique Charge CPU & RAM (%)</h3>
<p class="text-[11px] text-slate-300">Actualisation automatique toutes les 3 secondes</p>
</div>
<div class="flex items-center gap-3 text-xs font-medium">
<span class="flex items-center gap-1.5 text-indigo-300"><i class="w-2.5 h-2.5 rounded-full bg-indigo-400 inline-block"></i>CPU</span>
<span class="flex items-center gap-1.5 text-emerald-300"><i class="w-2.5 h-2.5 rounded-full bg-emerald-400 inline-block"></i>RAM</span>
</div>
</div>
<div class="relative h-52 w-full bg-anthracite-950 rounded-xl border border-anthracite-700 p-2 overflow-hidden">
<svg id="chart-cpu-ram" viewBox="0 0 500 180" preserveAspectRatio="none" class="w-full h-full"></svg>
</div>
</div>
<!-- Graphe 2 : Historique Temps Réel Trafic Réseau RX / TX (Ko/s) -->
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl p-5 shadow-lg flex flex-col justify-between">
<div class="flex items-center justify-between mb-3">
<div>
<h3 class="text-sm font-bold text-white">Débit Réseau Temps Réel (Ko/s)</h3>
<p class="text-[11px] text-slate-300">Flux SIP (5060/5061) & Flux Audio RTP</p>
</div>
<div class="flex items-center gap-3 text-xs font-medium">
<span class="flex items-center gap-1.5 text-cyan-300"><i class="w-2.5 h-2.5 rounded-full bg-cyan-400 inline-block"></i>RX</span>
<span class="flex items-center gap-1.5 text-purple-300"><i class="w-2.5 h-2.5 rounded-full bg-purple-400 inline-block"></i>TX</span>
</div>
</div>
<div class="relative h-52 w-full bg-anthracite-950 rounded-xl border border-anthracite-700 p-2 overflow-hidden">
<svg id="chart-network" viewBox="0 0 500 180" preserveAspectRatio="none" class="w-full h-full"></svg>
</div>
</div>
<!-- Graphe 3 : Jauges Anneaux (RAM / Disque / Postes SIP Connectés) -->
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl p-5 shadow-lg flex flex-col justify-between">
<div>
<h3 class="text-sm font-bold text-white">Synthèse Ressources & Téléphonie</h3>
<p class="text-[11px] text-slate-300">État global du serveur Debian & d'Asterisk 22</p>
</div>
<div class="grid grid-cols-3 gap-3 my-4">
<!-- Donut CPU -->
<div class="flex flex-col items-center p-3 rounded-xl bg-anthracite-950 border border-anthracite-700">
<svg class="w-20 h-20 -rotate-90" viewBox="0 0 36 36">
<circle cx="18" cy="18" r="15.5" fill="none" stroke="#2c323f" stroke-width="3.5"></circle>
<circle id="donut-cpu" cx="18" cy="18" r="15.5" fill="none" stroke="#818cf8" stroke-width="3.5" stroke-dasharray="0, 100" stroke-linecap="round"></circle>
</svg>
<span id="donut-cpu-label" class="text-xs font-bold font-mono text-white mt-1.5">0%</span>
<span class="text-[10px] text-slate-400 uppercase">Charge CPU</span>
</div>
<!-- Donut RAM -->
<div class="flex flex-col items-center p-3 rounded-xl bg-anthracite-950 border border-anthracite-700">
<svg class="w-20 h-20 -rotate-90" viewBox="0 0 36 36">
<circle cx="18" cy="18" r="15.5" fill="none" stroke="#2c323f" stroke-width="3.5"></circle>
<circle id="donut-ram" cx="18" cy="18" r="15.5" fill="none" stroke="#34d399" stroke-width="3.5" stroke-dasharray="0, 100" stroke-linecap="round"></circle>
</svg>
<span id="donut-ram-label" class="text-xs font-bold font-mono text-white mt-1.5">0%</span>
<span class="text-[10px] text-slate-400 uppercase">Mémoire RAM</span>
</div>
<!-- Donut HDD -->
<div class="flex flex-col items-center p-3 rounded-xl bg-anthracite-950 border border-anthracite-700">
<svg class="w-20 h-20 -rotate-90" viewBox="0 0 36 36">
<circle cx="18" cy="18" r="15.5" fill="none" stroke="#2c323f" stroke-width="3.5"></circle>
<circle id="donut-disk" cx="18" cy="18" r="15.5" fill="none" stroke="#fbbf24" stroke-width="3.5" stroke-dasharray="0, 100" stroke-linecap="round"></circle>
</svg>
<span id="donut-disk-label" class="text-xs font-bold font-mono text-white mt-1.5">0%</span>
<span class="text-[10px] text-slate-400 uppercase">Disque HDD</span>
</div>
</div>
<div class="grid grid-cols-2 gap-3 text-xs">
<div class="px-3.5 py-2.5 rounded-xl bg-anthracite-950 border border-anthracite-700 flex items-center justify-between">
<span class="text-slate-300">Appels en cours :</span>
<span id="dash-active-calls" class="font-mono font-bold text-emerald-400 text-sm">0</span>
</div>
<div class="px-3.5 py-2.5 rounded-xl bg-anthracite-950 border border-anthracite-700 flex items-center justify-between">
<span class="text-slate-300">Postes enregistrés :</span>
<span id="dash-online-contacts" class="font-mono font-bold text-indigo-300 text-sm">0</span>
</div>
</div>
</div>
</div>
<!-- LIGNE 3 : ÉTAT DES TÉLÉPHONES SIP EN DIRECT & RACCOURCIS PBX -->
<div class="grid grid-cols-1 xl:grid-cols-3 gap-6">
<!-- Table des Postes SIP et état d'enregistrement PJSIP en direct -->
<div class="xl:col-span-2 bg-anthracite-800 border border-anthracite-700 rounded-2xl overflow-hidden shadow-lg">
<div class="px-5 py-4 border-b border-anthracite-700 flex flex-wrap items-center justify-between gap-2">
<div>
<h3 class="text-sm font-bold text-white">État des Téléphones SIP en Temps Réel (PJSIP Contacts)</h3>
<p class="text-xs text-slate-300">Visualisez instantanément quels téléphones sont enregistrés sur Asterisk et leur latence RTT.</p>
</div>
<button type="button" onclick="openExtensionModal()"
class="px-3.5 py-1.5 rounded-xl text-xs font-semibold bg-indigo-600 hover:bg-indigo-500 text-white transition">
+ Ajouter un poste
</button>
</div>
<div class="overflow-x-auto">
<table class="w-full text-left border-collapse">
<thead>
<tr class="border-b border-anthracite-700 text-[11px] font-semibold uppercase tracking-wider text-slate-300 bg-anthracite-950/60">
<th class="py-3 px-4">Poste</th>
<th class="py-3 px-4">Nom</th>
<th class="py-3 px-4">Transport & Sécurité (Réel)</th>
<th class="py-3 px-4">Statut SIP Temps Réel</th>
<th class="py-3 px-4">IP & Équipement Détecté</th>
<th class="py-3 px-4 text-right">Latence RTT</th>
</tr>
</thead>
<tbody id="dashboard-phones-tbody" class="divide-y divide-anthracite-700/60 text-sm"></tbody>
</table>
</div>
</div>
<!-- Résumé Architecture PBX & Codes de test -->
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl p-5 shadow-lg flex flex-col justify-between space-y-4">
<div>
<h3 class="text-sm font-bold text-white mb-1">Architecture Téléphonique Active</h3>
<p class="text-xs text-slate-300 mb-4">Vue rapide des objets configurés dans SQLite.</p>
<div class="grid grid-cols-2 gap-3">
<div onclick="switchTab('extensions')" class="cursor-pointer p-3.5 rounded-xl bg-anthracite-950 border border-anthracite-700 hover:border-indigo-400 transition">
<div class="text-xs text-slate-300">Postes SIP</div>
<div id="stat-extensions" class="text-2xl font-extrabold text-white mt-0.5">0</div>
<div id="stat-extensions-sub" class="text-[11px] text-indigo-300 mt-0.5">0 UDP • 0 TLS</div>
</div>
<div onclick="switchTab('groups')" class="cursor-pointer p-3.5 rounded-xl bg-anthracite-950 border border-anthracite-700 hover:border-cyan-400 transition">
<div class="text-xs text-slate-300">Groupes d'appel</div>
<div id="stat-groups" class="text-2xl font-extrabold text-white mt-0.5">0</div>
<div class="text-[11px] text-cyan-300 mt-0.5">Ring Groups</div>
</div>
<div onclick="switchTab('trunks')" class="cursor-pointer p-3.5 rounded-xl bg-anthracite-950 border border-anthracite-700 hover:border-emerald-400 transition">
<div class="text-xs text-slate-300">Trunks SIP</div>
<div id="stat-trunks" class="text-2xl font-extrabold text-white mt-0.5">0</div>
<div class="text-[11px] text-emerald-300 mt-0.5">Opérateurs VoIP</div>
</div>
<div onclick="switchTab('ivrs')" class="cursor-pointer p-3.5 rounded-xl bg-anthracite-950 border border-anthracite-700 hover:border-amber-400 transition">
<div class="text-xs text-slate-300">Menus SVI / IVR</div>
<div id="stat-ivrs" class="text-2xl font-extrabold text-white mt-0.5">0</div>
<div class="text-[11px] text-amber-300 mt-0.5">Serveurs vocaux</div>
</div>
</div>
</div>
<div class="p-4 rounded-xl bg-anthracite-950 border border-anthracite-700 text-xs space-y-2">
<div class="font-bold text-white flex items-center justify-between">
<span>Codes de test audio intégrés</span>
<span id="stat-health" class="text-[11px] text-emerald-400 font-semibold">OK</span>
</div>
<div class="flex justify-between text-slate-300">
<span>Test d'écho interactif :</span>
<code class="text-emerald-300 font-mono font-bold">*43</code>
</div>
<div class="flex justify-between text-slate-300">
<span>Horloge parlante :</span>
<code class="text-emerald-300 font-mono font-bold">*60</code>
</div>
<div class="flex justify-between text-slate-300">
<span>Tester le SVI d'accueil :</span>
<code class="text-emerald-300 font-mono font-bold">*801</code>
</div>
</div>
</div>
</div>
</section>
<!-- ========================================================= -->
<!-- ONGLET : JOURNAL D'APPELS (CDR) -->
<!-- ========================================================= -->
<section id="tab-cdr" class="tab-panel hidden space-y-4">
<!-- En-tête avec Actions -->
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
<div>
<div class="flex items-center gap-2">
<h2 class="text-lg font-bold text-white">Journal d'Appels & Historique CDR</h2>
<span class="px-2 py-0.5 rounded-full text-[10px] font-bold bg-sky-500/20 text-sky-300 border border-sky-500/30">Asterisk CDR</span>
</div>
<p class="text-xs text-slate-300">Traçabilité complète des flux téléphoniques entrants, sortants et internes avec identification automatique des correspondants et des Trunks SIP.</p>
</div>
<div class="flex items-center flex-wrap gap-2">
<button type="button" onclick="loadCdr()" id="btn-refresh-cdr"
class="inline-flex items-center gap-1.5 px-3 py-2 rounded-xl text-xs sm:text-sm font-medium bg-anthracite-800 hover:bg-anthracite-750 text-slate-200 border border-anthracite-700 transition" title="Recharger les derniers appels">
<svg id="spinner-refresh-cdr" class="w-4 h-4 text-sky-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
<span>Actualiser</span>
</button>
<button type="button" onclick="exportCdrCsv()"
class="inline-flex items-center gap-1.5 px-3 py-2 rounded-xl text-xs sm:text-sm font-medium bg-emerald-600/20 hover:bg-emerald-600/30 text-emerald-300 border border-emerald-500/40 shadow-sm transition" title="Exporter au format CSV compatible Excel">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
<span>Exporter CSV</span>
</button>
<button type="button" onclick="confirmClearCdr()"
class="inline-flex items-center gap-1.5 px-3 py-2 rounded-xl text-xs sm:text-sm font-medium bg-rose-600/10 hover:bg-rose-600/20 text-rose-300 border border-rose-500/30 transition" title="Purger le journal d'appels">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>
<span>Purger</span>
</button>
</div>
</div>
<!-- Cartes KPIs Statistiques -->
<div class="grid grid-cols-2 lg:grid-cols-5 gap-3">
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl p-4 shadow-lg">
<div class="text-[11px] font-semibold uppercase text-slate-400">Total Appels</div>
<div id="cdr-stat-total" class="text-2xl font-bold text-white mt-1">0</div>
<div class="text-[11px] text-slate-400 mt-0.5">Sur la période</div>
</div>
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl p-4 shadow-lg">
<div class="text-[11px] font-semibold uppercase text-emerald-400">Décrochés</div>
<div id="cdr-stat-answered" class="text-2xl font-bold text-emerald-400 mt-1">0</div>
<div id="cdr-stat-rate" class="text-[11px] text-emerald-300 font-semibold mt-0.5">0% de réponse</div>
</div>
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl p-4 shadow-lg">
<div class="text-[11px] font-semibold uppercase text-amber-400">Non Répondus / Manqués</div>
<div id="cdr-stat-missed" class="text-2xl font-bold text-amber-400 mt-1">0</div>
<div class="text-[11px] text-slate-400 mt-0.5">Sans réponse / échecs</div>
</div>
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl p-4 shadow-lg">
<div class="text-[11px] font-semibold uppercase text-cyan-400">Appels Sortants</div>
<div id="cdr-stat-outbound" class="text-2xl font-bold text-cyan-400 mt-1">0</div>
<div class="text-[11px] text-slate-400 mt-0.5">Vers opérateurs / Trunks</div>
</div>
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl p-4 shadow-lg">
<div class="text-[11px] font-semibold uppercase text-teal-400">Appels Entrants</div>
<div id="cdr-stat-inbound" class="text-2xl font-bold text-teal-400 mt-1">0</div>
<div class="text-[11px] text-slate-400 mt-0.5">Reçus via SDA / Trunks</div>
</div>
</div>
<!-- Barre de Recherche & Filtres Multicritères -->
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl p-4 shadow-lg space-y-3">
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-3">
<!-- Recherche textuelle -->
<div class="relative lg:col-span-2">
<span class="absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none text-slate-400">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
</span>
<input type="text" id="cdr-filter-search" oninput="debounceCdrLoad()" placeholder="Rechercher par appelant, destinataire, trunk..."
class="w-full pl-10 pr-4 py-2 rounded-xl bg-anthracite-950 border border-anthracite-700 text-xs sm:text-sm text-white placeholder-slate-500 focus:outline-none focus:border-sky-500 transition">
</div>
<!-- Filtre Période -->
<div>
<select id="cdr-filter-period" onchange="loadCdr()"
class="w-full px-3 py-2 rounded-xl bg-anthracite-950 border border-anthracite-700 text-xs text-white focus:outline-none focus:border-sky-500 transition">
<option value="all">Toute la période</option>
<option value="today" selected>Aujourd'hui</option>
<option value="yesterday">Hier</option>
<option value="week">7 derniers jours</option>
<option value="month">30 derniers jours</option>
</select>
</div>
<!-- Filtre Direction -->
<div>
<select id="cdr-filter-direction" onchange="loadCdr()"
class="w-full px-3 py-2 rounded-xl bg-anthracite-950 border border-anthracite-700 text-xs text-white focus:outline-none focus:border-sky-500 transition">
<option value="all">Toutes les directions</option>
<option value="inbound">Entrants (Trunk → Poste)</option>
<option value="outbound">Sortants (Poste → Trunk)</option>
<option value="internal">Internes (Poste à Poste)</option>
<option value="service">Services (*98, SVI, etc.)</option>
</select>
</div>
<!-- Filtre Statut / Disposition -->
<div>
<select id="cdr-filter-disposition" onchange="loadCdr()"
class="w-full px-3 py-2 rounded-xl bg-anthracite-950 border border-anthracite-700 text-xs text-white focus:outline-none focus:border-sky-500 transition">
<option value="all">Tous les statuts</option>
<option value="answered">Décrochés (Répondus)</option>
<option value="no_answer">Non répondus (Manqués)</option>
<option value="busy">Occupés</option>
<option value="failed">Échecs</option>
</select>
</div>
</div>
<!-- Ligne d'info & Limite d'affichage -->
<div class="flex items-center justify-between pt-2 border-t border-anthracite-700/60 text-xs text-slate-400">
<div id="cdr-status-info">Chargement de l'historique...</div>
<div class="flex items-center gap-2">
<span>Afficher :</span>
<select id="cdr-filter-limit" onchange="loadCdr()"
class="px-2 py-1 rounded-lg bg-anthracite-950 border border-anthracite-700 text-xs text-white focus:outline-none">
<option value="25">25</option>
<option value="50">50</option>
<option value="100" selected>100</option>
<option value="250">250</option>
<option value="500">500</option>
</select>
</div>
</div>
</div>
<!-- Tableau du Journal d'Appels -->
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl overflow-hidden shadow-lg">
<div class="overflow-x-auto">
<table class="w-full text-left border-collapse">
<thead>
<tr class="bg-anthracite-950/80 text-[11px] font-semibold uppercase text-slate-400 border-b border-anthracite-700">
<th class="py-3 px-4">Date & Heure</th>
<th class="py-3 px-3">Direction</th>
<th class="py-3 px-4">Appelant (Qui a appelé)</th>
<th class="py-3 px-4">Destinataire (Qui a été appelé)</th>
<th class="py-3 px-4">Trunk Utilisé</th>
<th class="py-3 px-3">Durée Parlée</th>
<th class="py-3 px-3">Statut</th>
</tr>
</thead>
<tbody id="cdr-table-body" class="divide-y divide-anthracite-700/60 text-xs">
<tr>
<td colspan="7" class="py-8 text-center text-slate-400">Chargement du journal d'appels...</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>
<!-- ========================================================= -->
<!-- ONGLET 1 : EXTENSIONS SIP -->
<!-- ========================================================= -->
<section id="tab-extensions" class="tab-panel hidden space-y-4">
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
<div>
<h2 class="text-lg font-bold text-white">Postes Téléphoniques PJSIP</h2>
<p class="text-xs text-slate-300">Gérez vos postes internes (UDP non chiffré) et vos postes nomades distants (TLS + chiffrement média SRTP via Let's Encrypt).</p>
</div>
<button type="button" onclick="openExtensionModal()"
class="inline-flex items-center gap-2 px-4 py-2.5 rounded-xl text-xs sm:text-sm font-semibold bg-indigo-600 hover:bg-indigo-500 text-white shadow-lg shadow-indigo-600/20 transition">
<span>+ Nouveau Poste SIP</span>
</button>
</div>
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl overflow-hidden shadow-lg">
<div class="overflow-x-auto">
<table class="w-full text-left border-collapse">
<thead>
<tr class="border-b border-anthracite-700 text-[11px] font-semibold uppercase tracking-wider text-slate-300 bg-anthracite-950/60">
<th class="py-3.5 px-4">Extension</th>
<th class="py-3.5 px-4">Nom d'affichage</th>
<th class="py-3.5 px-4">Groupes d'Extensions (Sites)</th>
<th class="py-3.5 px-4">Statut Enregistrement</th>
<th class="py-3.5 px-4">Mode de Connexion</th>
<th class="py-3.5 px-4">Codecs Audio</th>
<th class="py-3.5 px-4">Secret SIP</th>
<th class="py-3.5 px-4 text-right">Actions</th>
</tr>
</thead>
<tbody id="extensions-tbody" class="divide-y divide-anthracite-700/70 text-sm"></tbody>
</table>
</div>
</div>
</section>
<!-- ========================================================= -->
<!-- ONGLET 2A : GROUPES DE SONNERIE (RING GROUPS) -->
<!-- ========================================================= -->
<section id="tab-groups" class="tab-panel hidden space-y-4">
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
<div>
<h2 class="text-lg font-bold text-white">Groupes de Sonnerie (Ring Groups)</h2>
<p class="text-xs text-slate-300">Objets d'appel entrant avec numéro virtuel (ex: <code class="text-cyan-300">600</code>). Faites sonner plusieurs téléphones en simultané (<code class="text-indigo-300">ringall</code>) ou l'un après l'autre (<code class="text-indigo-300">hunt</code>) avec destination de débordement.</p>
</div>
<button type="button" onclick="openGroupModal()"
class="inline-flex items-center gap-2 px-4 py-2.5 rounded-xl text-xs sm:text-sm font-semibold bg-indigo-600 hover:bg-indigo-500 text-white shadow-lg shadow-indigo-600/20 transition">
<span>+ Nouveau Groupe de Sonnerie</span>
</button>
</div>
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl overflow-hidden shadow-lg">
<div class="overflow-x-auto">
<table class="w-full text-left border-collapse">
<thead>
<tr class="border-b border-anthracite-700 text-[11px] font-semibold uppercase tracking-wider text-slate-300 bg-anthracite-950/60">
<th class="py-3.5 px-4">Numéro</th>
<th class="py-3.5 px-4">Nom du Groupe</th>
<th class="py-3.5 px-4">Stratégie</th>
<th class="py-3.5 px-4">Postes Sonnants</th>
<th class="py-3.5 px-4">Durée / Débordement</th>
<th class="py-3.5 px-4 text-right">Actions</th>
</tr>
</thead>
<tbody id="groups-tbody" class="divide-y divide-anthracite-700/70 text-sm"></tbody>
</table>
</div>
</div>
</section>
<!-- ========================================================= -->
<!-- ONGLET 2B : GROUPES D'EXTENSIONS (SITES & DROITS ROUTES) -->
<!-- ========================================================= -->
<section id="tab-extgroups" class="tab-panel hidden space-y-4">
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
<div>
<h2 class="text-lg font-bold text-white">Groupes d'Extensions (Sites, Services & Droits Sortants)</h2>
<p class="text-xs text-slate-300">Regroupements logiques d'extensions (ex: <code class="text-emerald-300">Groupe Nice</code>, <code class="text-indigo-300">Groupe Paris</code>, <code class="text-cyan-300">Commerciaux</code>) pour gérer en 1 clic leurs droits d'appels sur les <strong>Routes Sortantes</strong>.</p>
</div>
<div class="flex flex-wrap items-center gap-2">
<button type="button" onclick="openExtGroupModal()"
class="inline-flex items-center gap-2 px-4 py-2.5 rounded-xl text-xs sm:text-sm font-semibold bg-cyan-600 hover:bg-cyan-500 text-white shadow-lg shadow-cyan-600/20 transition">
<span>+ Nouveau Groupe d'Extensions</span>
</button>
</div>
</div>
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl overflow-hidden shadow-lg">
<div class="overflow-x-auto">
<table class="w-full text-left border-collapse">
<thead>
<tr class="border-b border-anthracite-700 text-[11px] font-semibold uppercase tracking-wider text-slate-300 bg-anthracite-950/60">
<th class="py-3.5 px-4">Groupe</th>
<th class="py-3.5 px-4">Description</th>
<th class="py-3.5 px-4">Extensions Membres</th>
<th class="py-3.5 px-4">Routes Sortantes Autorisées</th>
<th class="py-3.5 px-4 text-right">Actions</th>
</tr>
</thead>
<tbody id="extgroups-tbody" class="divide-y divide-anthracite-700/70 text-sm"></tbody>
</table>
</div>
</div>
</section>
<!-- ========================================================= -->
<!-- ONGLET 2C : ANNUAIRE D'ENTREPRISE (YEALINK REMOTE XML) -->
<!-- ========================================================= -->
<section id="tab-phonebook" class="tab-panel hidden space-y-4">
<div class="flex flex-col lg:flex-row lg:items-center justify-between gap-3">
<div>
<div class="flex items-center gap-2">
<h2 class="text-lg font-bold text-white">Annuaire d'Entreprise & Carnet d'Adresses IP Phone</h2>
<span class="px-2 py-0.5 rounded-full text-[10px] font-bold bg-teal-500/20 text-teal-300 border border-teal-500/30">XML Téléphones IP</span>
</div>
<p class="text-xs text-slate-300">Répertoire unifié (postes internes & contacts externes) synchronisé automatiquement avec les téléphones IP (Yealink, Fanvil, Grandstream, Snom...) via l'API XML <code class="text-teal-300">phonebook.php</code>.</p>
</div>
<div class="flex items-center flex-wrap gap-2">
<button type="button" onclick="openYealinkXmlModal()"
class="inline-flex items-center gap-2 px-3.5 py-2 rounded-xl text-xs sm:text-sm font-semibold bg-teal-600/20 hover:bg-teal-600/30 text-teal-300 border border-teal-500/40 shadow-sm transition">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"/></svg>
<span>URL XML Annuaire (phonebook.php)</span>
</button>
<button type="button" onclick="syncPhonebookNow()"
class="inline-flex items-center gap-1.5 px-3 py-2 rounded-xl text-xs sm:text-sm font-medium bg-anthracite-800 hover:bg-anthracite-750 text-slate-200 border border-anthracite-700 transition" title="Synchroniser immédiatement avec la liste des extensions">
<svg class="w-4 h-4 text-cyan-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
<span>Synchroniser</span>
</button>
<button type="button" onclick="openPhonebookContactModal()"
class="inline-flex items-center gap-2 px-4 py-2 rounded-xl text-xs sm:text-sm font-semibold bg-indigo-600 hover:bg-indigo-500 text-white shadow-lg shadow-indigo-600/20 transition">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
<span>Nouveau Contact Externe</span>
</button>
</div>
</div>
<!-- Barre de Recherche & Filtres par Groupe d'Annuaire -->
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl p-4 shadow-lg space-y-3">
<div class="flex flex-col md:flex-row md:items-center justify-between gap-3">
<div class="relative flex-1 max-w-md">
<span class="absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none text-slate-400">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
</span>
<input type="text" id="phonebook-search" oninput="filterPhonebook()" placeholder="Rechercher par nom, numéro de poste, mobile, email..."
class="w-full pl-10 pr-4 py-2 rounded-xl bg-anthracite-950 border border-anthracite-700 text-sm text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500 transition">
</div>
<div id="phonebook-stats" class="text-xs text-slate-400 font-mono flex items-center gap-3">
<span>Chargement des contacts...</span>
</div>
</div>
<!-- Filtres par groupe (Pills dynamiques) -->
<div class="flex items-center gap-1.5 flex-wrap pt-1 border-t border-anthracite-700/60" id="phonebook-group-pills">
<button type="button" onclick="selectPhonebookGroupFilter('all')" class="phonebook-grp-pill active px-3 py-1 rounded-lg text-xs font-semibold bg-indigo-600 text-white transition">
Tous les groupes
</button>
</div>
</div>
<!-- Tableau des contacts d'annuaire -->
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl overflow-hidden shadow-lg">
<div class="overflow-x-auto">
<table class="w-full text-left border-collapse">
<thead>
<tr class="border-b border-anthracite-700 text-[11px] font-semibold uppercase tracking-wider text-slate-300 bg-anthracite-950/60">
<th class="py-3 px-4 w-12">Photo</th>
<th class="py-3 px-4">Nom de Contact</th>
<th class="py-3 px-4">Poste Interne</th>
<th class="py-3 px-4">Téléphone Mobile</th>
<th class="py-3 px-4">Autre Numéro</th>
<th class="py-3 px-4">Email</th>
<th class="py-3 px-4">Groupe Annuaire</th>
<th class="py-3 px-4">Type</th>
<th class="py-3 px-4 text-right">Actions</th>
</tr>
</thead>
<tbody id="phonebook-tbody" class="divide-y divide-anthracite-700/60 text-sm"></tbody>
</table>
</div>
</div>
</section>
<!-- ========================================================= -->
<!-- ONGLET 2D : TÉLÉAVERTISSEMENT & INTERPHONIE (PAGING) -->
<!-- ========================================================= -->
<section id="tab-paging" class="tab-panel hidden space-y-4">
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
<div>
<div class="flex items-center gap-2">
<h2 class="text-lg font-bold text-white">Groupes de Téléavertissement & Interphonie (Paging)</h2>
<span class="px-2 py-0.5 rounded-full text-[10px] font-bold bg-purple-500/20 text-purple-300 border border-purple-500/30">Auto-décroché immédiat</span>
</div>
<p class="text-xs text-slate-300">Diffusion d'annonces vocales en direct sur les haut-parleurs des téléphones IP sélectionnés. Composez le numéro du groupe (ex: <code class="text-purple-300 font-mono font-bold">100</code>) pour parler à tous les postes simultanément.</p>
</div>
<div class="flex items-center gap-2">
<button type="button" onclick="openPagingModal()"
class="inline-flex items-center gap-2 px-4 py-2.5 rounded-xl text-xs sm:text-sm font-semibold bg-purple-600 hover:bg-purple-500 text-white shadow-lg shadow-purple-600/20 transition">
<span>+ Nouveau Groupe de Téléavertissement</span>
</button>
</div>
</div>
<!-- Bannière d'aide rapide Paging & Intercom 1-à-1 -->
<div class="p-4 rounded-2xl bg-anthracite-800/90 border border-purple-500/30 grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="flex items-start gap-3">
<div class="w-8 h-8 rounded-xl bg-purple-500/20 border border-purple-500/40 flex items-center justify-center text-purple-300 shrink-0 font-bold text-sm">📢</div>
<div class="space-y-0.5">
<div class="text-xs font-bold text-purple-200">Annonce Générale de Groupe (Paging)</div>
<p class="text-[11px] text-slate-300">Composez le numéro du groupe configuré (ex: <span class="font-mono text-purple-300 font-bold">100</span>). Les téléphones cibles décrochent automatiquement sur haut-parleur après un bip d'avertissement sonore.</p>
</div>
</div>
<div class="flex items-start gap-3">
<div class="w-8 h-8 rounded-xl bg-indigo-500/20 border border-indigo-500/40 flex items-center justify-center text-indigo-300 shrink-0 font-bold text-sm">📞</div>
<div class="space-y-0.5">
<div class="text-xs font-bold text-indigo-200">Interphonie Directe 1-à-1 (*80 + poste)</div>
<p class="text-[11px] text-slate-300">Composez <span class="font-mono text-indigo-300 font-bold">*80</span> suivi du numéro de l'extension (ex: <span class="font-mono text-indigo-300 font-bold">*80101</span>) pour ouvrir directement le haut-parleur et le micro du poste en conversation duplex.</p>
</div>
</div>
</div>
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl overflow-hidden shadow-lg">
<div class="overflow-x-auto">
<table class="w-full text-left border-collapse">
<thead>
<tr class="border-b border-anthracite-700 text-[11px] font-semibold uppercase tracking-wider text-slate-300 bg-anthracite-950/60">
<th class="py-3.5 px-4">Numéro (Extension)</th>
<th class="py-3.5 px-4">Nom du Groupe</th>
<th class="py-3.5 px-4">Mode Audio</th>
<th class="py-3.5 px-4">Postes Membres Associés</th>
<th class="py-3.5 px-4">Si Occupé</th>
<th class="py-3.5 px-4">Bip</th>
<th class="py-3.5 px-4">Statut</th>
<th class="py-3.5 px-4 text-right">Actions</th>
</tr>
</thead>
<tbody id="paging-tbody" class="divide-y divide-anthracite-700/70 text-sm"></tbody>
</table>
</div>
</div>
</section>
<!-- ========================================================= -->
<!-- ONGLET 3 : TRUNKS SIP -->
<!-- ========================================================= -->
<section id="tab-trunks" class="tab-panel hidden space-y-4">
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
<div>
<h2 class="text-lg font-bold text-white">Trunks Opérateurs SIP (Configuration PJSIP Avancée)</h2>
<p class="text-xs text-slate-300">Configurez vos connecteurs PJSIP opérateurs et leurs Routes Entrantes. Pour définir l'ordre de sortie et les droits d'appels sortants, ouvrez <button type="button" onclick="switchTab('outbound')" class="text-emerald-300 underline font-semibold">Routes Sortantes</button>.</p>
</div>
<div class="flex items-center gap-2">
<button type="button" onclick="switchTab('outbound')"
class="px-3.5 py-2.5 rounded-xl text-xs sm:text-sm font-semibold bg-emerald-500/20 hover:bg-emerald-500/30 border border-emerald-500/40 text-emerald-300 transition">
↗ Gérer les Routes Sortantes
</button>
<button type="button" onclick="openTrunkModal()"
class="inline-flex items-center gap-2 px-4 py-2.5 rounded-xl text-xs sm:text-sm font-semibold bg-indigo-600 hover:bg-indigo-500 text-white shadow-lg shadow-indigo-600/20 transition">
<span>+ Nouveau Trunk SIP</span>
</button>
</div>
</div>
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl overflow-hidden shadow-lg">
<div class="overflow-x-auto">
<table class="w-full text-left border-collapse">
<thead>
<tr class="border-b border-anthracite-700 text-[11px] font-semibold uppercase tracking-wider text-slate-300 bg-anthracite-950/60">
<th class="py-3.5 px-4">Nom Trunk</th>
<th class="py-3.5 px-4">Statut PJSIP</th>
<th class="py-3.5 px-4">Serveur / Proxy</th>
<th class="py-3.5 px-4">Auth / Identité SIP</th>
<th class="py-3.5 px-4">Transport & Codecs</th>
<th class="py-3.5 px-4">Routage Entrant & SDA</th>
<th class="py-3.5 px-4 text-right">Actions</th>
</tr>
</thead>
<tbody id="trunks-tbody" class="divide-y divide-anthracite-700/70 text-sm"></tbody>
</table>
</div>
</div>
</section>
<!-- ========================================================= -->
<!-- ONGLET 3B : ROUTES SORTANTES (OUTBOUND ROUTES) -->
<!-- ========================================================= -->
<section id="tab-outbound" class="tab-panel hidden space-y-4">
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
<div>
<h2 class="text-lg font-bold text-white">Routes Sortantes (Outbound Routes & Cascade de Priorité)</h2>
<p class="text-xs text-slate-300">
Définissez l'ordre d'utilisation de vos routes sortantes (ex: <strong>1. Trunk SIP Principal</strong> → <strong>2. Ligne Box / Secours</strong>). Si le premier Trunk est indisponible <strong>ou si le poste/groupe appelant n'a pas les droits sur la première route</strong>, NexusPBX passe automatiquement à la route suivante !
</p>
</div>
<button type="button" onclick="openOutboundRouteModal()"
class="inline-flex items-center gap-2 px-4 py-2.5 rounded-xl text-xs sm:text-sm font-semibold bg-emerald-600 hover:bg-emerald-500 text-white shadow-lg shadow-emerald-600/20 transition shrink-0">
<span>+ Ajout route sortante</span>
</button>
</div>
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl overflow-hidden shadow-lg">
<div class="overflow-x-auto">
<table class="w-full text-left border-collapse">
<thead>
<tr class="border-b border-anthracite-700 text-[11px] font-semibold uppercase tracking-wider text-slate-300 bg-anthracite-950/60">
<th class="py-3.5 px-4 w-28">Ordre</th>
<th class="py-3.5 px-4">Name (Route)</th>
<th class="py-3.5 px-4">CID Sortant</th>
<th class="py-3.5 px-4">Séquence Trunks (Failover)</th>
<th class="py-3.5 px-4">Droits (Groupes & Postes)</th>
<th class="py-3.5 px-4">Attributs & Masques</th>
<th class="py-3.5 px-4 text-right">Actions</th>
</tr>
</thead>
<tbody id="outbound-routes-tbody" class="divide-y divide-anthracite-700/70 text-sm"></tbody>
</table>
</div>
</div>
</section>
<!-- ========================================================= -->
<!-- ONGLET 4 : SERVEURS VOCAUX (IVR) -->
<!-- ========================================================= -->
<section id="tab-ivrs" class="tab-panel hidden space-y-4">
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
<div>
<h2 class="text-lg font-bold text-white">Serveurs Vocaux Interactifs (SVI / IVR)</h2>
<p class="text-xs text-slate-300">Créez vos menus vocaux d'accueil avec routage par touches DTMF (0-9, *, #).</p>
</div>
<button type="button" onclick="openIvrModal()"
class="inline-flex items-center gap-2 px-4 py-2.5 rounded-xl text-xs sm:text-sm font-semibold bg-indigo-600 hover:bg-indigo-500 text-white transition">
<span>+ Nouveau SVI / IVR</span>
</button>
</div>
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl overflow-hidden shadow-lg">
<div class="overflow-x-auto">
<table class="w-full text-left border-collapse">
<thead>
<tr class="border-b border-anthracite-700 text-[11px] font-semibold uppercase tracking-wider text-slate-300 bg-anthracite-950/60">
<th class="py-3.5 px-4">SVI / Menu</th>
<th class="py-3.5 px-4">Annonce Vocale</th>
<th class="py-3.5 px-4">Num. Directe</th>
<th class="py-3.5 px-4">Touches DTMF</th>
<th class="py-3.5 px-4">Sécurité & Échecs</th>
<th class="py-3.5 px-4">Test Direct</th>
<th class="py-3.5 px-4 text-right">Actions</th>
</tr>
</thead>
<tbody id="ivrs-tbody" class="divide-y divide-anthracite-700/70 text-sm"></tbody>
</table>
</div>
</div>
</section>
<!-- ========================================================= -->
<!-- ONGLET 4B : FICHIERS SONS & MÉDIATHÈQUE AUDIO -->
<!-- ========================================================= -->
<section id="tab-sounds" class="tab-panel hidden space-y-4">
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
<div>
<h2 class="text-lg font-bold text-white flex items-center gap-2">
<span class="w-3 h-3 rounded-full bg-amber-400"></span>
<span>Fichiers sons & Musiques d'attente</span>
</h2>
<p class="text-xs text-slate-300">Gérez vos musiques d'attente (MoH), annonces d'accueil, menus SVI et messages de congestion classés par site et par usage.</p>
</div>
<div class="flex flex-wrap items-center gap-2">
<button type="button" onclick="openSoundSitesModal()"
class="inline-flex items-center gap-2 px-3.5 py-2.5 rounded-xl text-xs sm:text-sm font-semibold bg-anthracite-750 hover:bg-anthracite-700 text-slate-200 border border-anthracite-600 transition">
<svg class="w-4 h-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/>
</svg>
<span>Gérer les catégories de sites</span>
</button>
<button type="button" onclick="openSoundModal()"
class="inline-flex items-center gap-2 px-4 py-2.5 rounded-xl text-xs sm:text-sm font-semibold bg-gradient-to-r from-amber-600 to-amber-500 hover:from-amber-500 hover:to-amber-400 text-white shadow-lg shadow-amber-600/20 transition">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
</svg>
<span>Téléverser un fichier son</span>
</button>
</div>
</div>
<!-- Encart dédié : Musique d'attente générale par défaut du PBX -->
<div class="p-4 rounded-2xl bg-anthracite-800 border border-amber-500/30 flex flex-col md:flex-row md:items-center justify-between gap-4 shadow-lg">
<div class="space-y-0.5">
<div class="text-xs font-bold uppercase tracking-wider text-amber-300">Musique d'attente générale par défaut (MoH)</div>
<p class="text-xs text-slate-300">Définit la musique diffusée à tous les correspondants mis en garde sur le standard (sauf surcharge sur une route ou un poste spécifique).</p>
</div>
<div class="flex items-center gap-2">
<select id="global-default-moh-select" class="px-3 py-2 rounded-xl bg-anthracite-950 border border-anthracite-600 text-xs text-white focus:outline-none focus:border-amber-500 min-w-[240px]"></select>
<button type="button" onclick="applyDefaultMohFromSelect()" class="px-4 py-2 rounded-xl bg-amber-600 hover:bg-amber-500 text-white text-xs font-semibold whitespace-nowrap transition">
Définir par défaut
</button>
</div>
</div>
<!-- Barre de filtres par Site et par Usage -->
<div class="p-3.5 rounded-2xl bg-anthracite-800 border border-anthracite-700 flex flex-wrap items-center justify-between gap-3">
<div class="flex flex-wrap items-center gap-2">
<span class="text-xs font-semibold text-slate-400 mr-1">Filtrer par :</span>
<!-- Filtre Site / Contexte -->
<div class="relative">
<select id="filter-sound-site" onchange="renderSoundsTable()"
class="bg-anthracite-900 border border-anthracite-600 rounded-lg px-3 py-1.5 text-xs text-slate-200 focus:outline-none focus:border-amber-500">
<option value="all">Tous les sites</option>
<option value="Global">Global</option>
<option value="Site Nice">Site Nice</option>
<option value="Site Paris">Site Paris</option>
</select>
</div>
<!-- Filtre Usage / Type -->
<div class="relative">
<select id="filter-sound-usage" onchange="renderSoundsTable()"
class="bg-anthracite-900 border border-anthracite-600 rounded-lg px-3 py-1.5 text-xs text-slate-200 focus:outline-none focus:border-amber-500">
<option value="all">Tous les usages</option>
<option value="moh">Musique d'attente (MoH)</option>
<option value="announcement">Annonce d'accueil / Info</option>
<option value="ivr">Menu SVI / Interactif</option>
<option value="congestion">Ligne occupée / Congestion</option>
<option value="custom">Autre / Personnalisé</option>
</select>
</div>
</div>
<!-- Recherche textuelle -->
<div class="w-full sm:w-64">
<input type="text" id="filter-sound-search" oninput="renderSoundsTable()" placeholder="Rechercher un son ou code..."
class="w-full bg-anthracite-950 border border-anthracite-600 rounded-lg px-3 py-1.5 text-xs text-white placeholder-slate-500 focus:outline-none focus:border-amber-500">
</div>
</div>
<!-- Tableau des fichiers sons -->
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl overflow-hidden shadow-lg">
<div class="overflow-x-auto">
<table class="w-full text-left border-collapse">
<thead>
<tr class="border-b border-anthracite-700 text-[11px] font-semibold uppercase tracking-wider text-slate-300 bg-anthracite-950/60">
<th class="py-3.5 px-4">Nom & Description</th>
<th class="py-3.5 px-4">Catégorie de site</th>
<th class="py-3.5 px-4">Usage</th>
<th class="py-3.5 px-4 min-w-[200px]">Écoute audio</th>
<th class="py-3.5 px-4">Format & Durée</th>
<th class="py-3.5 px-4">Référence Asterisk</th>
<th class="py-3.5 px-4 text-right">Actions</th>
</tr>
</thead>
<tbody id="sounds-tbody" class="divide-y divide-anthracite-700/70 text-sm"></tbody>
</table>
</div>
</div>
</section>
<!-- ========================================================= -->
<!-- ONGLET 5 : SYSTÈME DEBIAN, SETTINGS & FICHIERS .CONF -->
<!-- ========================================================= -->
<section id="tab-system" class="tab-panel hidden space-y-6">
<div class="grid grid-cols-1 xl:grid-cols-2 gap-6">
<!-- Formulaire Paramètres Globaux -->
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl p-6 shadow-lg">
<h3 class="text-base font-bold text-white mb-1">Paramètres Globaux SIP, TLS & AMI</h3>
<p class="text-xs text-slate-300 mb-5">Stockés dans la table <code class="text-indigo-300">settings</code> de SQLite et utilisés lors de la génération PJSIP.</p>
<form id="settings-form" class="space-y-4">
<!-- Identité & Logo de l'Exploitant -->
<input type="file" id="company-logo-file-input" accept="image/png,image/svg+xml,image/jpeg,image/webp,image/gif" class="hidden" onchange="handleCompanyLogoUpload(this)">
<div class="p-4 rounded-xl bg-anthracite-950 border border-anthracite-700 space-y-3.5">
<div class="flex items-center justify-between pb-2 border-b border-anthracite-700/60">
<div>
<span class="text-xs font-bold uppercase tracking-wider text-emerald-300">Logo & Marque Blanche Exploitant</span>
<p class="text-[11px] text-slate-400">Affiché sous NexusPBX dans le menu et sur l'écran d'accueil</p>
</div>
<div class="flex items-center gap-2">
<button type="button" onclick="triggerCompanyLogoPicker()" class="px-3 py-1.5 rounded-lg bg-indigo-600 hover:bg-indigo-500 text-xs text-white font-semibold transition shadow-sm">
Importer un Logo
</button>
<button type="button" onclick="clearCompanyLogo()" class="px-2.5 py-1.5 rounded-lg bg-anthracite-800 hover:bg-red-500/20 border border-anthracite-700 text-xs text-slate-300 hover:text-red-300 transition">
Effacer
</button>
</div>
</div>
<div class="flex items-center gap-4">
<!-- Aperçu actuel du logo -->
<div id="settings-company-preview-slot" class="w-32 h-14 rounded-lg bg-anthracite-900 border border-dashed border-anthracite-700 flex items-center justify-center p-2 shrink-0">
<img id="settings-company-preview-img" src="" alt="Aperçu Logo" class="max-h-11 max-w-full object-contain hidden">
<span id="settings-company-preview-empty" class="text-[10px] text-slate-500 italic text-center">Aucun logo configuré</span>
</div>
<div class="flex-1">
<label class="block text-xs font-medium text-slate-200 mb-1">Nom de l'exploitant / entreprise</label>
<input type="text" name="company_name" placeholder="ex: Groupe Télécom & Réseaux SAS"
class="w-full px-3 py-2 rounded-lg bg-anthracite-900 border border-anthracite-700 text-sm text-white">
</div>
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Domaine SIP / Nom PBX</label>
<input type="text" name="domain" required
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white">
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Domaine Let's Encrypt (TLS)</label>
<input type="text" name="letsencrypt_domain" placeholder="ex: sip.mondomaine.fr"
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white">
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Réseau Local (CIDR NAT)</label>
<input type="text" name="local_net" placeholder="10.10.0.0/16"
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white">
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">IP Publique Externe (NAT)</label>
<input type="text" name="external_ip" placeholder="ex: 203.0.113.10"
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white">
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Port SIP UDP (Interne)</label>
<input type="number" name="udp_port" required
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white">
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Port SIP TLS (Distant Chiffré)</label>
<input type="number" name="tls_port" required
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white">
</div>
<div class="sm:col-span-2">
<label class="block text-xs font-medium text-slate-200 mb-1">Langue des invites & annonces vocales (Asterisk)</label>
<select name="system_language" class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white focus:ring-2 focus:ring-indigo-500">
<option value="fr">Français (fr) - Voix officielle francophone June / Asterisk FR</option>
<option value="en">English (en) - Allison / Asterisk Core Sounds EN</option>
</select>
<p class="text-[11px] text-slate-400 mt-1">Définit la langue audio des invites Asterisk : messagerie vocale (*97 / *98), Ne-pas-déranger (*78/*79), horloge parlante (*60) et annonces système.</p>
</div>
</div>
<div class="pt-3 border-t border-anthracite-700">
<h4 class="text-xs font-semibold uppercase tracking-wider text-indigo-300 mb-3">Connexion Asterisk Manager Interface (AMI)</h4>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Hôte AMI</label>
<input type="text" name="ami_host" required
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white">
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Port AMI</label>
<input type="number" name="ami_port" required
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white">
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Utilisateur AMI</label>
<input type="text" name="ami_user" required
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white">
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Secret AMI</label>
<input type="text" name="ami_secret" required
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white font-mono">
</div>
</div>
</div>
<div class="pt-3 border-t border-anthracite-700">
<label class="block text-xs font-medium text-slate-200 mb-1">Nouveau mot de passe Admin Web (optionnel)</label>
<input type="password" name="new_admin_password" placeholder="Laisser vide pour conserver le mot de passe actuel"
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white">
</div>
<div class="pt-2 flex justify-end">
<button type="submit"
class="px-5 py-2.5 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white text-sm font-semibold shadow-lg shadow-indigo-600/20 transition">
Enregistrer les paramètres
</button>
</div>
</form>
</div>
<!-- Diagnostic Système Debian -->
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl p-6 shadow-lg flex flex-col justify-between">
<div>
<div class="flex items-center justify-between mb-1">
<h3 class="text-base font-bold text-white">Diagnostic & Permissions Debian</h3>
<button type="button" onclick="loadState()" class="text-xs text-indigo-300 hover:text-indigo-200 font-semibold">
Actualiser
</button>
</div>
<p class="text-xs text-slate-300 mb-5">Vérification automatique de l'environnement Debian, des droits <code class="text-indigo-300">asterisk:asterisk</code> et du socket AMI.</p>
<div id="diagnostics-list" class="space-y-3"></div>
</div>
</div>
</div>
<!-- Mises à Jour du Système NexusPBX (OTA) -->
<div class="bg-gradient-to-r from-anthracite-800 to-indigo-950/40 border border-indigo-500/30 rounded-2xl p-6 shadow-xl space-y-4">
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 pb-4 border-b border-anthracite-700/80">
<div>
<div class="flex items-center gap-2.5">
<span class="flex h-3 w-3 relative">
<span id="update-ping" class="hidden animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
<span id="update-dot" class="relative inline-flex rounded-full h-3 w-3 bg-emerald-500"></span>
</span>
<h3 class="text-base font-bold text-white">Mises à Jour du Système NexusPBX</h3>
<span id="system-version-tag" class="px-2 py-0.5 text-xs font-mono font-bold rounded-lg bg-indigo-500/20 text-indigo-300 border border-indigo-500/30">v<?= htmlspecialchars($appVersion) ?></span>
</div>
<p class="text-xs text-slate-300 mt-1">Vérifiez les nouvelles versions officielles et mettez à jour votre PBX en 1 clic sans coupure de service.</p>
</div>
<div class="flex items-center gap-2">
<button type="button" onclick="checkSystemUpdate(true)" id="btn-check-update"
class="inline-flex items-center gap-2 px-3.5 py-2 rounded-xl text-xs font-semibold bg-anthracite-900 hover:bg-anthracite-750 text-slate-200 border border-anthracite-700 transition">
<svg class="w-4 h-4 text-cyan-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
<span>Vérifier les mises à jour</span>
</button>
<button type="button" onclick="runSystemUpdate()" id="btn-run-update" style="display:none;"
class="inline-flex items-center gap-2 px-4 py-2 rounded-xl text-xs font-bold bg-emerald-600 hover:bg-emerald-500 text-white shadow-lg shadow-emerald-600/30 transition animate-pulse">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/></svg>
<span>Mettre à jour maintenant (1-Clic)</span>
</button>
</div>
</div>
<!-- État & Changelog -->
<div id="update-status-box" class="p-4 rounded-xl bg-anthracite-950/80 border border-anthracite-700 text-xs space-y-2">
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-1 text-slate-300">
<span>Statut : <strong id="update-status-text" class="text-emerald-400">Votre PBX est à jour avec la dernière version officielle.</strong></span>
<span id="update-last-checked" class="text-[11px] text-slate-500 font-mono">Dernière vérification : À l'instant</span>
</div>
<div id="update-changelog-container" class="hidden pt-2 border-t border-anthracite-800 space-y-1.5">
<span class="font-bold text-white text-[11px] uppercase tracking-wider block">Nouveautés de la version :</span>
<ul id="update-changelog-list" class="list-disc list-inside text-slate-300 space-y-1 text-[11px]"></ul>
</div>
</div>
</div>
<!-- Prévisualisation en direct des fichiers natifs Asterisk -->
<div class="grid grid-cols-1 xl:grid-cols-2 gap-6">
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl overflow-hidden shadow-lg">
<div class="px-4 py-3 bg-anthracite-950 border-b border-anthracite-700 flex items-center justify-between">
<span class="text-xs font-mono font-semibold text-indigo-300">/etc/asterisk/pjsip.custom.conf</span>
<span class="text-[11px] text-slate-400">Généré depuis SQLite</span>
</div>
<pre id="preview-pjsip" class="p-4 text-xs font-mono text-slate-200 overflow-x-auto max-h-96 leading-relaxed"></pre>
</div>
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl overflow-hidden shadow-lg">
<div class="px-4 py-3 bg-anthracite-950 border-b border-anthracite-700 flex items-center justify-between">
<span class="text-xs font-mono font-semibold text-emerald-300">/etc/asterisk/extensions.custom.conf</span>
<span class="text-[11px] text-slate-400">Dialplan natif</span>
</div>
<pre id="preview-extensions" class="p-4 text-xs font-mono text-slate-200 overflow-x-auto max-h-96 leading-relaxed"></pre>
</div>
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl overflow-hidden shadow-lg xl:col-span-2">
<div class="px-4 py-3 bg-anthracite-950 border-b border-anthracite-700 flex items-center justify-between">
<span class="text-xs font-mono font-semibold text-amber-300">/etc/asterisk/voicemail.custom.conf</span>
<span class="text-[11px] text-slate-400">Boîtes vocales Asterisk</span>
</div>
<pre id="preview-voicemail" class="p-4 text-xs font-mono text-slate-200 overflow-x-auto max-h-64 leading-relaxed"></pre>
</div>
</div>
</section>
<!-- ========================================================= -->
<!-- ONGLET : SAUVEGARDES & RESTAURATION (DISASTER RECOVERY) -->
<!-- ========================================================= -->
<section id="tab-backups" class="tab-panel hidden space-y-6">
<!-- En-tête -->
<div class="flex flex-col md:flex-row md:items-center justify-between gap-4 bg-anthracite-800 border border-anthracite-700 rounded-2xl p-6 shadow-lg">
<div>
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-xl bg-purple-500/20 border border-purple-400/30 flex items-center justify-center text-purple-300 shadow-inner">
<svg class="w-5 h-5 text-purple-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4"/>
</svg>
</div>
<div>
<div class="flex items-center gap-2.5">
<h2 class="text-xl font-bold text-white tracking-tight">Sauvegardes & Restauration Système</h2>
<span class="px-2 py-0.5 rounded-full text-[10px] font-bold bg-purple-500/20 text-purple-300 border border-purple-500/30">Disaster Recovery</span>
</div>
<p class="text-xs text-slate-300 mt-0.5">Archive complète : Base de données SQLite, sons, messageries vocales, clés TLS et configurations Asterisk.</p>
</div>
</div>
</div>
<div class="flex items-center gap-2.5 flex-wrap">
<input type="file" id="backup-upload-input" accept=".tar.gz,.tgz,.gz,.tar,application/gzip,application/x-gzip,application/x-tar,application/x-compressed-tar,application/octet-stream" class="hidden" onchange="handleBackupUpload(this)">
<button type="button" onclick="triggerBackupUpload()"
class="inline-flex items-center gap-2 px-3.5 py-2.5 rounded-xl bg-anthracite-900 hover:bg-anthracite-750 border border-anthracite-600 text-xs font-semibold text-slate-200 hover:text-white transition shadow-sm">
<svg class="w-4 h-4 text-purple-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"/>
</svg>
<span>Téléverser & Restaurer (.tar.gz)</span>
</button>
<button type="button" id="btn-create-backup-now" onclick="createBackupNow()"
class="inline-flex items-center gap-2 px-4 py-2.5 rounded-xl bg-purple-600 hover:bg-purple-500 active:bg-purple-700 text-xs font-bold text-white transition shadow-lg shadow-purple-600/25">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
</svg>
<span id="btn-create-backup-label">Créer une Sauvegarde Immédiate</span>
</button>
</div>
</div>
<!-- Cartes de statut rapide -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl p-4 flex items-center gap-3.5">
<div class="w-10 h-10 rounded-xl bg-indigo-500/10 border border-indigo-500/20 flex items-center justify-center text-indigo-400 shrink-0">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</div>
<div class="min-w-0">
<div class="text-[11px] font-semibold uppercase tracking-wider text-slate-400">Dernière Sauvegarde</div>
<div id="backup-stat-last-run" class="text-sm font-bold text-white truncate">—</div>
</div>
</div>
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl p-4 flex items-center gap-3.5">
<div class="w-10 h-10 rounded-xl bg-emerald-500/10 border border-emerald-500/20 flex items-center justify-center text-emerald-400 shrink-0">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</div>
<div class="min-w-0">
<div class="text-[11px] font-semibold uppercase tracking-wider text-slate-400">Destination Sauvegardes</div>
<div id="backup-stat-storage" class="text-sm font-bold text-emerald-400 truncate">Stockage Local</div>
</div>
</div>
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl p-4 flex items-center gap-3.5">
<div class="w-10 h-10 rounded-xl bg-cyan-500/10 border border-cyan-500/20 flex items-center justify-center text-cyan-400 shrink-0">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2 1 3 3 3h10c2 0 3-1 3-3V7M4 7c0-2 1-3 3-3h10c2 0 3 1 3 3M4 7h16"/>
</svg>
</div>
<div class="min-w-0">
<div class="text-[11px] font-semibold uppercase tracking-wider text-slate-400">Sauvegardes Disponibles</div>
<div id="backup-stat-count" class="text-sm font-bold text-white truncate">0 archive(s)</div>
</div>
</div>
</div>
<!-- Tableau des sauvegardes & Formulaire de planification -->
<div class="grid grid-cols-1 xl:grid-cols-3 gap-6">
<!-- Liste des sauvegardes disponibles (2 colonnes sur grand écran) -->
<div class="xl:col-span-2 bg-anthracite-800 border border-anthracite-700 rounded-2xl p-6 shadow-lg space-y-4">
<div class="flex items-center justify-between pb-3 border-b border-anthracite-700">
<div>
<h3 class="text-base font-bold text-white">Archives de Sauvegarde Disponibles</h3>
<p class="text-xs text-slate-300">Sauvegardes prêtes pour téléchargement ou restauration complète en 1 clic.</p>
</div>
<button type="button" onclick="loadBackups()" class="p-2 rounded-lg bg-anthracite-950 border border-anthracite-700 hover:text-white text-slate-300 transition" title="Actualiser la liste">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
</svg>
</button>
</div>
<!-- Tableau -->
<div class="overflow-x-auto">
<table class="w-full text-left text-xs">
<thead class="uppercase bg-anthracite-950 text-slate-400 font-semibold border-b border-anthracite-700">
<tr>
<th class="px-4 py-3">Archive & Version</th>
<th class="px-4 py-3">Date</th>
<th class="px-4 py-3">Taille</th>
<th class="px-4 py-3">Emplacement</th>
<th class="px-4 py-3 text-right">Actions</th>
</tr>
</thead>
<tbody id="backups-table-body" class="divide-y divide-anthracite-700/70 text-slate-200">
<tr>
<td colspan="5" class="px-4 py-8 text-center text-slate-400">Chargement des sauvegardes...</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Paramètres Sauvegardes Automatiques & Distante SSH -->
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl p-6 shadow-lg space-y-5">
<div>
<h3 class="text-base font-bold text-white mb-1">Configuration & Automatisation</h3>
<p class="text-xs text-slate-300">Planification des sauvegardes et synchronisation distante SSH.</p>
</div>
<form id="backup-settings-form" class="space-y-4">
<!-- Activer sauvegarde auto -->
<div class="flex items-center justify-between p-3.5 rounded-xl bg-anthracite-950 border border-anthracite-700">
<div>
<div class="text-xs font-bold text-white">Sauvegarde Automatique</div>
<div class="text-[11px] text-slate-400">Exécution programmée périodique</div>
</div>
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" name="backup_auto_enabled" id="backup_auto_enabled" value="1" class="sr-only peer">
<div class="w-11 h-6 bg-anthracite-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-purple-600"></div>
</label>
</div>
<div class="grid grid-cols-2 gap-3">
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Fréquence</label>
<select name="backup_frequency" class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-xs text-white">
<option value="hourly">Toutes les heures</option>
<option value="6h">Toutes les 6 heures</option>
<option value="12h">Toutes les 12 heures</option>
<option value="daily" selected>Tous les jours (Quotidien)</option>
<option value="weekly">Toutes les semaines</option>
</select>
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Rétention (Max)</label>
<select name="backup_retention" class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-xs text-white">
<option value="3">3 sauvegardes</option>
<option value="7" selected>7 sauvegardes</option>
<option value="14">14 sauvegardes</option>
<option value="30">30 sauvegardes</option>
</select>
</div>
</div>
<!-- Emplacement : Local vs Distant SSH -->
<div>
<label class="block text-xs font-medium text-slate-200 mb-1.5">Type de Stockage</label>
<div class="grid grid-cols-2 gap-2">
<label class="flex items-center gap-2 p-2.5 rounded-lg bg-anthracite-950 border border-anthracite-700 cursor-pointer hover:border-purple-500/50 transition">
<input type="radio" name="backup_storage_type" value="local" checked onchange="toggleSshBackupFields(false)" class="text-purple-600 focus:ring-purple-500">
<span class="text-xs font-medium text-slate-200">Local (Système)</span>
</label>
<label class="flex items-center gap-2 p-2.5 rounded-lg bg-anthracite-950 border border-anthracite-700 cursor-pointer hover:border-purple-500/50 transition">
<input type="radio" name="backup_storage_type" value="ssh" onchange="toggleSshBackupFields(true)" class="text-purple-600 focus:ring-purple-500">
<span class="text-xs font-medium text-slate-200">Distant (SSH / SFTP)</span>
</label>
</div>
<p class="text-[10px] text-slate-400 mt-1">Le stockage distant SSH désactive la conservation locale et transfère les archives vers votre serveur externe.</p>
</div>
<!-- Champs de configuration SSH Distant -->
<div id="backup-ssh-fields" class="hidden p-3.5 rounded-xl bg-anthracite-950 border border-anthracite-700 space-y-3">
<div class="flex items-center justify-between pb-1 border-b border-anthracite-700/60">
<span class="text-xs font-bold uppercase tracking-wider text-purple-300">Paramètres SSH Distant</span>
<button type="button" onclick="testSshBackupConnection()" class="px-2.5 py-1 rounded text-[11px] font-semibold bg-purple-600/20 hover:bg-purple-600/30 text-purple-300 border border-purple-500/30 transition">
Tester la Connexion
</button>
</div>
<div class="grid grid-cols-3 gap-2">
<div class="col-span-2">
<label class="block text-[11px] font-medium text-slate-300 mb-1">Hôte / IP Distante</label>
<input type="text" name="backup_ssh_host" placeholder="ex: 192.168.1.50 ou backup.serveur.fr"
class="w-full px-2.5 py-1.5 rounded-lg bg-anthracite-900 border border-anthracite-700 text-xs text-white">
</div>
<div>
<label class="block text-[11px] font-medium text-slate-300 mb-1">Port</label>
<input type="number" name="backup_ssh_port" value="22"
class="w-full px-2.5 py-1.5 rounded-lg bg-anthracite-900 border border-anthracite-700 text-xs text-white">
</div>
</div>
<div class="grid grid-cols-2 gap-2">
<div>
<label class="block text-[11px] font-medium text-slate-300 mb-1">Utilisateur SSH</label>
<input type="text" name="backup_ssh_user" placeholder="root ou backupuser"
class="w-full px-2.5 py-1.5 rounded-lg bg-anthracite-900 border border-anthracite-700 text-xs text-white">
</div>
<div>
<label class="block text-[11px] font-medium text-slate-300 mb-1">Mot de Passe SSH</label>
<input type="password" name="backup_ssh_password" placeholder="••••••••"
class="w-full px-2.5 py-1.5 rounded-lg bg-anthracite-900 border border-anthracite-700 text-xs text-white font-mono">
</div>
</div>
<div>
<label class="block text-[11px] font-medium text-slate-300 mb-1">Dossier Distant sur le Serveur</label>
<input type="text" name="backup_ssh_remote_path" value="/backups/nexus"
class="w-full px-2.5 py-1.5 rounded-lg bg-anthracite-900 border border-anthracite-700 text-xs text-white font-mono">
</div>
</div>
<button type="submit"
class="w-full py-2.5 px-4 rounded-xl bg-purple-600 hover:bg-purple-500 active:bg-purple-700 text-xs font-bold text-white transition shadow-lg shadow-purple-600/20">
Enregistrer la Configuration
</button>
</form>
</div>
</div>
</section>
<!-- ========================================================= -->
<!-- ONGLET : PARE-FEU & SÉCURITÉ (FAIL2BAN & NFTABLES) -->
<!-- ========================================================= -->
<section id="tab-firewall" class="tab-panel hidden space-y-6">
<!-- En-tête -->
<div class="flex flex-col md:flex-row md:items-center justify-between gap-4 bg-anthracite-800 border border-anthracite-700 rounded-2xl p-6 shadow-lg">
<div>
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-xl bg-emerald-500/20 border border-emerald-400/30 flex items-center justify-center text-emerald-300 shadow-inner">
<svg class="w-5 h-5 text-emerald-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"/>
</svg>
</div>
<div>
<div class="flex items-center gap-2.5">
<h2 class="text-xl font-bold text-white tracking-tight">Pare-feu & Sécurité Réseau</h2>
<span class="px-2 py-0.5 rounded-full text-[10px] font-bold bg-emerald-500/20 text-emerald-300 border border-emerald-500/30">NFTables & Fail2ban</span>
</div>
<p class="text-xs text-slate-300 mt-0.5">Surveillance active des flux SIP/PJSIP, de la console SSH et protection anti-brute force de l'interface d'administration.</p>
</div>
</div>
</div>
<div class="flex items-center gap-2.5 flex-wrap">
<button type="button" onclick="loadFirewallStatus()"
class="inline-flex items-center gap-2 px-3.5 py-2.5 rounded-xl bg-anthracite-900 hover:bg-anthracite-750 border border-anthracite-600 text-xs font-semibold text-slate-200 hover:text-white transition shadow-sm">
<svg class="w-4 h-4 text-emerald-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
</svg>
<span>Actualiser le Statut</span>
</button>
<button type="button" onclick="whitelistMyIp()"
class="inline-flex items-center gap-2 px-4 py-2.5 rounded-xl bg-emerald-600 hover:bg-emerald-500 active:bg-emerald-700 text-xs font-bold text-white transition shadow-lg shadow-emerald-600/25">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
</svg>
<span>Ajouter mon IP à la Liste Blanche</span>
</button>
</div>
</div>
<!-- Cartes de statut rapide -->
<div class="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-4">
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl p-4 flex items-center gap-3.5">
<div class="w-10 h-10 rounded-xl bg-emerald-500/10 border border-emerald-500/20 flex items-center justify-center text-emerald-400 shrink-0">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"/>
</svg>
</div>
<div class="min-w-0">
<div class="text-[11px] font-semibold uppercase tracking-wider text-slate-400">Moteur Fail2ban</div>
<div id="fw-stat-engine" class="text-sm font-bold text-emerald-400 truncate">En ligne (Actif)</div>
</div>
</div>
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl p-4 flex items-center gap-3.5">
<div class="w-10 h-10 rounded-xl bg-indigo-500/10 border border-indigo-500/20 flex items-center justify-center text-indigo-400 shrink-0">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"/>
</svg>
</div>
<div class="min-w-0">
<div class="text-[11px] font-semibold uppercase tracking-wider text-slate-400">Jail Asterisk SIP</div>
<div id="fw-stat-sip-jail" class="text-sm font-bold text-white truncate">Surveillance PJSIP</div>
</div>
</div>
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl p-4 flex items-center gap-3.5">
<div class="w-10 h-10 rounded-xl bg-purple-500/10 border border-purple-500/20 flex items-center justify-center text-purple-400 shrink-0">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/>
</svg>
</div>
<div class="min-w-0">
<div class="text-[11px] font-semibold uppercase tracking-wider text-slate-400">Jail Web Admin & SSH</div>
<div id="fw-stat-web-jail" class="text-sm font-bold text-white truncate">Protégé (Max 3 essais)</div>
</div>
</div>
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl p-4 flex items-center gap-3.5">
<div class="w-10 h-10 rounded-xl bg-rose-500/10 border border-rose-500/20 flex items-center justify-center text-rose-400 shrink-0">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"/>
</svg>
</div>
<div class="min-w-0">
<div class="text-[11px] font-semibold uppercase tracking-wider text-slate-400">Adresses IP Bannies</div>
<div id="fw-stat-banned-count" class="text-sm font-bold text-emerald-400 truncate">0 bannie(s)</div>
</div>
</div>
</div>
<!-- Grille principale : IPs Bannies & Paramétrage -->
<div class="grid grid-cols-1 xl:grid-cols-3 gap-6">
<!-- Colonne Gauche : Liste des IPs Bannies (2 colonnes) -->
<div class="xl:col-span-2 space-y-6">
<!-- Tableau des IPs bannies -->
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl p-6 shadow-lg space-y-4">
<div class="flex items-center justify-between pb-3 border-b border-anthracite-700">
<div>
<h3 class="text-base font-bold text-white">Adresses IP Actuellement Bannies</h3>
<p class="text-xs text-slate-300">Trafic immédiatement bloqué au niveau du pare-feu NFTables.</p>
</div>
<button type="button" onclick="loadFirewallStatus()" class="p-2 rounded-lg bg-anthracite-950 border border-anthracite-700 hover:text-white text-slate-300 transition" title="Rafraîchir">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
</svg>
</button>
</div>
<div class="overflow-x-auto">
<table class="w-full text-left text-xs">
<thead class="uppercase bg-anthracite-950 text-slate-400 font-semibold border-b border-anthracite-700">
<tr>
<th class="px-4 py-3">Adresse IP</th>
<th class="px-4 py-3">Règle / Service Attaqué</th>
<th class="px-4 py-3">Statut Pare-feu</th>
<th class="px-4 py-3 text-right">Action</th>
</tr>
</thead>
<tbody id="firewall-banned-table-body" class="divide-y divide-anthracite-700/70 text-slate-200">
<tr>
<td colspan="4" class="px-4 py-8 text-center text-slate-400">Chargement des données de sécurité...</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Bloc Bannissement Manuel Rapide -->
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl p-5 shadow-lg">
<h4 class="text-xs font-bold uppercase tracking-wider text-rose-300 mb-2">Bannissement Manuel d'une IP Suspecte</h4>
<form id="form-manual-ban" class="flex flex-col sm:flex-row items-center gap-3">
<input type="text" id="manual-ban-ip" placeholder="ex: 198.51.100.25" required
class="flex-1 w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-xs text-white font-mono">
<select id="manual-ban-jail" class="w-full sm:w-48 px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-xs text-white">
<option value="asterisk">Téléphonie SIP</option>
<option value="nexus-web">Interface Web Admin</option>
<option value="sshd">Console SSH</option>
</select>
<button type="submit"
class="w-full sm:w-auto px-4 py-2 rounded-lg bg-rose-600 hover:bg-rose-500 text-xs font-bold text-white transition whitespace-nowrap shadow-sm">
Bloquer cette IP
</button>
</form>
</div>
</div>
<!-- Colonne Droite : Formulaire Configuration Pare-feu & Ports -->
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl p-6 shadow-lg space-y-5">
<div>
<h3 class="text-base font-bold text-white mb-1">Configuration Sécurité & Ports</h3>
<p class="text-xs text-slate-300">Séparation du port Let's Encrypt et personnalisation du port d'administration.</p>
</div>
<form id="firewall-settings-form" class="space-y-4">
<!-- Ports d'accès Web & Isolation Certbot -->
<div class="p-3.5 rounded-xl bg-anthracite-950 border border-anthracite-700 space-y-3">
<span class="text-xs font-bold uppercase tracking-wider text-indigo-300">Ports Web & Isolation Certbot</span>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Port HTTPS Administration NexusPBX</label>
<div class="flex items-center gap-2">
<input type="number" name="admin_https_port" id="admin_https_port" value="8443" min="1" max="65535" required
class="w-full px-3 py-2 rounded-lg bg-anthracite-900 border border-anthracite-700 text-xs text-white font-mono">
<span class="text-xs text-slate-400 whitespace-nowrap">(Défaut : 8443)</span>
</div>
<p class="text-[10px] text-slate-400 mt-1">L'accès Web sécurisé écoute sur ce port (ex: https://ip-du-pbx:8443).</p>
</div>
<!-- Mode Strict Port 80 (Certbot uniquement) -->
<div class="pt-2 border-t border-anthracite-700/60 flex items-start justify-between gap-3">
<div>
<div class="text-xs font-bold text-white">Mode Strict Certbot (Port 80)</div>
<p class="text-[11px] text-slate-400 leading-tight">Le port 80 sert UNIQUEMENT aux validations Let's Encrypt (`/.well-known/acme-challenge/`). Tout le reste renvoie 404.</p>
</div>
<label class="relative inline-flex items-center cursor-pointer shrink-0 mt-0.5">
<input type="checkbox" name="certbot_strict_port80" id="certbot_strict_port80" value="1" class="sr-only peer">
<div class="w-11 h-6 bg-anthracite-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-emerald-600"></div>
</label>
</div>
<!-- Activation Globale Pare-feu & Filtrage -->
<div class="pt-2 border-t border-anthracite-700/60 flex items-start justify-between gap-3">
<div>
<div class="text-xs font-bold text-white">Activer le Pare-feu & Filtrage Réseau</div>
<p class="text-[11px] text-slate-400 leading-tight">Active l'isolation des ports, l'inspection des flux et la protection active NexusPBX.</p>
</div>
<label class="relative inline-flex items-center cursor-pointer shrink-0 mt-0.5">
<input type="checkbox" name="firewall_enabled" id="firewall_enabled" value="1" class="sr-only peer">
<div class="w-11 h-6 bg-anthracite-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-emerald-600"></div>
</label>
</div>
</div>
<!-- Paramètres Fail2ban SIP -->
<div class="p-3.5 rounded-xl bg-anthracite-950 border border-anthracite-700 space-y-3">
<span class="text-xs font-bold uppercase tracking-wider text-cyan-300">Protection SIP & Téléphonie (PJSIP)</span>
<div class="grid grid-cols-2 gap-2.5">
<div>
<label class="block text-[11px] font-medium text-slate-300 mb-1">Max échecs SIP</label>
<select name="fail2ban_sip_maxretry" class="w-full px-2.5 py-1.5 rounded-lg bg-anthracite-900 border border-anthracite-700 text-xs text-white">
<option value="2">2 tentatives</option>
<option value="3" selected>3 tentatives</option>
<option value="5">5 tentatives</option>
</select>
</div>
<div>
<label class="block text-[11px] font-medium text-slate-300 mb-1">Durée du ban</label>
<select name="fail2ban_sip_bantime" class="w-full px-2.5 py-1.5 rounded-lg bg-anthracite-900 border border-anthracite-700 text-xs text-white">
<option value="3600" selected>1 heure</option>
<option value="43200">12 heures</option>
<option value="86400">24 heures</option>
<option value="604800">7 jours</option>
</select>
</div>
</div>
</div>
<!-- Liste Blanche Permanente (IgnoreIP) -->
<div>
<div class="flex items-center justify-between mb-1">
<label class="block text-xs font-medium text-slate-200">Liste Blanche Permanente (IgnoreIP)</label>
<button type="button" onclick="whitelistMyIp()" class="text-[10px] text-emerald-400 hover:text-emerald-300 font-semibold underline">
+ Mon IP
</button>
</div>
<textarea name="fail2ban_whitelist_ips" id="fail2ban_whitelist_ips" rows="3"
placeholder="127.0.0.1/8 10.0.0.0/8 192.168.0.0/16 82.122.84.60"
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-xs text-white font-mono leading-relaxed"></textarea>
<p class="text-[10px] text-slate-400 mt-1">Séparez les adresses ou sous-réseaux CIDR par un espace. Ces adresses ne seront jamais bannies par le pare-feu.</p>
</div>
<button type="submit" id="btn-save-firewall-settings"
class="w-full py-2.5 px-4 rounded-xl bg-emerald-600 hover:bg-emerald-500 active:bg-emerald-700 text-xs font-bold text-white transition shadow-lg shadow-emerald-600/20">
Enregistrer & Appliquer les Règles
</button>
</form>
</div>
</div>
</section>
<!-- ========================================================= -->
<!-- ONGLET 5 : CODES DE FONCTIONS & SERVICES TÉLÉPHONIQUES -->
<!-- ========================================================= -->
<section id="tab-featurecodes" class="tab-panel hidden space-y-6">
<!-- En-tête -->
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div>
<div class="flex items-center gap-2.5">
<div class="w-8 h-8 rounded-xl bg-indigo-500/20 text-indigo-400 flex items-center justify-center font-bold">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 20l4-16m2 16l4-16M6 9h14M4 15h14"/>
</svg>
</div>
<h2 class="text-lg font-bold text-white">Codes de Fonctions & Services Téléphoniques</h2>
</div>
<p class="text-xs text-slate-300 mt-1">Codes verticaux Asterisk pour piloter vos renvois d'appels, le mode Ne-pas-Déranger, les boîtes vocales, la liste noire et l'interception d'appels.</p>
</div>
<div class="flex items-center gap-2">
<button type="button" onclick="resetFeatureCodes()"
class="px-3.5 py-2 rounded-xl bg-anthracite-800 hover:bg-anthracite-700 border border-anthracite-700 text-slate-300 hover:text-white font-semibold text-xs flex items-center gap-1.5 transition">
<span>↺ Réinitialiser valeurs FreePBX</span>
</button>
</div>
</div>
<!-- Guide d'interception & Touches BLF Postes IP -->
<div class="bg-gradient-to-r from-indigo-950/60 to-anthracite-800 border border-indigo-500/30 rounded-2xl p-5 shadow-lg space-y-3">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2.5">
<span class="flex h-3 w-3 relative">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
<span class="relative inline-flex rounded-full h-3 w-3 bg-emerald-500"></span>
</span>
<h3 class="text-sm font-bold text-white">Touches de Ligne BLF & Interception d'Appel sur Postes IP (Yealink, Fanvil, Grandstream, Snom...)</h3>
</div>
<span class="px-2.5 py-0.5 rounded-full text-[10px] font-bold bg-indigo-500/20 text-indigo-300 border border-indigo-500/40">FreePBX Compatible</span>
</div>
<p class="text-xs text-slate-300 leading-relaxed">
NexusPBX génère automatiquement les <strong>hints</strong> Asterisk pour surveiller l'état en direct de vos téléphones (<span class="text-emerald-400 font-semibold">Vert</span> = libre, <span class="text-rose-400 font-semibold animate-pulse">Rouge clignotant</span> = sonnerie en cours, <span class="text-rose-500 font-semibold">Rouge fixe</span> = en communication).
</p>
<div class="p-3 bg-anthracite-950/80 rounded-xl border border-anthracite-700/80 text-xs space-y-2">
<div class="font-semibold text-slate-200">Pour configurer une touche de surveillance et récupération d'appel sur votre poste IP (Yealink, Fanvil, Grandstream, Snom...) :</div>
<div class="grid grid-cols-2 sm:grid-cols-5 gap-2 text-[11px] font-mono pt-1">
<div class="bg-anthracite-900 p-2 rounded-lg border border-anthracite-700"><span class="text-slate-400 block text-[10px] uppercase font-sans">Type</span><strong class="text-white">BLF</strong></div>
<div class="bg-anthracite-900 p-2 rounded-lg border border-anthracite-700"><span class="text-slate-400 block text-[10px] uppercase font-sans">Valeur (Value)</span><strong class="text-cyan-400">101</strong> (ou n° poste)</div>
<div class="bg-anthracite-900 p-2 rounded-lg border border-anthracite-700"><span class="text-slate-400 block text-[10px] uppercase font-sans">Étiquette (Label)</span><strong class="text-white">Maurice</strong></div>
<div class="bg-anthracite-900 p-2 rounded-lg border border-anthracite-700"><span class="text-slate-400 block text-[10px] uppercase font-sans">Ligne (Line)</span><strong class="text-white">Line 1</strong></div>
<div class="bg-anthracite-900 p-2 rounded-lg border border-indigo-500/40 bg-indigo-950/30"><span class="text-indigo-300 block text-[10px] uppercase font-sans">Extension (Pickup)</span><strong class="text-indigo-300 font-bold">**</strong></div>
</div>
<div class="text-[11px] text-slate-400 pt-1">
💡 Quand le poste surveillé sonne, sa touche clignote : un simple appui dessus intercepte l'appel immédiatement via le préfixe <code class="text-indigo-300">**</code> !
</div>
</div>
</div>
<!-- Filtres par catégorie -->
<div class="flex items-center gap-2 overflow-x-auto pb-1" id="feature-codes-filters">
<button type="button" onclick="filterFeatureCodes('all')" class="fc-filter-btn px-3 py-1.5 rounded-xl text-xs font-semibold bg-indigo-600 text-white transition" data-category="all">Tous (<span id="fc-count-all">0</span>)</button>
<button type="button" onclick="filterFeatureCodes('forward')" class="fc-filter-btn px-3 py-1.5 rounded-xl text-xs font-semibold bg-anthracite-800 text-slate-300 hover:text-white transition" data-category="forward">Renvois d'appels</button>
<button type="button" onclick="filterFeatureCodes('pickup')" class="fc-filter-btn px-3 py-1.5 rounded-xl text-xs font-semibold bg-anthracite-800 text-slate-300 hover:text-white transition" data-category="pickup">Supervision & BLF</button>
<button type="button" onclick="filterFeatureCodes('dnd')" class="fc-filter-btn px-3 py-1.5 rounded-xl text-xs font-semibold bg-anthracite-800 text-slate-300 hover:text-white transition" data-category="dnd">Ne-pas-Déranger</button>
<button type="button" onclick="filterFeatureCodes('voicemail')" class="fc-filter-btn px-3 py-1.5 rounded-xl text-xs font-semibold bg-anthracite-800 text-slate-300 hover:text-white transition" data-category="voicemail">Messagerie Vocale</button>
<button type="button" onclick="filterFeatureCodes('security')" class="fc-filter-btn px-3 py-1.5 rounded-xl text-xs font-semibold bg-anthracite-800 text-slate-300 hover:text-white transition" data-category="security">Sécurité & Blacklist</button>
<button type="button" onclick="filterFeatureCodes('tools')" class="fc-filter-btn px-3 py-1.5 rounded-xl text-xs font-semibold bg-anthracite-800 text-slate-300 hover:text-white transition" data-category="tools">Outils & Tests</button>
</div>
<!-- Tableau des codes de fonction -->
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl overflow-hidden shadow-lg">
<div class="overflow-x-auto">
<table class="w-full text-left text-xs text-slate-300">
<thead class="bg-anthracite-950 text-slate-400 uppercase text-[10px] tracking-wider border-b border-anthracite-700">
<tr>
<th class="py-3 px-4 font-semibold">Code / Raccourci</th>
<th class="py-3 px-4 font-semibold">Fonction & Service</th>
<th class="py-3 px-4 font-semibold hidden sm:table-cell">Description & Modalités</th>
<th class="py-3 px-4 font-semibold">Catégorie</th>
<th class="py-3 px-4 font-semibold text-center">État</th>
<th class="py-3 px-4 font-semibold text-right">Actions</th>
</tr>
</thead>
<tbody id="feature-codes-table-body" class="divide-y divide-anthracite-700/60">
<!-- Rempli dynamiquement par renderFeatureCodes() -->
</tbody>
</table>
</div>
</div>
</section>
<!-- ========================================================= -->
<!-- ONGLET 5B : PARAMÈTRES SIP GLOBAUX & TRAVERSÉE NAT -->
<!-- ========================================================= -->
<section id="tab-sipsettings" class="tab-panel hidden space-y-6">
<!-- En-tête -->
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div>
<div class="flex items-center gap-2.5">
<div class="w-8 h-8 rounded-xl bg-cyan-500/20 text-cyan-400 flex items-center justify-center font-bold">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9"/>
</svg>
</div>
<h2 class="text-lg font-bold text-white">Paramètres SIP Globaux & Traversée NAT</h2>
</div>
<p class="text-xs text-slate-300 mt-1">Configurez l'adresse externe (IP ou DNS dynamique FQDN), la redirection des ports NAT et la plage de ports audio RTP pour garantir une voix bidirectionnelle parfaite sans coupure.</p>
</div>
</div>
<form id="form-sip-settings" class="space-y-6">
<!-- 1. Configuration NAT & Hôte Public -->
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl p-6 shadow-lg space-y-4">
<div class="flex items-center justify-between pb-3 border-b border-anthracite-700">
<div class="flex items-center gap-2">
<span class="w-2.5 h-2.5 rounded-full bg-cyan-400"></span>
<h3 class="text-sm font-bold text-white">Traversée NAT & Détection d'Adresse Publique (WAN)</h3>
</div>
<span class="px-2.5 py-0.5 rounded-full text-[10px] font-bold bg-cyan-500/20 text-cyan-300 border border-cyan-500/30">PJSIP Transports</span>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- Adresse Externe / FQDN -->
<div class="space-y-2">
<div class="flex items-center justify-between">
<label class="block text-xs font-semibold text-slate-200">Adresse Publique ou Nom de Domaine (FQDN / DDNS) *</label>
<button type="button" onclick="detectPublicIp()" id="btn-detect-ip"
class="text-[11px] text-cyan-400 hover:text-cyan-300 flex items-center gap-1 font-semibold transition">
<span id="spinner-detect-ip" class="hidden animate-spin">↻</span>
<span>🔍 Détecter l'IP publique</span>
</button>
</div>
<input type="text" id="sip-external-ip" name="external_ip" placeholder="ex: sip.mondomaine.com ou 198.51.100.1"
class="w-full px-3 py-2 rounded-xl bg-anthracite-950 border border-anthracite-700 text-xs text-white font-mono focus:ring-2 focus:ring-cyan-500 focus:outline-none">
<p class="text-[11px] text-slate-400 leading-relaxed">
Si votre IP est dynamique, renseignez un <strong>nom de domaine FQDN / DynDNS</strong> (ex: <code>sip.mondomaine.fr</code>) pour ne jamais perdre vos téléphones distants lors d'un renouvellement de bail IP par votre opérateur.
</p>
</div>
<!-- Réseaux Locaux (Local Networks) -->
<div class="space-y-2">
<div class="flex items-center justify-between">
<label class="block text-xs font-semibold text-slate-200">Réseaux Locaux (Local Networks - CIDR) *</label>
<button type="button" onclick="addLocalNetworkField()"
class="text-[11px] text-emerald-400 hover:text-emerald-300 font-semibold flex items-center gap-1 transition">
<span>➕ Ajouter un réseau local</span>
</button>
</div>
<div id="local-networks-container" class="space-y-2 max-h-48 overflow-y-auto pr-1"></div>
<p class="text-[11px] text-slate-400 leading-relaxed">
Sous-réseaux privés LAN. Quand un téléphone appelle depuis l'un de ces réseaux, Asterisk transmet son IP locale privée sans appliquer la translation NAT (évite le hairpinning).
</p>
</div>
</div>
</div>
<!-- 2. Ports d'Écoute Internes vs Ports Externes NAT -->
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl p-6 shadow-lg space-y-4">
<div class="flex items-center justify-between pb-3 border-b border-anthracite-700">
<div class="flex items-center gap-2">
<span class="w-2.5 h-2.5 rounded-full bg-indigo-400"></span>
<h3 class="text-sm font-bold text-white">Ports d'Écoute Internes & Redirection de Ports (Port Forwarding Box / Routeur)</h3>
</div>
<span class="px-2.5 py-0.5 rounded-full text-[10px] font-bold bg-indigo-500/20 text-indigo-300 border border-indigo-500/30">Signaling Ports</span>
</div>
<p class="text-xs text-slate-300 leading-relaxed">
Si vous effectuez une translation de port sur votre routeur (ex: port public <code>7788</code> redirigé vers le <code>5060</code> interne de votre PBX), renseignez le port externe. Asterisk annoncera automatiquement ce port externe dans ses paquets SIP.
</p>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Transport UDP -->
<div class="p-4 rounded-xl bg-anthracite-950/80 border border-anthracite-700/80 space-y-3">
<div class="flex items-center justify-between">
<span class="text-xs font-bold text-slate-200">Transport UDP (Standard)</span>
<span class="text-[10px] font-mono text-slate-400">[transport-udp]</span>
</div>
<div class="grid grid-cols-2 gap-3">
<div>
<label class="block text-[11px] text-slate-400 mb-1">Port d'écoute interne</label>
<input type="number" id="sip-udp-port" name="udp_port" value="5060" min="1024" max="65535" required
class="w-full px-3 py-2 rounded-xl bg-anthracite-900 border border-anthracite-700 text-xs text-white font-mono focus:ring-2 focus:ring-indigo-500 focus:outline-none">
</div>
<div>
<label class="block text-[11px] text-slate-400 mb-1">Port externe NAT (WAN)</label>
<input type="number" id="sip-external-udp-port" name="external_udp_port" placeholder="ex: 7788" min="0" max="65535"
class="w-full px-3 py-2 rounded-xl bg-anthracite-900 border border-anthracite-700 text-xs text-white font-mono focus:ring-2 focus:ring-indigo-500 focus:outline-none">
</div>
</div>
<div class="text-[10px] text-slate-500">Laissez le port externe vide ou à 0 s'il est identique au port interne.</div>
</div>
<!-- Transport TLS -->
<div class="p-4 rounded-xl bg-anthracite-950/80 border border-anthracite-700/80 space-y-3">
<div class="flex items-center justify-between">
<span class="text-xs font-bold text-slate-200">Transport TLS (Sécurisé)</span>
<span class="text-[10px] font-mono text-slate-400">[transport-tls]</span>
</div>
<div class="grid grid-cols-2 gap-3">
<div>
<label class="block text-[11px] text-slate-400 mb-1">Port d'écoute interne</label>
<input type="number" id="sip-tls-port" name="tls_port" value="5061" min="1024" max="65535" required
class="w-full px-3 py-2 rounded-xl bg-anthracite-900 border border-anthracite-700 text-xs text-white font-mono focus:ring-2 focus:ring-emerald-500 focus:outline-none">
</div>
<div>
<label class="block text-[11px] text-slate-400 mb-1">Port externe NAT (WAN)</label>
<input type="number" id="sip-external-tls-port" name="external_tls_port" placeholder="ex: 7789" min="0" max="65535"
class="w-full px-3 py-2 rounded-xl bg-anthracite-900 border border-anthracite-700 text-xs text-white font-mono focus:ring-2 focus:ring-emerald-500 focus:outline-none">
</div>
</div>
<div class="text-[10px] text-slate-500">Laissez le port externe vide ou à 0 s'il est identique au port interne.</div>
</div>
</div>
</div>
<!-- 3. Flux Média Audio (RTP Settings) -->
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl p-6 shadow-lg space-y-4">
<div class="flex items-center justify-between pb-3 border-b border-anthracite-700">
<div class="flex items-center gap-2">
<span class="w-2.5 h-2.5 rounded-full bg-emerald-400"></span>
<h3 class="text-sm font-bold text-white">Plage de Ports Audio RTP & Protection de Flux</h3>
</div>
<span class="px-2.5 py-0.5 rounded-full text-[10px] font-bold bg-emerald-500/20 text-emerald-300 border border-emerald-500/30">/etc/asterisk/rtp.conf</span>
</div>
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
<div>
<label class="block text-xs font-semibold text-slate-200 mb-1">Port RTP Début (Start) *</label>
<input type="number" id="sip-rtp-start" name="rtp_start" value="10000" min="1024" max="65530" required
class="w-full px-3 py-2 rounded-xl bg-anthracite-950 border border-anthracite-700 text-xs text-white font-mono focus:ring-2 focus:ring-emerald-500 focus:outline-none">
</div>
<div>
<label class="block text-xs font-semibold text-slate-200 mb-1">Port RTP Fin (End) *</label>
<input type="number" id="sip-rtp-end" name="rtp_end" value="20000" min="1028" max="65535" required
class="w-full px-3 py-2 rounded-xl bg-anthracite-950 border border-anthracite-700 text-xs text-white font-mono focus:ring-2 focus:ring-emerald-500 focus:outline-none">
</div>
<div>
<label class="block text-xs font-semibold text-slate-200 mb-1">Strict RTP *</label>
<select id="sip-rtp-strict" name="rtp_strict"
class="w-full px-3 py-2 rounded-xl bg-anthracite-950 border border-anthracite-700 text-xs text-white focus:ring-2 focus:ring-emerald-500 focus:outline-none">
<option value="1">Oui (Recommandé - Anti-spoofing)</option>
<option value="0">Non</option>
</select>
</div>
<div>
<label class="block text-xs font-semibold text-slate-200 mb-1">RTP Checksums *</label>
<select id="sip-rtp-checksums" name="rtp_checksums"
class="w-full px-3 py-2 rounded-xl bg-anthracite-950 border border-anthracite-700 text-xs text-white focus:ring-2 focus:ring-emerald-500 focus:outline-none">
<option value="1">Oui (Validation intégrité)</option>
<option value="0">Non</option>
</select>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 pt-2">
<div>
<label class="block text-xs font-semibold text-slate-200 mb-1">RTP Keepalive (secondes)</label>
<input type="number" id="sip-rtp-keepalive" name="rtp_keepalive" value="15" min="0" max="120"
class="w-full px-3 py-2 rounded-xl bg-anthracite-950 border border-anthracite-700 text-xs text-white font-mono focus:ring-2 focus:ring-emerald-500 focus:outline-none">
<p class="text-[11px] text-slate-400 mt-1">Envoie des paquets silencieux réguliers pour maintenir le port NAT ouvert sur les routeurs stricts.</p>
</div>
<div>
<label class="block text-xs font-semibold text-slate-200 mb-1">RTP Timeout (secondes)</label>
<input type="number" id="sip-rtp-timeout" name="rtp_timeout" value="30" min="5" max="300"
class="w-full px-3 py-2 rounded-xl bg-anthracite-950 border border-anthracite-700 text-xs text-white font-mono focus:ring-2 focus:ring-emerald-500 focus:outline-none">
<p class="text-[11px] text-slate-400 mt-1">Durée avant coupure si aucun flux voix n'est reçu (raccroché distant brutal).</p>
</div>
</div>
</div>
<!-- 4. Sécurité & Appels Anonymes -->
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl p-6 shadow-lg space-y-3">
<div class="flex items-center justify-between pb-3 border-b border-anthracite-700">
<div class="flex items-center gap-2">
<span class="w-2.5 h-2.5 rounded-full bg-rose-400"></span>
<h3 class="text-sm font-bold text-white">Sécurité SIP & Filtrage des Requêtes Anonymes</h3>
</div>
</div>
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div class="space-y-1">
<div class="text-xs font-semibold text-slate-200">Permettre les appels SIP anonymes / non authentifiés (Guest SIP)</div>
<p class="text-[11px] text-slate-400">
Recommandé : <strong>Non</strong>. Empêche les robots et scanners automatisés d'Internet de faire sonner vos téléphones ou d'utiliser vos lignes sortantes.
</p>
</div>
<select id="sip-allow-anonymous" name="allow_anonymous_sip"
class="px-3 py-2 rounded-xl bg-anthracite-950 border border-anthracite-700 text-xs text-white focus:ring-2 focus:ring-rose-500 focus:outline-none shrink-0">
<option value="0">Non (Sécurisé - Bloquer les anonymes)</option>
<option value="1">Oui (Tolérer les flux SIP invités)</option>
</select>
</div>
</div>
<div class="flex justify-end pt-2">
<button type="submit" id="btn-save-sip-settings"
class="px-6 py-2.5 rounded-xl bg-cyan-600 hover:bg-cyan-500 text-white font-semibold text-xs flex items-center gap-2 shadow-lg shadow-cyan-600/20 transition">
<span id="spinner-save-sip-settings" class="hidden animate-spin">↻</span>
<span>Enregistrer & Appliquer les Paramètres SIP & NAT</span>
</button>
</div>
</form>
</section>
<!-- ========================================================= -->
<!-- ONGLET 6 : CERTIFICATS SSL / TLS (SIGNALISATION & RTP) -->
<!-- ========================================================= -->
<section id="tab-certificates" class="tab-panel hidden space-y-6">
<!-- En-tête avec Actions -->
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div>
<div class="flex items-center gap-2.5">
<div class="w-8 h-8 rounded-xl bg-emerald-500/20 text-emerald-400 flex items-center justify-center font-bold">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"/>
</svg>
</div>
<h2 class="text-lg font-bold text-white">Gestionnaire de Certificats SSL / TLS & Chiffrement</h2>
</div>
<p class="text-xs text-slate-300 mt-1">Générez des certificats Let's Encrypt automatisés ou importez vos certificats d'entreprise (DigiCert, Sectigo, OVH). Le certificat actif sécurise la signalisation SIP TLS (5061) et les communications audio chiffrées (SRTP / DTLS).</p>
</div>
<div class="flex items-center gap-2.5 flex-wrap">
<button type="button" onclick="openLetsEncryptModal()"
class="px-3.5 py-2 rounded-xl bg-emerald-600 hover:bg-emerald-500 text-white font-semibold text-xs flex items-center gap-2 shadow-lg shadow-emerald-600/20 transition">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/>
</svg>
<span>+ Générer via Let's Encrypt</span>
</button>
<button type="button" onclick="openCustomCertModal()"
class="px-3.5 py-2 rounded-xl bg-anthracite-700 hover:bg-anthracite-600 text-slate-200 border border-anthracite-600 font-semibold text-xs flex items-center gap-2 transition">
<svg class="w-4 h-4 text-cyan-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"/>
</svg>
<span>+ Importer un Certificat (Custom / DigiCert)</span>
</button>
</div>
</div>
<!-- Carte Résumé : Certificat Actif en Production -->
<div id="active-cert-banner" class="rounded-2xl p-5 border border-emerald-500/40 bg-gradient-to-r from-emerald-950/40 via-anthracite-800 to-anthracite-900 shadow-xl relative overflow-hidden">
<div class="absolute -right-6 -bottom-6 w-32 h-32 rounded-full bg-emerald-500/5 blur-2xl pointer-events-none"></div>
<div class="flex flex-col lg:flex-row lg:items-center justify-between gap-4">
<div class="flex items-start gap-4">
<div class="w-12 h-12 rounded-2xl bg-emerald-500/20 border border-emerald-500/40 flex items-center justify-center text-emerald-400 shrink-0 shadow-inner">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"/>
</svg>
</div>
<div>
<div class="flex items-center gap-2.5 flex-wrap">
<span class="px-2 py-0.5 rounded-full text-[10px] font-bold uppercase tracking-wider bg-emerald-500/20 text-emerald-300 border border-emerald-500/30">Certificat Actif en Production</span>
<span id="active-cert-type-badge" class="px-2 py-0.5 rounded-full text-[10px] font-bold bg-anthracite-700 text-slate-300 border border-anthracite-600">Auto-signé Système</span>
</div>
<h3 id="active-cert-name" class="text-base sm:text-lg font-bold text-white mt-1">Certificat Auto-signé Système</h3>
<p class="text-xs text-slate-300 font-mono mt-0.5 flex items-center gap-2">
<span>FQDN / CN :</span>
<span id="active-cert-domain" class="text-emerald-300 font-bold">pbx.local</span>
<span class="text-slate-500">•</span>
<span>Émetteur :</span>
<span id="active-cert-issuer" class="text-slate-200">NexusPBX</span>
</p>
</div>
</div>
<!-- Pôles de sécurité protégés -->
<div class="flex items-center gap-3 flex-wrap">
<div class="px-3 py-2 rounded-xl bg-anthracite-900/80 border border-anthracite-700 text-center">
<div class="text-[10px] text-slate-400 font-semibold uppercase">Signalisation</div>
<div class="text-xs font-bold text-emerald-400 mt-0.5">TLS 5061</div>
</div>
<div class="px-3 py-2 rounded-xl bg-anthracite-900/80 border border-anthracite-700 text-center">
<div class="text-[10px] text-slate-400 font-semibold uppercase">Voix Audio</div>
<div class="text-xs font-bold text-emerald-400 mt-0.5">SRTP / DTLS</div>
</div>
<div class="px-3 py-2 rounded-xl bg-anthracite-900/80 border border-anthracite-700 text-center">
<div class="text-[10px] text-slate-400 font-semibold uppercase">Validité</div>
<div id="active-cert-days" class="text-xs font-bold text-emerald-400 mt-0.5">3648 j restants</div>
</div>
<div id="active-cert-renew-wrapper" class="hidden">
<button type="button" id="btn-renew-active-cert" onclick="renewCertificateFromBanner()"
class="px-3 py-2 rounded-xl bg-emerald-600/30 hover:bg-emerald-600 text-emerald-300 hover:text-white border border-emerald-500/40 text-xs font-bold transition">
↻ Renouveler
</button>
</div>
</div>
</div>
</div>
<!-- Paramètres de Sécurité Transport TLS & Mutual TLS (mTLS) -->
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl p-5 shadow-lg">
<div class="flex flex-col md:flex-row md:items-center justify-between gap-4">
<div class="space-y-1">
<div class="flex items-center gap-2">
<span class="w-2.5 h-2.5 rounded-full bg-cyan-400"></span>
<h3 class="text-sm font-bold text-white">Politique de Vérification Client TLS (Mutual TLS / mTLS)</h3>
<span id="badge-mtls-status" class="px-2 py-0.5 rounded-full text-[10px] font-bold bg-anthracite-700 text-slate-300 border border-anthracite-600">Désactivé (Standard)</span>
</div>
<p class="text-xs text-slate-300 max-w-3xl leading-relaxed">
Définit la directive PJSIP <code>verify_client</code> sur le transport TLS (port 5061).
En mode standard, le combiné (Yealink, Fanvil, Cisco...) valide le serveur PBX et s'authentifie par identifiant / mot de passe SIP dans le tunnel crypté. En mode Mutual TLS strict, Asterisk exige en plus un certificat client X.509 installé sur chaque téléphone.
</p>
</div>
<div class="flex items-center gap-3 shrink-0">
<select id="select-tls-verify-client"
class="px-3 py-2 rounded-xl bg-anthracite-900 border border-anthracite-700 text-xs text-white focus:ring-2 focus:ring-cyan-500 focus:outline-none">
<option value="0">Désactivé (Standard - Recommandé)</option>
<option value="1">Activé (Strict - Certificat client requis)</option>
</select>
<button type="button" onclick="saveTlsSecurityPolicy()"
class="px-4 py-2 rounded-xl bg-cyan-600 hover:bg-cyan-500 text-white font-semibold text-xs shadow-md shadow-cyan-600/20 transition">
Appliquer
</button>
</div>
</div>
</div>
<!-- Tableau des Certificats Installés -->
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl overflow-hidden shadow-lg">
<div class="px-6 py-4 bg-anthracite-950/60 border-b border-anthracite-700 flex items-center justify-between">
<div>
<h3 class="text-sm font-bold text-white">Magasin de Certificats SSL / TLS</h3>
<p class="text-xs text-slate-400">Tous les certificats enregistrés sur cette instance NexusPBX.</p>
</div>
<span id="certs-count-badge" class="px-2.5 py-0.5 text-xs rounded-full bg-anthracite-800 text-slate-300 border border-anthracite-700 font-bold">1 certificat</span>
</div>
<div class="overflow-x-auto">
<table class="w-full text-left border-collapse">
<thead>
<tr class="bg-anthracite-950/80 text-[11px] font-semibold uppercase text-slate-400 border-b border-anthracite-700">
<th class="py-3 px-4">Nom & Label</th>
<th class="py-3 px-4">Type</th>
<th class="py-3 px-4">Domaine Principal & SAN</th>
<th class="py-3 px-4">Émetteur (Issuer)</th>
<th class="py-3 px-4">Échéance & Validité</th>
<th class="py-3 px-4 text-center">Utilisation</th>
<th class="py-3 px-4 text-right">Actions</th>
</tr>
</thead>
<tbody id="certificates-tbody" class="divide-y divide-anthracite-700/60 text-xs"></tbody>
</table>
</div>
</div>
<!-- Fiches d'aide & Bonnes pratiques TLS / SRTP -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 text-xs">
<div class="bg-anthracite-800/80 border border-anthracite-700 rounded-xl p-4 space-y-1.5">
<div class="flex items-center gap-2 text-emerald-400 font-bold">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/></svg>
<span>Signalisation Chiffrée (SIP TLS)</span>
</div>
<p class="text-slate-300 leading-relaxed">Le port 5061 écoute en TLS 1.2/1.3. Vos téléphones distants et softphones (MicroSIP, Linphone, Yealink, Fanvil) échangent leurs identifiants de manière totalement cryptée contre toute interception.</p>
</div>
<div class="bg-anthracite-800/80 border border-anthracite-700 rounded-xl p-4 space-y-1.5">
<div class="flex items-center gap-2 text-cyan-400 font-bold">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 100-6 3 3 0 000 6z"/></svg>
<span>Communications Audio (SRTP)</span>
</div>
<p class="text-slate-300 leading-relaxed">Les flux voix RTP sont chiffrés avec la suite cryptographique SDES / DTLS. Même en cas de capture réseau (Wireshark), aucune conversation n'est audible.</p>
</div>
<div class="bg-anthracite-800/80 border border-anthracite-700 rounded-xl p-4 space-y-1.5">
<div class="flex items-center gap-2 text-amber-400 font-bold">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
<span>Renouvellement sans coupure</span>
</div>
<p class="text-slate-300 leading-relaxed">Les certificats Let's Encrypt sont renouvelables en 1 clic ou automatiquement. Asterisk et Nginx rechargent les certificats en mémoire sans couper les appels en cours.</p>
</div>
</div>
</section>
<!-- ========================================================================= -->
<!-- ONGLET 16 : HAUTE DISPONIBILITÉ & CLUSTER HA (ACTIF / PASSIF KEEPALIVED) -->
<!-- ========================================================================= -->
<section id="tab-cluster" class="tab-panel hidden space-y-6">
<!-- EN-TÊTE DE LA SECTION -->
<div class="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4 bg-anthracite-800 p-6 rounded-2xl border border-anthracite-700">
<div>
<div class="flex items-center gap-2 mb-1">
<span class="px-2.5 py-0.5 rounded-full text-xs font-bold bg-cyan-500/20 text-cyan-300 border border-cyan-500/30">CLUSTER HA</span>
<span id="cluster-header-state-badge" class="px-2.5 py-0.5 rounded-full text-xs font-bold bg-slate-700 text-slate-300">DÉSACTIVÉ</span>
</div>
<h2 class="text-xl font-black text-white tracking-wide">Haute Disponibilité (Cluster HA Actif / Passif)</h2>
<p class="text-xs text-slate-300 mt-1">Bascule automatique en &lt; 3 secondes par IP Virtuelle Flottante (VRRP / Keepalived) et synchronisation temps réel.</p>
</div>
<div class="flex items-center gap-2">
<button type="button" onclick="refreshClusterStatus()"
class="px-3.5 py-2 rounded-xl text-xs font-bold text-slate-300 bg-anthracite-900 border border-anthracite-700 hover:text-white hover:border-slate-500 transition flex items-center gap-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
<span>Actualiser</span>
</button>
<button type="button" onclick="triggerClusterSwitchover()" id="btn-cluster-switchover"
class="px-3.5 py-2 rounded-xl text-xs font-bold text-amber-300 bg-amber-500/10 border border-amber-500/30 hover:bg-amber-500/20 transition flex items-center gap-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4"/></svg>
<span>Bascule Manuelle (Switchover)</span>
</button>
</div>
</div>
<!-- CARTES STATISTIQUES / ÉTAT EN TEMPS RÉEL -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<!-- CARTE 1 : ÉTAT NŒUD LOCAL -->
<div class="p-4 rounded-xl bg-anthracite-800 border border-anthracite-700 space-y-2">
<div class="flex items-center justify-between text-xs text-slate-400">
<span>Nœud Local</span>
<span id="cluster-card-role-badge" class="px-2 py-0.5 rounded text-[10px] font-bold bg-slate-700 text-slate-300">MASTER</span>
</div>
<div class="flex items-baseline justify-between">
<span id="cluster-card-state" class="text-lg font-bold text-white tracking-wide">STANDALONE</span>
<span id="cluster-card-vip-indicator" class="text-[10px] font-mono px-1.5 py-0.5 rounded bg-anthracite-900 text-slate-400">VIP: Inactive</span>
</div>
<p id="cluster-card-iface" class="text-[11px] text-slate-400 font-mono">Interface : ens18</p>
</div>
<!-- CARTE 2 : ADRESSE IP VIRTUELLE (VIP) -->
<div class="p-4 rounded-xl bg-anthracite-800 border border-anthracite-700 space-y-2">
<div class="flex items-center justify-between text-xs text-slate-400">
<span>IP Virtuelle (VIP)</span>
<span class="text-[10px] font-bold text-cyan-400">VRRP ID: <span id="cluster-card-vrid">51</span></span>
</div>
<div class="text-lg font-bold font-mono text-cyan-300 tracking-wide" id="cluster-card-vip">
Non configurée
</div>
<p class="text-[11px] text-slate-400">Point de connexion des Postes &amp; Trunks</p>
</div>
<!-- CARTE 3 : NŒUD DISTANT (PEER) -->
<div class="p-4 rounded-xl bg-anthracite-800 border border-anthracite-700 space-y-2">
<div class="flex items-center justify-between text-xs text-slate-400">
<span>Nœud Distant (Pair)</span>
<span id="cluster-card-peer-status" class="px-2 py-0.5 rounded text-[10px] font-bold bg-slate-700 text-slate-300">INCONNU</span>
</div>
<div class="text-base font-bold font-mono text-white tracking-wide truncate" id="cluster-card-peer-ip">
Non configuré
</div>
<p id="cluster-card-peer-ping" class="text-[11px] text-slate-400 font-mono">Latence ping : —</p>
</div>
<!-- CARTE 4 : RÉPLICATION DES DONNÉES -->
<div class="p-4 rounded-xl bg-anthracite-800 border border-anthracite-700 space-y-2">
<div class="flex items-center justify-between text-xs text-slate-400">
<span>Synchronisation</span>
<button type="button" onclick="triggerClusterSyncNow()" class="text-[10px] text-indigo-400 hover:text-indigo-300 font-bold underline">Sync Now</button>
</div>
<div class="text-sm font-bold text-white truncate" id="cluster-card-sync-status">
Jamais synchronisé
</div>
<p id="cluster-card-sync-time" class="text-[11px] text-slate-400 font-mono">Dernier échange : —</p>
</div>
</div>
<!-- CONTENU PRINCIPAL : FORMULAIRE & ACTIONS -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- FORMULAIRE DE CONFIGURATION DU CLUSTER (2/3) -->
<div class="lg:col-span-2 bg-anthracite-800 p-6 rounded-2xl border border-anthracite-700 space-y-5">
<h3 class="text-sm font-bold text-white uppercase tracking-wider flex items-center gap-2">
<svg class="w-4 h-4 text-cyan-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4"/></svg>
<span>Paramétrage de la Haute Disponibilité (Keepalived VRRP)</span>
</h3>
<form id="form-cluster-settings" onsubmit="saveClusterSettings(event)" class="space-y-4">
<!-- SWITCH ACTIVATION -->
<div class="flex items-center justify-between p-3.5 rounded-xl bg-anthracite-900/80 border border-anthracite-700">
<div>
<label for="cluster-enabled" class="text-sm font-bold text-white cursor-pointer">Activer le Cluster Haute Disponibilité</label>
<p class="text-xs text-slate-400">Active Keepalived pour surveiller Asterisk et partager l'IP flottante.</p>
</div>
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" id="cluster-enabled" name="enabled" value="1" class="sr-only peer">
<div class="w-11 h-6 bg-anthracite-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-slate-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-cyan-500"></div>
</label>
</div>
<!-- RÔLE DU SERVEUR -->
<div class="space-y-1.5">
<label class="block text-xs font-bold text-slate-300 uppercase">Rôle initial de ce serveur</label>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
<label class="flex items-center gap-3 p-3 rounded-xl border border-anthracite-700 bg-anthracite-900/60 cursor-pointer hover:border-slate-500 transition">
<input type="radio" name="role" value="master" class="text-cyan-500 focus:ring-cyan-400" checked>
<div>
<span class="text-xs font-bold text-white">Nœud 1 : Master (Primaire)</span>
<p class="text-[10px] text-slate-400">Priorité VRRP 100 • Porte la VIP au démarrage.</p>
</div>
</label>
<label class="flex items-center gap-3 p-3 rounded-xl border border-anthracite-700 bg-anthracite-900/60 cursor-pointer hover:border-slate-500 transition">
<input type="radio" name="role" value="backup" class="text-cyan-500 focus:ring-cyan-400">
<div>
<span class="text-xs font-bold text-white">Nœud 2 : Standby (Secondaire)</span>
<p class="text-[10px] text-slate-400">Priorité VRRP 90 • Prend le relais si le Master tombe.</p>
</div>
</label>
</div>
</div>
<!-- INTERFACE RÉSEAU & IP VIRTUELLE -->
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3">
<div class="sm:col-span-1">
<label class="block text-xs font-bold text-slate-300 uppercase mb-1">Carte Réseau</label>
<select id="cluster-interface" name="interface" required
class="w-full px-3 py-2 rounded-xl bg-anthracite-900 border border-anthracite-700 text-sm text-white focus:outline-none focus:border-cyan-400 font-mono">
<option value="eth0">eth0</option>
</select>
</div>
<div class="sm:col-span-2">
<label class="block text-xs font-bold text-slate-300 uppercase mb-1">IP Virtuelle Flottante (VIP)</label>
<div class="flex gap-2">
<input type="text" id="cluster-virtual-ip" name="virtual_ip" placeholder="ex: 10.10.0.100"
class="flex-1 px-3 py-2 rounded-xl bg-anthracite-900 border border-anthracite-700 text-sm text-white focus:outline-none focus:border-cyan-400 font-mono">
<span class="flex items-center text-slate-400 font-mono text-sm">/</span>
<input type="text" id="cluster-virtual-netmask" name="virtual_netmask" value="24" placeholder="24"
class="w-16 px-2 py-2 rounded-xl bg-anthracite-900 border border-anthracite-700 text-sm text-white text-center focus:outline-none focus:border-cyan-400 font-mono">
</div>
</div>
</div>
<!-- IP DU PAIR & JETON SECRET -->
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
<div>
<label class="block text-xs font-bold text-slate-300 uppercase mb-1">IP & Port d'Administration du Pair</label>
<div class="flex gap-2">
<input type="text" id="cluster-peer-ip" name="peer_ip" placeholder="ex: 10.10.0.5"
class="flex-1 px-3 py-2 rounded-xl bg-anthracite-900 border border-anthracite-700 text-sm text-white focus:outline-none focus:border-cyan-400 font-mono">
<span class="flex items-center text-slate-400 font-mono text-sm">:</span>
<input type="number" id="cluster-peer-port" name="peer_port" value="8443" min="1" max="65535" placeholder="8443"
class="w-24 px-3 py-2 rounded-xl bg-anthracite-900 border border-anthracite-700 text-sm text-white text-center focus:outline-none focus:border-cyan-400 font-mono" title="Port HTTPS d'administration du serveur distant (ex: 8443 ou 2356)">
</div>
</div>
<div>
<label class="block text-xs font-bold text-slate-300 uppercase mb-1">Jeton de Sécurité Cluster (Secret)</label>
<div class="flex gap-1.5">
<input type="text" id="cluster-secret" name="cluster_secret" required
class="flex-1 px-3 py-2 rounded-xl bg-anthracite-900 border border-anthracite-700 text-xs text-white font-mono focus:outline-none focus:border-cyan-400">
<button type="button" onclick="regenerateClusterSecret()" title="Générer un nouveau token"
class="px-2.5 py-2 rounded-xl bg-anthracite-900 border border-anthracite-700 text-slate-300 hover:text-white transition">🎲</button>
<button type="button" onclick="copyClusterSecret()" title="Copier le token"
class="px-2.5 py-2 rounded-xl bg-anthracite-900 border border-anthracite-700 text-slate-300 hover:text-white transition">📋</button>
</div>
</div>
</div>
<!-- DÉLAI D'EXPIRATION / RÉABONNEMENT BLF (HA) -->
<div id="cluster-blf-expiry-container" class="hidden p-3.5 rounded-xl bg-anthracite-900/80 border border-cyan-500/30 space-y-1">
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
<div>
<div class="flex items-center gap-2 mb-0.5">
<label for="cluster-blf-expiry" class="text-xs font-bold text-white cursor-pointer">Fréquence de réabonnement BLF &amp; Postes</label>
<span class="px-2 py-0.2 rounded text-[10px] font-bold bg-cyan-500/20 text-cyan-300 border border-cyan-500/30">Recommandé HA : 120s</span>
</div>
<p class="text-[11px] text-slate-400">Plafond d'expiration imposé aux téléphones pour forcer le rafraîchissement rapide des voyants BLF après bascule HA (par défaut 1800s sans HA, 120s avec HA).</p>
</div>
<div class="flex items-center gap-2 shrink-0">
<input type="number" id="cluster-blf-expiry" name="blf_expiry" value="120" min="30" max="3600"
class="w-24 px-3 py-2 rounded-xl bg-anthracite-900 border border-anthracite-700 text-sm text-white text-center font-mono focus:outline-none focus:border-cyan-400">
<span class="text-xs text-slate-400 font-mono">sec</span>
</div>
</div>
</div>
<!-- OPTIONS AVANCÉES (VRID, AUTO-SYNC) -->
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 pt-2 border-t border-anthracite-700/60">
<div class="flex items-center gap-2">
<input type="checkbox" id="cluster-auto-sync" name="auto_sync" value="1" checked
class="rounded bg-anthracite-900 border-anthracite-700 text-cyan-500 focus:ring-cyan-400">
<label for="cluster-auto-sync" class="text-xs text-slate-300 cursor-pointer">Synchroniser automatiquement la base et les sons vers le pair</label>
</div>
<div class="flex items-center justify-end gap-2 text-xs text-slate-400 font-mono">
<span>Virtual Router ID (VRID) :</span>
<input type="number" id="cluster-vrid" name="vrid" value="51" min="1" max="255"
class="w-16 px-2 py-1 rounded bg-anthracite-900 border border-anthracite-700 text-white text-center text-xs">
</div>
</div>
<div class="pt-2 flex justify-end">
<button type="submit"
class="px-5 py-2.5 rounded-xl font-bold text-xs bg-cyan-500 hover:bg-cyan-400 text-slate-950 transition flex items-center gap-2 shadow-lg shadow-cyan-500/20">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>
<span>Enregistrer &amp; Déployer le Cluster Keepalived</span>
</button>
</div>
</form>
</div>
<!-- PANNEAU DROIT : LOGS & DIAGNOSTIC EN DIRECT (1/3) -->
<div class="bg-anthracite-800 p-6 rounded-2xl border border-anthracite-700 space-y-4 flex flex-col justify-between">
<div class="space-y-4">
<h3 class="text-sm font-bold text-white uppercase tracking-wider flex items-center gap-2">
<svg class="w-4 h-4 text-emerald-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
<span>Services &amp; Santé Système</span>
</h3>
<div class="space-y-2 text-xs">
<div class="flex items-center justify-between p-2.5 rounded-xl bg-anthracite-900 border border-anthracite-700/80">
<span class="text-slate-300">Démon Keepalived</span>
<span id="cluster-svc-keepalived" class="font-mono text-slate-400">Vérification...</span>
</div>
<div class="flex items-center justify-between p-2.5 rounded-xl bg-anthracite-900 border border-anthracite-700/80">
<span class="text-slate-300">Processus Asterisk</span>
<span id="cluster-svc-asterisk" class="font-mono text-slate-400">Vérification...</span>
</div>
<div class="flex items-center justify-between p-2.5 rounded-xl bg-anthracite-900 border border-anthracite-700/80">
<span class="text-slate-300">Ping Nœud Pair</span>
<span id="cluster-svc-peer" class="font-mono text-slate-400">—</span>
</div>
</div>
<div class="pt-2">
<h4 class="text-xs font-bold text-slate-300 uppercase mb-1.5 flex items-center justify-between">
<span>Journal d'Événements Cluster</span>
<span class="text-[10px] text-slate-500 font-mono">/var/log/nexus_cluster.log</span>
</h4>
<div id="cluster-logs-container"
class="h-44 overflow-y-auto p-3 rounded-xl bg-anthracite-950 border border-anthracite-700/80 font-mono text-[11px] text-slate-300 space-y-1">
<p class="text-slate-500">Aucun événement enregistré.</p>
</div>
</div>
</div>
<div class="p-3 rounded-xl bg-indigo-950/40 border border-indigo-500/30 text-[11px] text-indigo-300">
💡 <strong>Conseil d'Architecture :</strong> Vos téléphones IP (Yealink, Groundwire) et vos Trunks SIP doivent pointer sur l'adresse <strong>IP Virtuelle (VIP)</strong>.
</div>
</div>
</div>
</section>
</main>
</div>
</div>
<!-- MODALE CRUD EXTENSION SIP -->
<div id="modal-extension" class="fixed inset-0 z-50 hidden items-center justify-center bg-black/70 backdrop-blur-sm p-3 sm:p-4">
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl max-w-2xl w-full p-6 shadow-2xl max-h-[90vh] overflow-y-auto">
<div class="flex items-center justify-between mb-4">
<h3 id="modal-extension-title" class="text-base font-bold text-white">Nouveau Poste SIP</h3>
<button type="button" onclick="closeModal('modal-extension')" class="text-slate-300 hover:text-white">✕</button>
</div>
<!-- ONGLETS DE NAVIGATION DANS LA MODALE -->
<div class="flex items-center gap-2 border-b border-anthracite-700 pb-2 mb-4">
<button type="button" id="tab-btn-ext-general" onclick="switchExtModalTab('general')"
class="px-3 py-1.5 rounded-lg text-xs font-bold transition bg-indigo-600 text-white">
⚙️ Général & SIP
</button>
<button type="button" id="tab-btn-ext-voicemail" onclick="switchExtModalTab('voicemail')"
class="px-3 py-1.5 rounded-lg text-xs font-semibold transition text-slate-300 hover:text-white hover:bg-anthracite-700 flex items-center gap-1.5">
<span>📟 Messagerie Vocale</span>
<span id="badge-ext-vm-status" class="w-2 h-2 rounded-full bg-slate-500"></span>
</button>
</div>
<form id="form-extension" class="space-y-4">
<input type="hidden" name="id">
<!-- ONGLET 1 : GÉNÉRAL & SIP -->
<div id="tab-panel-ext-general" class="space-y-4">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Numéro d'extension</label>
<input type="text" name="extension" placeholder="ex: 103" required
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white font-mono">
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Nom d'affichage (CallerID)</label>
<input type="text" name="name" placeholder="ex: Comptabilité" required
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white">
</div>
</div>
<div>
<div class="flex items-center justify-between mb-1">
<label class="text-xs font-medium text-slate-200">Secret SIP (Mot de passe)</label>
<button type="button" onclick="generateRandomSecret()" class="text-xs text-indigo-300 hover:text-indigo-200 font-semibold">
Générer un secret fort
</button>
</div>
<input type="text" name="secret" required
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white font-mono">
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-2">Profil de connexion & Sécurité</label>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
<label class="flex items-start gap-3 p-3 rounded-xl bg-anthracite-950 border border-anthracite-700 cursor-pointer hover:border-indigo-400">
<input type="radio" name="transport" value="udp" checked onchange="syncEncryptionToggle(this.value)" class="mt-1">
<div>
<div class="text-xs font-bold text-white">Interne LAN (UDP)</div>
<div class="text-[11px] text-slate-300">Téléphones IP de bureau sur réseau local non chiffré.</div>
</div>
</label>
<label class="flex items-start gap-3 p-3 rounded-xl bg-anthracite-950 border border-anthracite-700 cursor-pointer hover:border-emerald-400">
<input type="radio" name="transport" value="tls" onchange="syncEncryptionToggle(this.value)" class="mt-1">
<div>
<div class="text-xs font-bold text-emerald-300">Distant (TLS + SRTP)</div>
<div class="text-[11px] text-slate-300">Télétravail / 4G chiffré via certificat TLS.</div>
</div>
</label>
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 items-center">
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Codecs autorisés</label>
<input type="text" name="codecs" value="alaw,ulaw,g722,opus" required
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white font-mono">
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Contacts simultanés max (AOR)</label>
<input type="number" name="max_contacts" value="2" min="1" max="10" required
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white">
</div>
</div>
<div class="space-y-1.5 pt-1">
<label class="block text-xs font-semibold text-slate-200">Chiffrement Média Audio (SRTP / SDES)</label>
<select id="ext-encryption" name="encryption"
class="w-full px-3 py-2 rounded-xl bg-anthracite-950 border border-anthracite-700 text-xs text-white focus:ring-2 focus:ring-indigo-500 focus:outline-none">
<option value="1">Optionnel / Négociable (Recommandé - Chiffre en SRTP si supporté, sinon RTP)</option>
<option value="2">Obligatoire / Forcé (SRTP Strict - Rejette l'appel si non chiffré)</option>
<option value="0">Désactivé (RTP standard en clair sans chiffrement audio)</option>
</select>
<p class="text-[11px] text-slate-400">Le mode optionnel négocie le SRTP automatiquement tout en évitant les échecs d'appels si le poste IP a le SRTP désactivé.</p>
</div>
<div class="p-3.5 rounded-xl bg-anthracite-950 border border-anthracite-700 space-y-2">
<div class="flex items-center justify-between">
<span class="text-xs font-bold text-cyan-300">Appartenance aux Groupes d'Extensions (Sites & Droits)</span>
<span class="text-[11px] text-slate-400">ex: Support, Direction, Commerciaux...</span>
</div>
<div id="extension-groups-checkboxes" class="grid grid-cols-1 sm:grid-cols-2 gap-2 max-h-32 overflow-y-auto"></div>
</div>
</div>
<!-- ONGLET 2 : MESSAGERIE VOCALE (VOICEMAIL) -->
<div id="tab-panel-ext-voicemail" class="hidden space-y-4">
<div class="p-4 rounded-xl bg-anthracite-950 border border-anthracite-700 flex items-center justify-between">
<div>
<div class="text-sm font-bold text-white flex items-center gap-2">
<span>Activer la boîte vocale</span>
<span class="px-2 py-0.5 rounded text-[10px] font-semibold bg-amber-500/20 text-amber-300 border border-amber-500/30">Voicemail</span>
</div>
<p class="text-xs text-slate-300 mt-0.5">Enregistre les messages vocaux des correspondants en cas de non-réponse, occupé ou DND.</p>
</div>
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" name="voicemail_enabled" id="ext-voicemail-enabled" value="1" onchange="syncVoicemailToggle(this.checked)" class="sr-only peer">
<div class="w-11 h-6 bg-anthracite-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-slate-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-amber-500"></div>
</label>
</div>
<div id="voicemail-details-container" class="space-y-4">
<!-- Configuration du mot de passe / code PIN -->
<div class="p-4 rounded-xl bg-anthracite-950 border border-anthracite-700 space-y-3">
<label class="block text-xs font-bold uppercase tracking-wider text-amber-300">Code PIN de la boîte vocale</label>
<div class="space-y-2">
<label class="flex items-start gap-3 p-3 rounded-xl bg-anthracite-900 border border-anthracite-700 cursor-pointer hover:border-amber-400">
<input type="radio" name="pin_mode" value="manual" checked onchange="syncPinMode(this.value)" class="mt-1 text-amber-500">
<div class="flex-1">
<div class="text-xs font-bold text-white">Définir un code PIN fixe maintenant</div>
<div class="text-[11px] text-slate-300">Code numérique de consultation (ex: depuis un autre poste ou l'extérieur via *98).</div>
<div id="pin-input-box" class="mt-2">
<input type="text" name="voicemail_pin" placeholder="ex: 1234 (chiffres)" pattern="[0-9]*" inputmode="numeric"
class="w-48 px-3 py-1.5 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm font-mono text-white">
</div>
</div>
</label>
<label class="flex items-start gap-3 p-3 rounded-xl bg-anthracite-900 border border-anthracite-700 cursor-pointer hover:border-amber-400">
<input type="radio" name="pin_mode" value="first_call" onchange="syncPinMode(this.value)" class="mt-1 text-amber-500">
<div>
<div class="text-xs font-bold text-amber-300">Choisir au premier appel (*97) - Assistant guidé</div>
<div class="text-[11px] text-slate-300">Au 1er appel vers sa boîte (*97), Asterisk invitera vocalement l'utilisateur à choisir son code PIN, puis à enregistrer son nom et son message d'accueil personnalisé.</div>
</div>
</label>
</div>
<input type="hidden" name="force_pin_change" id="ext-force-pin-change" value="0">
</div>
<!-- Paramètres de sonnerie et accès direct -->
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="p-3.5 rounded-xl bg-anthracite-950 border border-anthracite-700">
<label class="block text-xs font-bold text-white mb-1">Temps de sonnerie avant boîte</label>
<p class="text-[11px] text-slate-300 mb-2">Durée d'appel avant routage vers la messagerie.</p>
<div class="flex items-center gap-2">
<input type="number" name="ring_time" value="20" min="5" max="120" required
class="w-24 px-3 py-2 rounded-lg bg-anthracite-900 border border-anthracite-700 text-sm font-mono text-white">
<span class="text-xs text-slate-300">secondes (défaut: 20s)</span>
</div>
</div>
<div class="p-3.5 rounded-xl bg-anthracite-950 border border-anthracite-700">
<label class="block text-xs font-bold text-white mb-1">Accès combiné (*97)</label>
<p class="text-[11px] text-slate-300 mb-2">Comportement lors de l'appel depuis son propre poste.</p>
<label class="flex items-center gap-2 cursor-pointer mt-1">
<input type="checkbox" name="voicemail_skip_pin" id="ext-skip-pin" value="1" checked class="rounded bg-anthracite-900 border-anthracite-600 text-indigo-500">
<span class="text-xs text-white">Accès direct sans PIN depuis son poste</span>
</label>
<span class="text-[10px] text-slate-400 block mt-1">(Si décoché, le mot de passe est exigé même depuis son combiné)</span>
</div>
</div>
<!-- Notifications Email -->
<div class="p-4 rounded-xl bg-anthracite-950 border border-anthracite-700 space-y-3">
<div class="flex items-center justify-between">
<span class="text-xs font-bold uppercase tracking-wider text-indigo-300">Notification par Email (Voicemail to Email)</span>
<span class="text-[11px] text-slate-400">Optionnel</span>
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Adresse email destinataire</label>
<input type="email" name="voicemail_email" placeholder="utilisateur@societe.com"
class="w-full px-3 py-2 rounded-lg bg-anthracite-900 border border-anthracite-700 text-sm text-white">
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 pt-1">
<label class="flex items-center gap-2 cursor-pointer">
<input type="checkbox" name="voicemail_attach" id="ext-vm-attach" value="1" checked class="rounded bg-anthracite-900 border-anthracite-600 text-indigo-500">
<span class="text-xs text-slate-200">Joindre le message audio (.wav) à l'email</span>
</label>
<label class="flex items-center gap-2 cursor-pointer">
<input type="checkbox" name="voicemail_delete" id="ext-vm-delete" value="1" class="rounded bg-anthracite-900 border-anthracite-600 text-indigo-500">
<span class="text-xs text-slate-200">Supprimer de la boîte après envoi email</span>
</label>
</div>
</div>
<!-- Indicateur MWI Yealink -->
<div class="p-3 rounded-xl bg-emerald-500/10 border border-emerald-500/30 flex items-start gap-2.5">
<span class="text-base">💡</span>
<div class="text-xs text-emerald-200">
<strong class="font-bold">Notification MWI automatique :</strong>
Le voyant LED ou l'enveloppe clignoteront sur vos téléphones IP (Yealink, Fanvil, Grandstream, Snom...) dès qu'un message vocal est reçu.
</div>
</div>
</div>
</div>
<div class="pt-3 border-t border-anthracite-700 flex justify-end gap-2">
<button type="button" onclick="closeModal('modal-extension')" class="px-4 py-2 rounded-xl bg-anthracite-700 text-slate-200 text-xs font-semibold">Annuler</button>
<button type="submit" class="px-4 py-2 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-semibold">Enregistrer le poste</button>
</div>
</form>
</div>
</div>
<!-- MODALE CRUD GROUPE DE SONNERIE (RING GROUP) -->
<div id="modal-group" class="fixed inset-0 z-50 hidden items-center justify-center bg-black/75 backdrop-blur-sm p-3 sm:p-4 overflow-y-auto">
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl w-[94vw] max-w-4xl p-6 shadow-2xl my-auto">
<div class="flex items-center justify-between pb-3 mb-4 border-b border-anthracite-700">
<div>
<h3 id="modal-group-title" class="text-base sm:text-lg font-bold text-white">Groupe de Sonnerie (Ring Group)</h3>
<p class="text-xs text-slate-300">Numéro virtuel d'appel entrant avec stratégie de sonnerie et destination de débordement.</p>
</div>
<button type="button" onclick="closeModal('modal-group')" class="text-slate-300 hover:text-white text-lg">✕</button>
</div>
<form id="form-group" class="space-y-4">
<input type="hidden" name="id">
<div class="grid grid-cols-1 sm:grid-cols-4 gap-4">
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Numéro d'appel du groupe *</label>
<input type="text" name="extension" placeholder="ex: 600" required
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white font-mono">
</div>
<div class="sm:col-span-2">
<label class="block text-xs font-medium text-slate-200 mb-1">Nom du groupe * <span class="text-slate-400">(ex: Support Général, Accueil)</span></label>
<input type="text" name="name" placeholder="ex: Support Général" required
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white">
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Temps sonnerie (sec)</label>
<input type="number" name="ring_time" value="20" min="5" max="120" required
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white">
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Stratégie de sonnerie</label>
<select name="strategy" class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white">
<option value="ringall">ringall (Tous les postes du groupe en même temps)</option>
<option value="hunt">hunt (Séquentiel poste par poste)</option>
</select>
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Destination si non-réponse (Débordement)</label>
<select id="group-failover-select" name="failover_dest" class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white"></select>
</div>
</div>
<!-- Sélection Double Liste Interactive : Postes Sonnants -->
<div class="p-4 rounded-xl bg-anthracite-950 border border-anthracite-700 space-y-3">
<div class="flex items-center justify-between">
<span class="text-xs font-bold uppercase tracking-wider text-cyan-300">Postes à faire sonner (Cliquez pour inclure / retirer)</span>
<div class="flex gap-2">
<button type="button" onclick="groupTransferAllExtensions(true)" class="px-2.5 py-1 rounded-lg bg-emerald-500/15 hover:bg-emerald-500/25 border border-emerald-500/30 text-[11px] font-semibold text-emerald-300">← Tout inclure</button>
<button type="button" onclick="groupTransferAllExtensions(false)" class="px-2.5 py-1 rounded-lg bg-anthracite-800 hover:bg-anthracite-700 border border-anthracite-600 text-[11px] font-semibold text-slate-300">Tout retirer →</button>
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="p-3 rounded-xl bg-anthracite-900 border border-emerald-500/30 min-h-[130px]">
<div class="text-xs font-bold text-emerald-300 mb-2 flex items-center justify-between">
<span>✓ Postes Membres qui sonnent</span>
<span id="group-selected-count" class="px-2 py-0.2 rounded-full bg-emerald-500/20 text-[10px]">0</span>
</div>
<div id="group-members-allowed-box" class="flex flex-wrap gap-1.5"></div>
</div>
<div class="p-3 rounded-xl bg-anthracite-900 border border-anthracite-700 min-h-[130px]">
<div class="text-xs font-bold text-slate-400 mb-2 flex items-center justify-between">
<span>Postes Disponibles</span>
<span id="group-unselected-count" class="px-2 py-0.2 rounded-full bg-anthracite-800 text-[10px]">0</span>
</div>
<div id="group-members-blocked-box" class="flex flex-wrap gap-1.5"></div>
</div>
</div>
<div id="group-members-checkboxes" class="hidden"></div>
</div>
<div class="pt-3 border-t border-anthracite-700 flex justify-end gap-2">
<button type="button" onclick="closeModal('modal-group')" class="px-4 py-2 rounded-xl bg-anthracite-700 text-slate-200 text-xs font-semibold">Annuler</button>
<button type="submit" class="px-4 py-2 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-semibold">Enregistrer le groupe de sonnerie</button>
</div>
</form>
</div>
</div>
<!-- MODALE CRUD GROUPE D'EXTENSIONS (SITES & DROITS ROUTES SORTANTES) -->
<div id="modal-extgroup" class="fixed inset-0 z-50 hidden items-center justify-center bg-black/75 backdrop-blur-sm p-3 sm:p-4 overflow-y-auto">
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl w-[94vw] max-w-4xl p-6 shadow-2xl my-auto">
<div class="flex items-center justify-between pb-3 mb-4 border-b border-anthracite-700">
<div>
<h3 id="modal-extgroup-title" class="text-base sm:text-lg font-bold text-white">Groupe d'Extensions (Sites & Droits)</h3>
<p class="text-xs text-slate-300">Regroupement sans numéro d'appel. Sert à autoriser ou bloquer ce groupe sur les Routes Sortantes (ex: Trunk SIP, Passerelle...).</p>
</div>
<button type="button" onclick="closeModal('modal-extgroup')" class="text-slate-300 hover:text-white text-lg">✕</button>
</div>
<form id="form-extgroup" class="space-y-4">
<input type="hidden" name="id">
<div class="grid grid-cols-1 sm:grid-cols-12 gap-4">
<div class="sm:col-span-6">
<label class="block text-xs font-medium text-slate-200 mb-1">Nom du Groupe * <span class="text-slate-400">(ex: Groupe Direction, Groupe Support)</span></label>
<input type="text" name="name" placeholder="ex: Groupe Support" required
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white font-bold">
</div>
<div class="sm:col-span-6">
<label class="block text-xs font-medium text-slate-200 mb-1">Description / Emplacement</label>
<input type="text" name="description" placeholder="ex: Équipe Commerciale - Sortie Prioritaire"
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white">
</div>
</div>
<!-- Double liste interactive pour affecter les membres au Groupe d'Extensions -->
<div class="p-4 rounded-xl bg-anthracite-950 border border-cyan-500/40 space-y-3">
<div class="flex items-center justify-between">
<span class="text-xs font-bold uppercase tracking-wider text-cyan-300">Extensions rattachées à ce groupe (Cliquez pour ajouter/retirer)</span>
<div class="flex gap-2">
<button type="button" onclick="extGroupTransferAll(true)" class="px-2.5 py-1 rounded-lg bg-cyan-500/15 hover:bg-cyan-500/25 border border-cyan-500/30 text-[11px] font-semibold text-cyan-300">← Tout ajouter</button>
<button type="button" onclick="extGroupTransferAll(false)" class="px-2.5 py-1 rounded-lg bg-anthracite-800 hover:bg-anthracite-700 border border-anthracite-600 text-[11px] font-semibold text-slate-300">Tout retirer →</button>
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="p-3 rounded-xl bg-anthracite-900 border border-cyan-500/40 min-h-[140px]">
<div class="text-xs font-bold text-cyan-300 mb-2 flex items-center justify-between">
<span>✓ Extensions membres de ce groupe</span>
<span id="extgroup-selected-count" class="px-2 py-0.2 rounded-full bg-cyan-500/20 text-[10px]">0</span>
</div>
<div id="extgroup-members-allowed-box" class="flex flex-wrap gap-1.5"></div>
</div>
<div class="p-3 rounded-xl bg-anthracite-900 border border-anthracite-700 min-h-[140px]">
<div class="text-xs font-bold text-slate-400 mb-2 flex items-center justify-between">
<span>Autres extensions</span>
<span id="extgroup-unselected-count" class="px-2 py-0.2 rounded-full bg-anthracite-800 text-[10px]">0</span>
</div>
<div id="extgroup-members-blocked-box" class="flex flex-wrap gap-1.5"></div>
</div>
</div>
</div>
<div class="pt-3 border-t border-anthracite-700 flex justify-end gap-2">
<button type="button" onclick="closeModal('modal-extgroup')" class="px-4 py-2 rounded-xl bg-anthracite-700 text-slate-200 text-xs font-semibold">Annuler</button>
<button type="submit" class="px-4 py-2 rounded-xl bg-cyan-600 hover:bg-cyan-500 text-white text-xs font-semibold">Enregistrer le groupe d'extensions</button>
</div>
</form>
</div>
</div>
<!-- MODALE CRUD GROUPE DE TÉLÉAVERTISSEMENT & INTERPHONIE (PAGING) -->
<div id="modal-paging" class="fixed inset-0 z-50 hidden items-center justify-center bg-black/75 backdrop-blur-sm p-3 sm:p-4 overflow-y-auto">
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl w-[94vw] max-w-4xl p-6 shadow-2xl my-auto">
<div class="flex items-center justify-between pb-3 mb-4 border-b border-anthracite-700">
<div>
<h3 id="modal-paging-title" class="text-base sm:text-lg font-bold text-white">Groupe de Téléavertissement (Paging / Intercom)</h3>
<p class="text-xs text-slate-300">Diffusion d'annonces vocales directes avec auto-décroché immédiat sur haut-parleur.</p>
</div>
<button type="button" onclick="closeModal('modal-paging')" class="text-slate-300 hover:text-white text-lg">✕</button>
</div>
<form id="form-paging" class="space-y-4">
<input type="hidden" name="id">
<div class="grid grid-cols-1 sm:grid-cols-12 gap-4">
<div class="sm:col-span-3">
<label class="block text-xs font-medium text-slate-200 mb-1">Numéro d'appel (Extension) *</label>
<input type="text" name="extension" placeholder="ex: 100" required
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-purple-300 font-mono font-bold">
</div>
<div class="sm:col-span-5">
<label class="block text-xs font-medium text-slate-200 mb-1">Nom du groupe *</label>
<input type="text" name="name" placeholder="ex: Téléavertissement Général, Atelier..." required
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white">
</div>
<div class="sm:col-span-4">
<label class="block text-xs font-medium text-slate-200 mb-1">Description (optionnel)</label>
<input type="text" name="description" placeholder="ex: Annonce vocale globale"
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white">
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Mode Audio</label>
<select name="duplex" class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white">
<option value="0">Simplex (Haut-parleur seul / Micro coupé)</option>
<option value="1">Duplex (Interphonie 2 voies / Conversation)</option>
</select>
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Gestion des Postes Occupés</label>
<select name="busy_mode" class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white">
<option value="skip">Ignorer si le poste est en communication</option>
<option value="force">Forcer l'annonce même si en ligne</option>
</select>
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Durée maximale d'annonce (sec)</label>
<input type="number" name="timeout" value="120" min="10" max="600" required
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white">
</div>
</div>
<div class="flex flex-wrap items-center gap-6 p-3 rounded-xl bg-anthracite-950 border border-anthracite-700">
<label class="flex items-center gap-2 cursor-pointer">
<input type="checkbox" name="play_beep" value="1" checked class="w-4 h-4 rounded text-purple-600 focus:ring-purple-500 bg-anthracite-900 border-anthracite-700">
<span class="text-xs font-medium text-slate-200">Émettre un bip sonore d'avertissement avant la prise de parole</span>
</label>
<label class="flex items-center gap-2 cursor-pointer">
<input type="checkbox" name="enabled" value="1" checked class="w-4 h-4 rounded text-emerald-600 focus:ring-emerald-500 bg-anthracite-900 border-anthracite-700">
<span class="text-xs font-medium text-slate-200">Activer ce groupe de téléavertissement</span>
</label>
</div>
<!-- Sélection Double Liste Interactive : Postes qui reçoivent l'annonce -->
<div class="p-4 rounded-xl bg-anthracite-950 border border-purple-500/40 space-y-3">
<div class="flex items-center justify-between">
<span class="text-xs font-bold uppercase tracking-wider text-purple-300">Postes membres qui recevront l'annonce (Cliquez pour inclure / retirer)</span>
<div class="flex gap-2">
<button type="button" onclick="pagingTransferAllExtensions(true)" class="px-2.5 py-1 rounded-lg bg-purple-500/15 hover:bg-purple-500/25 border border-purple-500/30 text-[11px] font-semibold text-purple-300">← Tout inclure</button>
<button type="button" onclick="pagingTransferAllExtensions(false)" class="px-2.5 py-1 rounded-lg bg-anthracite-800 hover:bg-anthracite-700 border border-anthracite-600 text-[11px] font-semibold text-slate-300">Tout retirer →</button>
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="p-3 rounded-xl bg-anthracite-900 border border-purple-500/30 min-h-[130px]">
<div class="text-xs font-bold text-purple-300 mb-2 flex items-center justify-between">
<span>✓ Postes ciblés par le téléavertissement</span>
<span id="paging-selected-count" class="px-2 py-0.2 rounded-full bg-purple-500/20 text-[10px]">0</span>
</div>
<div id="paging-members-allowed-box" class="flex flex-wrap gap-1.5"></div>
</div>
<div class="p-3 rounded-xl bg-anthracite-900 border border-anthracite-700 min-h-[130px]">
<div class="text-xs font-bold text-slate-400 mb-2 flex items-center justify-between">
<span>Autres postes disponibles</span>
<span id="paging-unselected-count" class="px-2 py-0.2 rounded-full bg-anthracite-800 text-[10px]">0</span>
</div>
<div id="paging-members-blocked-box" class="flex flex-wrap gap-1.5"></div>
</div>
</div>
<div id="paging-members-checkboxes" class="hidden"></div>
</div>
<div class="pt-3 border-t border-anthracite-700 flex justify-end gap-2">
<button type="button" onclick="closeModal('modal-paging')" class="px-4 py-2 rounded-xl bg-anthracite-700 text-slate-200 text-xs font-semibold">Annuler</button>
<button type="submit" class="px-4 py-2 rounded-xl bg-purple-600 hover:bg-purple-500 text-white text-xs font-semibold">Enregistrer le groupe de téléavertissement</button>
</div>
</form>
</div>
</div>
<!-- MODALE CRUD ROUTE SORTANTE (OUTBOUND ROUTE : TRUNK SEQUENCE, DIAL PATTERNS, EXTENSION & GROUP ROUTING) -->
<div id="modal-outbound" class="fixed inset-0 z-50 hidden items-center justify-center bg-black/75 backdrop-blur-sm p-2 sm:p-4 overflow-y-auto">
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl w-[96vw] max-w-[1380px] p-5 sm:p-7 shadow-2xl my-auto max-h-[94vh] flex flex-col">
<div class="flex flex-wrap items-center justify-between gap-3 pb-4 border-b border-anthracite-700">
<div>
<h3 id="modal-outbound-title" class="text-base sm:text-lg font-bold text-white">Éditer la Route Sortante</h3>
<p class="text-xs text-slate-300">Configurez la séquence de Trunks, les masques de numérotation (Dial Patterns) et les Groupes / Postes autorisés à utiliser cette route.</p>
</div>
<button type="button" onclick="closeModal('modal-outbound')" class="text-slate-300 hover:text-white p-1 text-lg">✕</button>
</div>
<!-- Onglets de la modale Route Sortante (comme sur tes captures) -->
<div class="flex flex-wrap gap-1 pt-3 border-b border-anthracite-700">
<button type="button" data-ob-tab="settings" onclick="switchOutboundModalTab('settings')"
class="ob-tab-btn px-4 py-2 text-xs font-bold rounded-t-xl bg-indigo-600 text-white transition">
1. Route Settings & Séquence Trunks
</button>
<button type="button" data-ob-tab="patterns" onclick="switchOutboundModalTab('patterns')"
class="ob-tab-btn px-4 py-2 text-xs font-semibold rounded-t-xl bg-anthracite-950 text-emerald-300 hover:text-white transition flex items-center gap-1.5">
<span>2. Dial Patterns (Masques de numérotation)</span>
<span id="ob-patterns-count-badge" class="px-1.5 py-0.2 rounded-full text-[10px] font-bold bg-emerald-500/20 text-emerald-300 border border-emerald-500/30">0</span>
</button>
<button type="button" data-ob-tab="permissions" onclick="switchOutboundModalTab('permissions')"
class="ob-tab-btn px-4 py-2 text-xs font-semibold rounded-t-xl bg-anthracite-950 text-cyan-300 hover:text-white transition flex items-center gap-1.5">
<span>3. Paramètres complémentaires & Droits (Groupes & Postes)</span>
</button>
</div>
<form id="form-outbound" class="flex-1 overflow-y-auto py-4 space-y-4 pr-1">
<input type="hidden" name="id">
<!-- ONGLET 1 : ROUTE SETTINGS & SÉQUENCE TRUNKS -->
<div id="ob-panel-settings" class="ob-subpanel space-y-4">
<div class="grid grid-cols-1 sm:grid-cols-12 gap-4">
<div class="sm:col-span-4">
<label class="block text-xs font-medium text-slate-200 mb-1">Nom de la Route * <span class="text-slate-400">(ex: National, Mobile, International, Secours)</span></label>
<input type="text" name="name" placeholder="ex: National" required
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white font-semibold">
</div>
<div class="sm:col-span-5">
<label class="block text-xs font-medium text-slate-200 mb-1">Route CID <span class="text-slate-400">(CallerID Sortant affiché)</span></label>
<input type="text" name="outbound_callerid" placeholder="ex: Mon Entreprise <0123456789>"
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white font-mono">
</div>
<div class="sm:col-span-3">
<label class="block text-xs font-medium text-slate-200 mb-1">Ordre de Priorité *</label>
<input type="number" name="priority" value="1" min="1" max="999" required
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white font-mono">
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4 p-4 rounded-xl bg-anthracite-950 border border-anthracite-700">
<div>
<label class="block text-xs font-medium text-slate-200 mb-1.5">Remplacer le CID du poste <span class="text-slate-400">(Override Extension)</span></label>
<div class="inline-flex rounded-lg bg-anthracite-900 p-1 border border-anthracite-700">
<label class="cursor-pointer">
<input type="radio" name="override_ext_cid" value="1" class="peer hidden">
<span class="px-3 py-1 rounded-md text-xs font-semibold text-slate-300 peer-checked:bg-emerald-600 peer-checked:text-white transition inline-block">Oui</span>
</label>
<label class="cursor-pointer">
<input type="radio" name="override_ext_cid" value="0" checked class="peer hidden">
<span class="px-3 py-1 rounded-md text-xs font-semibold text-slate-300 peer-checked:bg-emerald-600 peer-checked:text-white transition inline-block">Non</span>
</label>
</div>
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1.5">Route Type <span class="text-slate-400">(Profil de la route)</span></label>
<div class="inline-flex rounded-lg bg-anthracite-900 p-1 border border-anthracite-700">
<label class="cursor-pointer">
<input type="radio" name="route_type" value="normal" checked class="peer hidden">
<span class="px-2.5 py-1 rounded-md text-xs font-semibold text-slate-300 peer-checked:bg-indigo-600 peer-checked:text-white transition inline-block">Standard</span>
</label>
<label class="cursor-pointer">
<input type="radio" name="route_type" value="emergency" class="peer hidden">
<span class="px-2.5 py-1 rounded-md text-xs font-semibold text-slate-300 peer-checked:bg-emerald-600 peer-checked:text-white transition inline-block">🚑 Urgence</span>
</label>
<label class="cursor-pointer">
<input type="radio" name="route_type" value="intracompany" class="peer hidden">
<span class="px-2.5 py-1 rounded-md text-xs font-semibold text-slate-300 peer-checked:bg-cyan-600 peer-checked:text-white transition inline-block">🏢 Intra-Company</span>
</label>
</div>
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Mot de Passe de la Route <span class="text-slate-400">(PIN optionnel)</span></label>
<input type="text" name="route_password" placeholder="Vide = sans code PIN"
class="w-full px-3 py-1.5 rounded-lg bg-anthracite-900 border border-anthracite-700 text-xs text-white font-mono">
</div>
</div>
<!-- TRUNK SEQUENCE FOR MATCHED ROUTES -->
<div class="p-4 rounded-xl bg-anthracite-950 border border-indigo-500/40 space-y-3">
<div class="flex flex-wrap items-center justify-between gap-2">
<div>
<div class="text-xs font-bold uppercase tracking-wider text-indigo-300">Trunk Sequence for Matched Routes (Ordre des Trunks à utiliser)</div>
<p class="text-[11px] text-slate-300">
Choisissez un ou plusieurs Trunks par ordre de priorité (ex: <strong>1. ovh-telecom</strong> puis <strong>2. livebox</strong>). Si le 1er Trunk est indisponible ou occupé, l'appel bascule automatiquement sur le Trunk suivant !
</p>
</div>
<button type="button" onclick="addOutboundTrunkSeqRow()"
class="px-3 py-1.5 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-semibold transition">
+ Ajouter un Trunk de secours
</button>
</div>
<div id="ob-trunk-seq-container" class="space-y-2"></div>
</div>
<!-- COMPORTEMENT DE CASCADE, CONGESTION & MUSIQUE D'ATTENTE -->
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4 p-4 rounded-xl bg-anthracite-950 border border-anthracite-700">
<div class="flex flex-col justify-center">
<label class="flex items-start gap-2.5 text-xs text-slate-200 cursor-pointer">
<input type="checkbox" name="continue_next_route" value="1" checked class="mt-0.5 rounded bg-anthracite-900 border-anthracite-600">
<div>
<div class="font-bold text-emerald-300">Cascade vers route suivante</div>
<div class="text-[11px] text-slate-400">Si poste non autorisé ou Trunks indisponibles, passe à la route suivante.</div>
</div>
</label>
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">🎵 Musique d'attente (MoH)</label>
<select id="ob-musicclass-select" name="moh_class" class="w-full px-3 py-2 rounded-lg bg-anthracite-900 border border-anthracite-700 text-xs text-white"></select>
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">📢 Si Trunks indisponibles <span class="text-slate-400">(Échec / Congestion)</span></label>
<select id="ob-congestion-dest-select" name="congestion_dest" class="w-full px-3 py-2 rounded-lg bg-anthracite-900 border border-anthracite-700 text-xs text-white"></select>
</div>
</div>
</div>
<!-- ONGLET 2 : DIAL PATTERNS (MASQUES DE NUMÉROTATION) -->
<div id="ob-panel-patterns" class="ob-subpanel hidden space-y-4">
<div class="p-4 rounded-xl bg-anthracite-950 border border-emerald-500/30 space-y-3">
<div class="flex flex-wrap items-center justify-between gap-2">
<div>
<div class="text-xs font-bold uppercase tracking-wider text-emerald-300">🪄 Masques de numérotation pré-définis (1 clic)</div>
<p class="text-[11px] text-slate-300">Ajoutez instantanément les masques standards ou créez vos propres règles <code class="text-indigo-300">(prepend) + préfixe | [match pattern / ID appelant]</code>.</p>
</div>
<button type="button" onclick="addOutboundPatternRow()"
class="px-3.5 py-1.5 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-bold transition">
+ Ajouter une ligne de masque
</button>
</div>
<div class="flex flex-wrap gap-2 pt-1">
<button type="button" onclick="applyOutboundPatternPreset('fr_national')"
class="px-3 py-1.5 rounded-lg bg-anthracite-900 hover:bg-emerald-500/20 border border-anthracite-600 hover:border-emerald-500/40 text-xs font-semibold text-emerald-300 transition">
🇫🇷 Fixes & Mobiles France (0[1-59].. & 0[67]..)
</button>
<button type="button" onclick="applyOutboundPatternPreset('fr_free')"
class="px-3 py-1.5 rounded-lg bg-anthracite-900 hover:bg-emerald-500/20 border border-anthracite-600 hover:border-emerald-500/40 text-xs font-semibold text-cyan-300 transition">
🟢 Numéros Verts / Gratuits (080[0-9]XXXXXX)
</button>
<button type="button" onclick="applyOutboundPatternPreset('fr_emergency')"
class="px-3 py-1.5 rounded-lg bg-anthracite-900 hover:bg-amber-500/20 border border-anthracite-600 hover:border-amber-500/40 text-xs font-semibold text-amber-300 transition">
🚑 Numéros d'Urgence (15, 17, 18, 112, 114)
</button>
<button type="button" onclick="applyOutboundPatternPreset('international')"
class="px-3 py-1.5 rounded-lg bg-anthracite-900 hover:bg-indigo-500/20 border border-anthracite-600 hover:border-indigo-500/40 text-xs font-semibold text-indigo-300 transition">
🌍 International (00. & +X.)
</button>
<button type="button" onclick="applyOutboundPatternPreset('all')"
class="px-3 py-1.5 rounded-lg bg-anthracite-900 hover:bg-purple-500/20 border border-anthracite-600 hover:border-purple-500/40 text-xs font-semibold text-purple-300 transition">
⭐ Tout autoriser (X.)
</button>
</div>
</div>
<div class="rounded-xl border border-anthracite-700 overflow-hidden bg-anthracite-900">
<div class="grid grid-cols-12 gap-2 px-4 py-2.5 bg-anthracite-950 border-b border-anthracite-700 text-[11px] font-bold uppercase tracking-wider text-slate-300">
<div class="col-span-2">( Prepend )</div>
<div class="col-span-2">Préfixe |</div>
<div class="col-span-5">[ Match Pattern (Masque appelé) *</div>
<div class="col-span-2">/ ID Appelant ]</div>
<div class="col-span-1 text-right">Actions</div>
</div>
<div id="ob-patterns-container" class="divide-y divide-anthracite-700/60 max-h-[360px] overflow-y-auto"></div>
</div>
</div>
<!-- ONGLET 3 : PARAMÈTRES COMPLÉMENTAIRES & DROITS (GROUPES & POSTES AUTORISÉS) -->
<div id="ob-panel-permissions" class="ob-subpanel hidden space-y-4">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 p-4 rounded-xl bg-anthracite-950 border border-anthracite-700">
<div>
<label class="block text-xs font-medium text-slate-200 mb-1.5">Enregistrer l'appel</label>
<div class="inline-flex rounded-lg bg-anthracite-900 p-1 border border-anthracite-700 flex-wrap gap-1">
<label class="cursor-pointer"><input type="radio" name="record_mode" value="force" class="peer hidden"><span class="px-2.5 py-1 rounded text-xs font-semibold text-slate-300 peer-checked:bg-emerald-600 peer-checked:text-white inline-block">Forcer</span></label>
<label class="cursor-pointer"><input type="radio" name="record_mode" value="yes" class="peer hidden"><span class="px-2.5 py-1 rounded text-xs font-semibold text-slate-300 peer-checked:bg-emerald-600 peer-checked:text-white inline-block">Oui</span></label>
<label class="cursor-pointer"><input type="radio" name="record_mode" value="dontcare" checked class="peer hidden"><span class="px-2.5 py-1 rounded text-xs font-semibold text-slate-300 peer-checked:bg-emerald-600 peer-checked:text-white inline-block">Peu importe</span></label>
<label class="cursor-pointer"><input type="radio" name="record_mode" value="no" class="peer hidden"><span class="px-2.5 py-1 rounded text-xs font-semibold text-slate-300 peer-checked:bg-emerald-600 peer-checked:text-white inline-block">Non</span></label>
<label class="cursor-pointer"><input type="radio" name="record_mode" value="never" class="peer hidden"><span class="px-2.5 py-1 rounded text-xs font-semibold text-slate-300 peer-checked:bg-emerald-600 peer-checked:text-white inline-block">Jamais</span></label>
</div>
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1.5">Contrôle des Droits d'utilisation de la Route</label>
<div class="flex flex-col gap-1.5 text-xs">
<label class="flex items-center gap-2 cursor-pointer">
<input type="radio" name="permission_mode" value="all" checked onchange="toggleOutboundPermissionMode(this.value)">
<span class="text-white font-semibold">Tous les postes et groupes sont autorisés</span>
</label>
<label class="flex items-center gap-2 cursor-pointer">
<input type="radio" name="permission_mode" value="restricted" onchange="toggleOutboundPermissionMode(this.value)">
<span class="text-cyan-300 font-semibold">Restreindre aux Groupes (ex: Nice, Paris) et/ou Postes sélectionnés ci-dessous</span>
</label>
</div>
</div>
</div>
<div id="ob-permissions-details" class="space-y-4">
<!-- 1. SÉLECTION DES GROUPES D'EXTENSIONS AUTORISÉS (EX: GROUPE NICE, GROUPE PARIS) -->
<div class="p-4 rounded-xl bg-anthracite-950 border border-cyan-500/40 space-y-3">
<div class="flex flex-wrap items-center justify-between gap-2">
<div>
<div class="text-xs font-bold uppercase tracking-wider text-cyan-300">👥 Groupes d'Extensions Autorisés (ex: Groupe Nice, Groupe Paris)</div>
<p class="text-[11px] text-slate-300">Tous les postes rattachés à un groupe d'extensions coché ci-dessous auront le droit d'appeler via cette route sortante.</p>
</div>
<button type="button" onclick="closeModal('modal-outbound'); switchTab('extgroups'); openExtGroupModal();"
class="px-3 py-1 rounded-lg bg-cyan-500/15 hover:bg-cyan-500/25 border border-cyan-500/30 text-cyan-300 text-xs font-semibold transition">
+ Créer / Gérer les Groupes d'Extensions
</button>
</div>
<div id="ob-allowed-groups-container" class="grid grid-cols-1 sm:grid-cols-3 gap-2.5"></div>
</div>
<!-- 2. EXTENSION ROUTING (ALLOWED EXTENSIONS <-> BLOCKED EXTENSIONS COMME SUR LA CAPTURE 4) -->
<div class="p-4 rounded-xl bg-anthracite-950 border border-anthracite-700 space-y-3">
<div class="flex flex-wrap items-center justify-between gap-2">
<div>
<div class="text-xs font-bold uppercase tracking-wider text-emerald-300">📱 Extension Routing (Postes individuels Autorisés ↔ Bloqués)</div>
<p class="text-[11px] text-slate-300">Cliquez sur un poste pour le déplacer entre <strong>Allowed Extensions</strong> et <strong>Blocked Extensions</strong>. Si un poste est bloqué ici, l'appel passera automatiquement à la Route Sortante suivante.</p>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-11 gap-3 items-center">
<!-- Allowed Extensions -->
<div class="lg:col-span-5 p-3.5 rounded-xl bg-anthracite-900 border border-emerald-500/40 min-h-[180px]">
<div class="text-xs font-bold text-emerald-300 mb-2.5 flex items-center justify-between">
<span>Allowed Extensions (Postes Autorisés)</span>
<span id="ob-allowed-exts-count" class="px-2 py-0.5 rounded-full bg-emerald-500/20 text-[10px]">0</span>
</div>
<div id="ob-allowed-exts-box" class="flex flex-wrap gap-2"></div>
</div>
<!-- Boutons centraux ← ↔ → -->
<div class="lg:col-span-1 flex lg:flex-col items-center justify-center gap-2">
<button type="button" onclick="obTransferExtensions('all_allow')" title="Autoriser tous les postes"
class="px-3 py-1.5 rounded-lg bg-emerald-500/20 hover:bg-emerald-500/30 border border-emerald-500/40 text-emerald-300 font-bold text-xs">←</button>
<button type="button" onclick="obTransferExtensions('invert')" title="Inverser Autorisés / Bloqués"
class="px-3 py-1.5 rounded-lg bg-anthracite-800 hover:bg-anthracite-700 border border-anthracite-600 text-slate-200 font-bold text-xs">↔</button>
<button type="button" onclick="obTransferExtensions('all_block')" title="Bloquer tous les postes individuels"
class="px-3 py-1.5 rounded-lg bg-amber-500/20 hover:bg-amber-500/30 border border-amber-500/40 text-amber-300 font-bold text-xs">→</button>
</div>
<!-- Blocked Extensions -->
<div class="lg:col-span-5 p-3.5 rounded-xl bg-anthracite-900 border border-anthracite-700 min-h-[180px]">
<div class="text-xs font-bold text-slate-300 mb-2.5 flex items-center justify-between">
<span>Blocked Extensions (Non autorisés sur cette route)</span>
<span id="ob-blocked-exts-count" class="px-2 py-0.5 rounded-full bg-anthracite-800 text-[10px]">0</span>
</div>
<div id="ob-blocked-exts-box" class="flex flex-wrap gap-2"></div>
</div>
</div>
</div>
</div>
</div>
<div class="pt-4 border-t border-anthracite-700 flex items-center justify-between">
<label class="flex items-center gap-2 text-xs text-slate-200 cursor-pointer">
<input type="checkbox" name="enabled" value="1" checked class="rounded bg-anthracite-950 border-anthracite-600">
<span>Route Sortante active dans le Dialplan</span>
</label>
<div class="flex gap-2">
<button type="button" onclick="closeModal('modal-outbound')" class="px-4 py-2 rounded-xl bg-anthracite-700 text-slate-200 text-xs font-semibold">Annuler</button>
<button type="submit" class="px-5 py-2 rounded-xl bg-emerald-600 hover:bg-emerald-500 text-white text-xs font-semibold shadow-lg shadow-emerald-600/20">Soumettre & Enregistrer la Route</button>
</div>
</div>
</form>
</div>
</div>
<!-- MODALE CRUD TRUNK SIP (CONFIGURATION PJSIP COMPLÈTE) -->
<div id="modal-trunk" class="fixed inset-0 z-50 hidden items-center justify-center bg-black/75 backdrop-blur-sm p-2 sm:p-4 overflow-y-auto">
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl w-[96vw] max-w-[1380px] p-5 sm:p-7 shadow-2xl my-auto max-h-[94vh] flex flex-col">
<div class="flex flex-wrap items-center justify-between gap-3 pb-4 border-b border-anthracite-700">
<div>
<h3 id="modal-trunk-title" class="text-base sm:text-lg font-bold text-white">Configuration Complète Trunk SIP / PJSIP Universel</h3>
<p class="text-xs text-slate-300">Compatible avec tous les opérateurs SIP, SBC, passerelles RNIS/FXO et interconnexions IP (50 paramètres PJSIP natifs).</p>
</div>
<button type="button" onclick="closeModal('modal-trunk')" class="text-slate-300 hover:text-white p-1 text-lg">✕</button>
</div>
<!-- Architectures PJSIP universelles (sans écraser les serveurs) + Convertisseur FreePBX -->
<div class="py-3 border-b border-anthracite-700/80 flex flex-wrap items-center justify-between gap-2 text-xs">
<div class="flex flex-wrap items-center gap-1.5">
<span class="text-slate-300 font-semibold mr-1">Architecture PJSIP :</span>
<button type="button" onclick="applyTrunkArchitecture('register_auth')" class="px-2.5 py-1 rounded-lg bg-anthracite-950 hover:bg-indigo-600/30 border border-anthracite-600 text-indigo-300 font-medium transition">
Trunk avec Enregistrement (REGISTER + Auth)
</button>
<button type="button" onclick="applyTrunkArchitecture('gateway_receive')" class="px-2.5 py-1 rounded-lg bg-anthracite-950 hover:bg-emerald-600/30 border border-emerald-500/50 text-emerald-300 font-medium transition flex items-center gap-1">
<span>📟 Passerelle FXO / Ligne Box (Grandstream, Cisco SPA, AudioCodes)</span>
</button>
<button type="button" onclick="applyTrunkArchitecture('ip_peering')" class="px-2.5 py-1 rounded-lg bg-anthracite-950 hover:bg-indigo-600/30 border border-anthracite-600 text-purple-300 font-medium transition">
Peering IP / SBC / Passerelle (Sans REGISTER)
</button>
<button type="button" onclick="applyTrunkArchitecture('tls_srtp')" class="px-2.5 py-1 rounded-lg bg-anthracite-950 hover:bg-indigo-600/30 border border-anthracite-600 text-cyan-300 font-medium transition">
Trunk Sécurisé (TLS 5061 + SRTP)
</button>
</div>
<button type="button" onclick="toggleFreePbxImporter()"
class="px-3 py-1 rounded-lg bg-emerald-500/20 hover:bg-emerald-500/30 border border-emerald-500/40 text-emerald-300 font-semibold transition">
Coller depuis FreePBX / Register String
</button>
</div>
<!-- Convertisseur FreePBX caché par défaut -->
<div id="freepbx-importer-box" class="hidden my-2 p-3.5 rounded-xl bg-anthracite-950 border border-emerald-500/40 space-y-2">
<div class="flex items-center justify-between">
<span class="text-xs font-bold text-emerald-300">Convertisseur automatique FreePBX / chan_sip → PJSIP</span>
<button type="button" onclick="toggleFreePbxImporter()" class="text-xs text-slate-400 hover:text-white">Fermer</button>
</div>
<p class="text-[11px] text-slate-300">
Collez votre <strong>Register String</strong> (<code class="text-indigo-300">user:secret@host:port/did</code>) ou vos <strong>PEER Details FreePBX</strong> (<code class="text-indigo-300">host=... username=... secret=... context=...</code>) :
</p>
<textarea id="freepbx-paste-input" rows="3" placeholder="user:secret@sip.operateur.com:5060/num&#10;ou&#10;host=sip.operateur.com&#10;username=mon_user&#10;secret=mon_secret&#10;context=from-pstn-toheader"
class="w-full px-3 py-2 rounded-lg bg-anthracite-900 border border-anthracite-700 text-xs text-white font-mono"></textarea>
<div class="flex justify-end">
<button type="button" onclick="parseFreePbxConfig()"
class="px-3.5 py-1.5 rounded-lg bg-emerald-600 hover:bg-emerald-500 text-white text-xs font-semibold">
Analyser & Remplir les champs PJSIP
</button>
</div>
</div>
<!-- Sous-onglets de la modale Trunk -->
<div class="flex flex-wrap gap-1 pt-3 border-b border-anthracite-700">
<button type="button" data-trunk-tab="general" onclick="switchTrunkModalTab('general')"
class="trunk-tab-btn px-3.5 py-2 text-xs font-bold rounded-t-xl bg-indigo-600 text-white transition">
1. Serveur & Auth SIP
</button>
<button type="button" data-trunk-tab="inbound" onclick="switchTrunkModalTab('inbound')"
class="trunk-tab-btn px-3.5 py-2 text-xs font-semibold rounded-t-xl bg-anthracite-950 text-emerald-300 hover:text-white transition flex items-center gap-1.5">
<span>2. Routes Entrantes & Filtrage ID Appelant</span>
<span id="trunk-inbound-rules-badge" class="px-1.5 py-0.2 rounded-full text-[10px] font-bold bg-emerald-500/20 text-emerald-300 border border-emerald-500/30">0</span>
</button>
<button type="button" data-trunk-tab="identity" onclick="switchTrunkModalTab('identity')"
class="trunk-tab-btn px-3.5 py-2 text-xs font-semibold rounded-t-xl bg-anthracite-950 text-slate-300 hover:text-white transition">
3. Registration & Identité SIP (From / PAI / Diversion)
</button>
<button type="button" data-trunk-tab="media" onclick="switchTrunkModalTab('media')"
class="trunk-tab-btn px-3.5 py-2 text-xs font-semibold rounded-t-xl bg-anthracite-950 text-slate-300 hover:text-white transition">
4. Codecs, NAT, Timers, T.38 & Identify IP
</button>
<button type="button" data-trunk-tab="routing" onclick="switchTrunkModalTab('routing')"
class="trunk-tab-btn px-3.5 py-2 text-xs font-semibold rounded-t-xl bg-anthracite-950 text-slate-300 hover:text-white transition">
5. Directives PJSIP Brutes
</button>
</div>
<form id="form-trunk" class="flex-1 overflow-y-auto py-4 space-y-4 pr-1">
<input type="hidden" name="id">
<!-- SOUS-ONGLET 1 : SERVEUR, AUTHENTIFICATION & ROUTAGE DIRECT -->
<div id="trunk-panel-general" class="trunk-subpanel space-y-4">
<!-- Mode d'Enregistrement SIP (Send / Receive / None) -->
<div class="p-4 rounded-xl bg-anthracite-950 border border-anthracite-700 space-y-3">
<div class="flex flex-wrap items-center justify-between gap-2">
<span class="text-xs font-bold uppercase tracking-wider text-emerald-300">Mode d'Enregistrement SIP (Registration)</span>
<span class="text-[11px] text-slate-400">Définit le sens de l'enregistrement ou le mode peering</span>
</div>
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3 text-xs">
<label class="flex items-start gap-2.5 p-3 rounded-xl bg-anthracite-900 border border-anthracite-700 cursor-pointer hover:border-indigo-500/50 transition">
<input type="radio" name="registration_mode" value="send" checked onchange="onTrunkRegistrationModeChange(this.value)" class="mt-0.5">
<div>
<div class="text-white font-semibold flex items-center gap-1.5">
<span>Émission (Send)</span>
<span class="text-[10px] px-1.5 py-0.2 rounded bg-indigo-500/20 text-indigo-300">Défaut</span>
</div>
<div class="text-[11px] text-slate-400 mt-0.5">Asterisk s'enregistre vers l'opérateur (OVH, Keyyo...). Serveur SIP requis.</div>
</div>
</label>
<label class="flex items-start gap-2.5 p-3 rounded-xl bg-anthracite-900 border border-emerald-500/40 cursor-pointer hover:border-emerald-500 transition">
<input type="radio" name="registration_mode" value="receive" onchange="onTrunkRegistrationModeChange(this.value)" class="mt-0.5">
<div>
<div class="text-emerald-300 font-semibold flex items-center gap-1.5">
<span>Réception (Receive)</span>
<span class="text-[10px] px-1.5 py-0.2 rounded bg-emerald-500/20 text-emerald-300">Passerelle FXO</span>
</div>
<div class="text-[11px] text-slate-300 mt-0.5">La passerelle s'enregistre sur Asterisk (Grandstream HT813/HT503, Cisco SPA, AudioCodes, Box Internet / Ligne RTC). Hôte libre.</div>
</div>
</label>
<label class="flex items-start gap-2.5 p-3 rounded-xl bg-anthracite-900 border border-anthracite-700 cursor-pointer hover:border-purple-500/50 transition">
<input type="radio" name="registration_mode" value="none" onchange="onTrunkRegistrationModeChange(this.value)" class="mt-0.5">
<div>
<div class="text-purple-300 font-semibold flex items-center gap-1.5">
<span>Aucun (None)</span>
<span class="text-[10px] px-1.5 py-0.2 rounded bg-purple-500/20 text-purple-300">Peering IP</span>
</div>
<div class="text-[11px] text-slate-400 mt-0.5">Interconnexion directe sans enregistrement (Trunk IP dédié, SBC). Serveur SIP requis.</div>
</div>
</label>
</div>
<!-- Guide interactif d'aide pour Passerelles FXO (Grandstream, Cisco SPA...) -->
<div id="trunk-receive-guide" class="hidden p-3.5 rounded-xl bg-emerald-950/40 border border-emerald-500/40 text-xs text-slate-200 space-y-2">
<div class="flex items-center gap-2 font-bold text-emerald-300 text-xs">
<span>💡 Guide Rapide : Paramétrage d'une passerelle FXO (Grandstream HT813/HT503, Cisco SPA3102...)</span>
</div>
<p class="text-[11px] text-slate-300 leading-relaxed">
Dans l'interface web de votre passerelle (onglet <strong>FXO Port</strong> Grandstream ou <strong>Line 1/PSTN</strong> Cisco SPA relié à la prise téléphonique d'une Box Internet / modem FAI / ligne analogique) :
</p>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2 text-[11px] font-mono bg-anthracite-950 p-3 rounded-lg border border-emerald-500/25">
<div>• <strong>Primary SIP Server</strong> : <span class="text-emerald-300 font-bold"><?= htmlspecialchars($_SERVER['SERVER_ADDR'] ?? '192.168.1.100') ?>:5060</span></div>
<div>• <strong>SIP Registration</strong> : <span class="text-emerald-300 font-bold">Yes</span></div>
<div>• <strong>SIP User ID</strong> : <span class="text-indigo-300 font-bold" id="ht813-guide-user">Même valeur que Username</span></div>
<div>• <strong>Authenticate ID</strong> : <span class="text-indigo-300 font-bold" id="ht813-guide-auth">Même valeur que Username</span></div>
<div>• <strong>Authenticate Password</strong> : <span class="text-indigo-300 font-bold">Votre mot de passe secret</span></div>
<div>• <strong>Stage Method (1/2)</strong> : <span class="text-slate-300">1 (Recommandé pour appel direct)</span></div>
<div class="sm:col-span-2 text-slate-300 font-sans border-t border-anthracite-800 pt-2 mt-1">
• <strong>Unconditional Call Forward to VoIP</strong> : Définir <em>User ID</em> = votre numéro (ex: <code class="text-amber-300 font-mono">0123456789</code>) et <em>SIP Server</em> = <code class="text-amber-300 font-mono">ch1:<?= htmlspecialchars($_SERVER['SERVER_ADDR'] ?? '192.168.1.100') ?>:5060;</code>
</div>
</div>
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-4 gap-4">
<div class="sm:col-span-1">
<label class="block text-xs font-medium text-slate-200 mb-1">Nom du Trunk * <span class="text-slate-400">(ID PJSIP)</span></label>
<input type="text" name="name" placeholder="ex: mon-trunk-sip ou passerelle-fxo" required
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white font-mono">
</div>
<div class="sm:col-span-2">
<label id="trunk-host-label" class="block text-xs font-medium text-slate-200 mb-1">Serveur SIP / SBC / Passerelle * <span class="text-slate-400">(Host / IP)</span></label>
<input type="text" name="host" id="trunk-host-input" placeholder="ex: sbc.operateur.net ou 192.168.1.250" required oninput="syncTrunkHostDomain(this.value)"
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white font-mono">
<div id="trunk-host-hint" class="hidden text-[10px] text-emerald-400 mt-1">En mode Réception (Receive), l'hôte peut rester vide (découverte dynamique de la passerelle lors du REGISTER).</div>
</div>
<div class="sm:col-span-1">
<label class="block text-xs font-medium text-slate-200 mb-1">Port SIP * <span class="text-slate-400">(Port)</span></label>
<input type="number" name="port" value="5060" required
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white font-mono">
</div>
</div>
<div class="p-4 rounded-xl bg-anthracite-950 border border-anthracite-700 space-y-4">
<div class="flex flex-wrap items-center justify-between gap-2">
<span class="text-xs font-bold uppercase tracking-wider text-indigo-300">Mode d'Authentification SIP</span>
<div class="flex items-center gap-4 text-xs">
<label class="flex items-center gap-1.5 cursor-pointer">
<input type="radio" name="auth_mode" value="userpass" checked onchange="toggleTrunkAuthFields(this.value)">
<span class="text-white font-medium">Identifiants (Username / Secret)</span>
</label>
<label class="flex items-center gap-1.5 cursor-pointer">
<input type="radio" name="auth_mode" value="ip" onchange="toggleTrunkAuthFields(this.value)">
<span class="text-slate-300 font-medium">Peering par IP (Sans mot de passe)</span>
</label>
</div>
</div>
<div id="trunk-auth-fields" class="grid grid-cols-1 sm:grid-cols-3 gap-4">
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Identifiant / Numéro SIP <span class="text-slate-400">(Username)</span></label>
<input type="text" name="username" placeholder="ex: 0033123456789 ou mon_user_sip"
class="w-full px-3 py-2 rounded-lg bg-anthracite-900 border border-anthracite-700 text-sm text-white font-mono">
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Secret / Mot de passe SIP <span class="text-slate-400">(Password)</span></label>
<input type="text" name="secret" placeholder="Secret SIP"
class="w-full px-3 py-2 rounded-lg bg-anthracite-900 border border-anthracite-700 text-sm text-white font-mono">
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Auth Username <span class="text-slate-400">(Si différent du Username)</span></label>
<input type="text" name="auth_username" placeholder="Vide = utilise Username"
class="w-full px-3 py-2 rounded-lg bg-anthracite-900 border border-anthracite-700 text-sm text-white font-mono">
</div>
</div>
</div>
<!-- Routage Entrant & Sortant (Assisté + 100% Libre) -->
<div class="p-4 rounded-xl bg-anthracite-950 border border-anthracite-700 space-y-3">
<div class="flex flex-wrap items-center justify-between gap-2">
<span class="text-xs font-bold uppercase tracking-wider text-emerald-300">Destination Entrante par Défaut & Contexte Dialplan</span>
<button type="button" onclick="switchTrunkModalTab('inbound')"
class="px-3 py-1 rounded-lg bg-emerald-500/20 hover:bg-emerald-500/30 border border-emerald-500/40 text-xs font-semibold text-emerald-300 transition">
Gérer les Routes Entrantes par ID Appelant / Anti-Spam →
</button>
</div>
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4 items-end">
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Aide à la sélection (Poste / Groupe / SVI)</label>
<select id="trunk-inbound-select" onchange="syncInboundDestSelect(this.value)"
class="w-full px-3 py-2 rounded-lg bg-anthracite-900 border border-anthracite-700 text-sm text-white">
</select>
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Cible entrante par défaut <span class="text-slate-400">(Si aucune règle ne matche)</span></label>
<input type="text" name="inbound_dest" value="101" placeholder="ex: 101, ext-101, grp-600, ivr-accueil"
class="w-full px-3 py-2 rounded-lg bg-anthracite-900 border border-anthracite-700 text-sm text-white font-mono">
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Contexte Dialplan (<code class="text-indigo-300">context</code>)</label>
<input type="text" name="context" list="trunk-context-suggestions" value="from-pstn-toheader" placeholder="from-pstn-toheader ou from-external"
class="w-full px-3 py-2 rounded-lg bg-anthracite-900 border border-anthracite-700 text-sm text-white font-mono">
<datalist id="trunk-context-suggestions">
<option value="from-pstn-toheader">Lit le numéro SDA dans l'en-tête SIP To: (Style FreePBX / OVH / Trunk DID)</option>
<option value="from-external">Lit le numéro dans l'URI INVITE standard</option>
<option value="from-internal">Interconnexion PBX de confiance (Accès interne direct)</option>
</datalist>
</div>
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-4 gap-4 items-end">
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Protocole Transport</label>
<select name="transport" class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white">
<option value="udp">UDP (Standard 5060)</option>
<option value="tcp">TCP (Fiable 5060)</option>
<option value="tls">TLS (Chiffré 5061)</option>
</select>
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Outbound Proxy (<code class="text-indigo-300">outbound_proxy</code>)</label>
<input type="text" name="outbound_proxy" placeholder="sip:proxy.op.net:5060\;lr"
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white font-mono">
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Préfixe / Règle Sortante</label>
<input type="text" name="outbound_prefix" placeholder="Vide = direct, X. = tout, ou ex: 9"
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white font-mono">
</div>
<div class="h-[40px] flex items-center">
<label class="flex items-center gap-2 text-xs text-slate-200 cursor-pointer">
<input type="checkbox" name="enabled" value="1" checked class="rounded bg-anthracite-950 border-anthracite-600">
<span><strong>Trunk Actif</strong></span>
</label>
</div>
</div>
<!-- Options d'appel Asterisk (Trunk Dial Options style FreePBX) -->
<div class="p-3.5 rounded-xl bg-anthracite-950 border border-anthracite-700/80 flex flex-col sm:flex-row sm:items-center justify-between gap-3">
<div class="max-w-xl">
<label class="block text-xs font-bold text-white mb-0.5 flex items-center gap-1.5">
<span>📞 Options d'appel Asterisk (Asterisk Trunk Dial Options)</span>
<span class="px-1.5 py-0.2 rounded bg-indigo-500/20 text-indigo-300 text-[10px] font-mono font-semibold">FreePBX Dial Options</span>
</label>
<p class="text-[11px] text-slate-300">
Paramètres transmis à l'application Asterisk <code class="text-indigo-300">Dial()</code> pour ce Trunk. <code class="text-emerald-300">R</code> génère la tonalité de sonnerie locale (indispensable pour les passerelles analogiques/FXO type Grandstream HT813, Cisco SPA3102, Box Internet / Ligne RTC). <code class="text-indigo-300">T/t</code> autorise le transfert DTMF.
</p>
</div>
<div class="flex items-center gap-2">
<input type="text" name="dial_options" value="tTR" placeholder="tTR"
class="w-24 px-3 py-1.5 rounded-lg bg-anthracite-900 border border-anthracite-700 text-xs text-white font-mono font-bold text-center focus:border-indigo-500">
<div class="flex items-center gap-1">
<button type="button" onclick="setTrunkDialOptionsPreset('tTR')" class="px-2 py-1 rounded bg-anthracite-800 hover:bg-anthracite-700 text-[11px] text-slate-300 hover:text-white border border-anthracite-600 transition" title="Par défaut recommandé (Transfert DTMF + Tonalité locale)">tTR</button>
<button type="button" onclick="setTrunkDialOptionsPreset('TR')" class="px-2 py-1 rounded bg-emerald-500/20 hover:bg-emerald-500/30 text-[11px] text-emerald-300 border border-emerald-500/40 transition" title="Style FreePBX standard pour passerelles FXO">TR</button>
<button type="button" onclick="setTrunkDialOptionsPreset('T')" class="px-2 py-1 rounded bg-indigo-500/20 hover:bg-indigo-500/30 text-[11px] text-indigo-300 border border-indigo-500/40 transition" title="Standard SIP sans forçage sonnerie">T</button>
</div>
</div>
</div>
</div>
<!-- SOUS-ONGLET 2 : ROUTES ENTRANTES & FILTRAGE PAR ID APPELANT (INBOUND ROUTES) -->
<div id="trunk-panel-inbound" class="trunk-subpanel hidden space-y-4">
<!-- Musique d'attente spécifique à ce Trunk -->
<div class="p-4 rounded-xl bg-anthracite-950 border border-amber-500/30 flex flex-col md:flex-row md:items-center justify-between gap-3 shadow-lg">
<div class="space-y-0.5">
<label class="block text-xs font-bold text-white flex items-center gap-2">
<span class="w-2.5 h-2.5 rounded-full bg-amber-400"></span>
<span>Musique d'attente (MoH) spécifique à ce Trunk</span>
</label>
<p class="text-[11px] text-slate-300">
Permet de substituer la musique globale par une musique dédiée à ce Trunk lors de la mise en garde (Hold) ou du transfert d'appels.
</p>
</div>
<div class="min-w-[280px]">
<select name="moh_sound_id" id="trunk-moh-sound-id"
class="w-full px-3 py-2 rounded-xl bg-anthracite-900 border border-anthracite-700 text-xs text-white focus:outline-none focus:border-amber-500 font-medium">
<option value="0">🌐 Hériter de la musique générale par défaut</option>
</select>
</div>
</div>
<!-- Préfixe CallerID (Nom affiché sur les téléphones IP pour identifier la ligne entrante) -->
<div class="p-3.5 rounded-xl bg-anthracite-950 border border-anthracite-700/80 flex flex-col sm:flex-row sm:items-center justify-between gap-3">
<div class="max-w-xl">
<label class="block text-xs font-bold text-white mb-0.5 flex items-center gap-1.5">
<span>🏷️ Préfixe Nom Appelant (CID Name Prefix)</span>
<span class="px-1.5 py-0.2 rounded bg-indigo-500/20 text-indigo-300 text-[10px] font-mono font-semibold">FreePBX CID Prefix</span>
</label>
<p class="text-[11px] text-slate-300">
Préfixe inséré devant le nom ou numéro de l'appelant sur l'écran de vos téléphones IP pour identifier la ligne appelée (ex: <code class="text-emerald-300">[Ligne-1] </code>, <code class="text-indigo-300">[Support] </code>, <code class="text-cyan-300">[Fixe] </code>).
</p>
</div>
<div class="min-w-[240px]">
<input type="text" name="cid_name_prefix" placeholder="ex: [Ligne-1] "
class="w-full px-3 py-2 rounded-xl bg-anthracite-900 border border-anthracite-700 text-xs text-white font-mono focus:outline-none focus:border-indigo-500">
</div>
</div>
<div class="p-4 rounded-xl bg-anthracite-950 border border-anthracite-700 space-y-3">
<div class="flex flex-wrap items-center justify-between gap-3">
<div>
<div class="text-sm font-bold text-white flex items-center gap-2">
<span>Routes Entrantes & Filtrage par ID Appelant (Inbound Routes)</span>
</div>
<p class="text-xs text-slate-300">
Redirigez ou bloquez les appels entrants de ce Trunk en fonction de <strong>qui appelle (ID Appelant / CallerID)</strong>. Les motifs commençant par <code class="text-indigo-300">_0</code> bloquent automatiquement les formats <code class="text-indigo-300">0...</code>, <code class="text-indigo-300">0033...</code> et <code class="text-indigo-300">+33...</code>.
</p>
</div>
<div class="flex flex-wrap items-center gap-2">
<button type="button" onclick="addArcepSpamBlockPreset()"
class="px-3 py-1.5 rounded-xl bg-amber-500/20 hover:bg-amber-500/30 border border-amber-500/40 text-xs font-semibold text-amber-300 transition">
🛡️ + Ajouter Pack Blocage Démarchage (0162, 0163, 0270, 0377, 0424, 0568, 0948...)
</button>
<button type="button" onclick="addTrunkInboundRouteRow('Any', 'Any', 'Nouvelle règle', 'ext-101', true)"
class="px-3.5 py-1.5 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-xs font-semibold text-white shadow-md shadow-indigo-600/20 transition">
+ Ajouter une Règle Entrante
</button>
</div>
</div>
<!-- Barre de filtre rapide dans les règles -->
<div class="flex flex-wrap items-center justify-between gap-2 pt-1">
<input type="text" id="inbound-routes-filter" oninput="filterInboundRoutesRows(this.value)"
placeholder="🔍 Filtrer vos règles (ex: 0162, Block, VIP, Issy, Occupation...)"
class="w-full sm:w-96 px-3 py-1.5 rounded-lg bg-anthracite-900 border border-anthracite-700 text-xs text-white">
<div class="text-[11px] text-slate-400">
Syntaxe Asterisk : <code class="text-indigo-300">Any</code> = Tous • <code class="text-indigo-300">_0162XXXXXX</code> = Plage 0162 • <code class="text-indigo-300">0612345678</code> = Numéro exact
</div>
</div>
<!-- Tableau des Routes Entrantes (ID Appelant, Description, Destination, Horaires & Actions) -->
<div class="border border-anthracite-700 rounded-xl overflow-hidden bg-anthracite-900">
<div class="grid grid-cols-12 gap-3 px-4 py-2.5 bg-anthracite-950 border-b border-anthracite-700 text-[11px] font-bold uppercase tracking-wider text-slate-300">
<div class="col-span-3">ID Appelant</div>
<div class="col-span-3">Description</div>
<div class="col-span-4">Destination (Si ouvert)</div>
<div class="col-span-2 text-right">Horaires & Actions</div>
</div>
<div id="trunk-did-container" class="divide-y divide-anthracite-700/70 max-h-[44vh] overflow-y-auto"></div>
</div>
</div>
</div>
<!-- SOUS-ONGLET 3 : ENREGISTREMENT (REGISTRATION) & IDENTITÉ SIP -->
<div id="trunk-panel-identity" class="trunk-subpanel hidden space-y-4">
<div id="trunk-reg-outbound-notice" class="hidden p-3.5 rounded-xl bg-indigo-950/40 border border-indigo-500/40 text-xs text-indigo-200 flex items-center gap-2">
<span>ℹ️</span>
<span>En mode <strong>Réception (Passerelle FXO / ATA)</strong> ou <strong>Peering IP direct (None)</strong>, Asterisk n'émet aucun SIP REGISTER sortant. Les paramètres d'enregistrement ci-dessous ne s'appliquent qu'en mode <strong>Émission (Send)</strong>.</span>
</div>
<div class="p-4 rounded-xl bg-anthracite-950 border border-anthracite-700 space-y-4">
<div class="flex flex-wrap items-center justify-between gap-2">
<span class="text-xs font-bold uppercase tracking-wider text-emerald-300">Paramètres d'Enregistrement SIP (<code class="text-emerald-300">[type = registration]</code>)</span>
<div class="flex items-center gap-4">
<label class="flex items-center gap-2 text-xs text-slate-200 cursor-pointer">
<input type="checkbox" name="register" value="1" checked class="rounded bg-anthracite-900 border-anthracite-600">
<span>Activer <code class="text-emerald-300">registration</code> (Envoi SIP REGISTER)</span>
</label>
<label class="flex items-center gap-2 text-xs text-slate-200 cursor-pointer">
<input type="checkbox" name="line_support" value="1" checked class="rounded bg-anthracite-900 border-anthracite-600">
<span>Activer <code class="text-indigo-300">line = yes</code> (Matching entrant auto)</span>
</label>
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Contact User (<code class="text-indigo-300">contact_user</code>)</label>
<input type="text" name="contact_user" placeholder="Extension/SDA reçue ou vide"
class="w-full px-3 py-2 rounded-lg bg-anthracite-900 border border-anthracite-700 text-sm text-white font-mono">
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Expiration REGISTER (<code class="text-indigo-300">expiration</code>)</label>
<input type="number" name="expiration" value="3600" min="60" max="86400"
class="w-full px-3 py-2 rounded-lg bg-anthracite-900 border border-anthracite-700 text-sm text-white font-mono">
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Intervalle de relance (<code class="text-indigo-300">retry_interval</code>)</label>
<input type="number" name="retry_interval" value="60" min="10" max="3600"
class="w-full px-3 py-2 rounded-lg bg-anthracite-900 border border-anthracite-700 text-sm text-white font-mono">
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Tentatives Max (<code class="text-indigo-300">max_retries</code>)</label>
<input type="number" name="max_retries" value="10000" min="0" max="1000000"
class="w-full px-3 py-2 rounded-lg bg-anthracite-900 border border-anthracite-700 text-sm text-white font-mono">
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Relance sur 403 Forbidden (<code class="text-indigo-300">forbidden_retry_interval</code>)</label>
<input type="number" name="forbidden_retry_interval" value="600" min="0" max="86400"
class="w-full px-3 py-2 rounded-lg bg-anthracite-900 border border-anthracite-700 text-sm text-white font-mono">
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Relance Erreur Fatale (<code class="text-indigo-300">fatal_retry_interval</code>)</label>
<input type="number" name="fatal_retry_interval" value="0" min="0" max="86400"
class="w-full px-3 py-2 rounded-lg bg-anthracite-900 border border-anthracite-700 text-sm text-white font-mono">
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Server URI personnalisée (<code class="text-indigo-300">server_uri</code> — auto si vide)</label>
<input type="text" name="server_uri" placeholder="Auto : sip:hote:port"
class="w-full px-3 py-2 rounded-lg bg-anthracite-900 border border-anthracite-700 text-xs text-white font-mono">
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Client URI personnalisée (<code class="text-indigo-300">client_uri</code> — auto si vide)</label>
<input type="text" name="client_uri" placeholder="Auto : sip:username@from_domain:port"
class="w-full px-3 py-2 rounded-lg bg-anthracite-900 border border-anthracite-700 text-xs text-white font-mono">
</div>
</div>
</div>
<div class="p-4 rounded-xl bg-anthracite-950 border border-anthracite-700 space-y-4">
<span class="text-xs font-bold uppercase tracking-wider text-indigo-300 block">En-têtes SIP, Domaine & Présentation du Numéro (CallerID / PAI / RPID / Diversion)</span>
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">From User (<code class="text-indigo-300">from_user</code>)</label>
<input type="text" name="from_user" placeholder="Vide = utilise Username"
class="w-full px-3 py-2 rounded-lg bg-anthracite-900 border border-anthracite-700 text-sm text-white font-mono">
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">From Domain (<code class="text-indigo-300">from_domain</code>)</label>
<input type="text" name="from_domain" placeholder="Vide = utilise le Serveur SIP (Host)"
class="w-full px-3 py-2 rounded-lg bg-anthracite-900 border border-anthracite-700 text-sm text-white font-mono">
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">CallerID Sortant Forcé (<code class="text-indigo-300">callerid</code>)</label>
<input type="text" name="outbound_callerid" placeholder='ex: "Mon Entreprise" <+33123456789>'
class="w-full px-3 py-2 rounded-lg bg-anthracite-900 border border-anthracite-700 text-sm text-white font-mono">
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 pt-1">
<label class="flex items-center gap-2 text-xs text-slate-200 cursor-pointer">
<input type="checkbox" name="send_pai" value="1" checked class="rounded bg-anthracite-900 border-anthracite-600">
<span>Envoyer <strong>P-Asserted-Identity</strong> (<code class="text-indigo-300">send_pai = yes</code>)</span>
</label>
<label class="flex items-center gap-2 text-xs text-slate-200 cursor-pointer">
<input type="checkbox" name="send_rpid" value="1" class="rounded bg-anthracite-900 border-anthracite-600">
<span>Envoyer <strong>Remote-Party-ID</strong> (<code class="text-indigo-300">send_rpid = yes</code>)</span>
</label>
<label class="flex items-center gap-2 text-xs text-slate-200 cursor-pointer">
<input type="checkbox" name="send_diversion" value="1" checked class="rounded bg-anthracite-900 border-anthracite-600">
<span>Envoyer l'en-tête <strong>Diversion</strong> lors des transferts (<code class="text-indigo-300">send_diversion = yes</code>)</span>
</label>
<label class="flex items-center gap-2 text-xs text-slate-200 cursor-pointer">
<input type="checkbox" name="trust_id_inbound" value="1" checked class="rounded bg-anthracite-900 border-anthracite-600">
<span>Accepter l'identité entrante PAI/RPID (<code class="text-indigo-300">trust_id_inbound = yes</code>)</span>
</label>
<label class="flex items-center gap-2 text-xs text-slate-200 cursor-pointer">
<input type="checkbox" name="trust_id_outbound" value="1" checked class="rounded bg-anthracite-900 border-anthracite-600">
<span>Transmettre l'identité sortante Caller ID (<code class="text-indigo-300">trust_id_outbound = yes</code>)</span>
</label>
</div>
</div>
</div>
<!-- SOUS-ONGLET 3 : CODECS, NAT, TIMERS, FAX T.38 & PEERING IP (IDENTIFY) -->
<div id="trunk-panel-media" class="trunk-subpanel hidden space-y-4">
<div class="grid grid-cols-1 sm:grid-cols-4 gap-4">
<div class="sm:col-span-2">
<label class="block text-xs font-medium text-slate-200 mb-1">Codecs Audio autorisés (ordre de priorité)</label>
<input type="text" name="codecs" value="alaw,ulaw,g722" required
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white font-mono">
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Mode DTMF (<code class="text-indigo-300">dtmf_mode</code>)</label>
<select name="dtmf_mode" class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white">
<option value="rfc4733">RFC4733 (RTP Payload)</option>
<option value="auto">Auto</option>
<option value="inband">Inband (Audio G.711)</option>
<option value="info">SIP INFO</option>
</select>
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Chiffrement Média (<code class="text-indigo-300">media_encryption</code>)</label>
<select name="media_encryption" class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white">
<option value="no">Non chiffré (RTP Standard)</option>
<option value="sdes">SRTP (SDES)</option>
<option value="dtls">DTLS-SRTP</option>
</select>
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Ping SIP OPTIONS (<code class="text-indigo-300">qualify_frequency</code> sec)</label>
<input type="number" name="qualify_frequency" value="60" min="0" max="3600"
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white font-mono">
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Keepalive RTP (<code class="text-indigo-300">rtp_keepalive</code> sec)</label>
<input type="number" name="rtp_keepalive" value="15" min="0" max="3600"
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white font-mono">
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Canaux simultanés max (0 = illimité)</label>
<input type="number" name="max_channels" value="0" min="0" max="500"
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white font-mono">
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">SIP Session Timers (<code class="text-indigo-300">timers</code>)</label>
<select name="timers" class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white">
<option value="yes">yes (Standard RFC 4028)</option>
<option value="no">no (Désactiver si coupure à 15 min)</option>
<option value="required">required (Exiger Session Timers)</option>
<option value="always">always (Forcer Session Timers)</option>
</select>
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Support Fax T.38 UDPTL (<code class="text-indigo-300">t38_udptl</code>)</label>
<select name="t38_udptl" class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white">
<option value="no">no (Désactivé / Audio G.711)</option>
<option value="yes">yes (Activer T.38 + Redundancy)</option>
<option value="nat">nat (Activer T.38 avec NAT)</option>
</select>
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Langue Audio (<code class="text-indigo-300">language</code>)</label>
<input type="text" name="language" value="fr" placeholder="fr, en, es..."
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white font-mono">
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 p-3.5 rounded-xl bg-anthracite-950 border border-anthracite-700">
<label class="flex items-center gap-2 text-xs text-slate-200 cursor-pointer">
<input type="checkbox" name="nat_symmetric" value="1" checked class="rounded bg-anthracite-900 border-anthracite-600">
<span><strong>Traversée NAT complète</strong> (<code class="text-indigo-300">rtp_symmetric</code> + <code class="text-indigo-300">force_rport</code> + <code class="text-indigo-300">rewrite_contact</code>)</span>
</label>
<label class="flex items-center gap-2 text-xs text-slate-200 cursor-pointer">
<input type="checkbox" name="direct_media" value="1" class="rounded bg-anthracite-900 border-anthracite-600">
<span>Autoriser Direct Media RTP (<code class="text-indigo-300">direct_media = yes</code>)</span>
</label>
<label class="flex items-center gap-2 text-xs text-slate-200 cursor-pointer">
<input type="checkbox" name="inband_progress" value="1" class="rounded bg-anthracite-900 border-anthracite-600">
<span>Audio Early Media 183 (<code class="text-indigo-300">inband_progress = yes</code>)</span>
</label>
<label class="flex items-center gap-2 text-xs text-slate-200 cursor-pointer">
<input type="checkbox" name="fax_detect" value="1" class="rounded bg-anthracite-900 border-anthracite-600">
<span>Détection tonalité Fax CNG (<code class="text-indigo-300">fax_detect = yes</code>)</span>
</label>
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">
Adresses IP / Sous-réseaux CIDR de l'opérateur pour <code class="text-indigo-300">[type = identify]</code> (séparés par virgule)
</label>
<input type="text" name="match_ips" placeholder="Vide = utilise l'hôte SIP automatiquement. Exemple : 91.121.129.0/24, 212.83.128.0/19"
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-xs text-white font-mono">
<p class="text-[11px] text-slate-400 mt-1">Indispensable pour les opérateurs ou SBC qui envoient les appels entrants depuis plusieurs adresses IP distinctes.</p>
</div>
</div>
<!-- SOUS-ONGLET 5 : DIRECTIVES PJSIP NATIVES -->
<div id="trunk-panel-routing" class="trunk-subpanel hidden space-y-4">
<div class="p-3 rounded-xl bg-anthracite-950 border border-anthracite-700 text-xs text-slate-300">
Injectez ici toute directive PJSIP brute supplémentaire (<code class="text-indigo-300">clé = valeur</code>) dans chacune des sections générées pour ce Trunk.
</div>
<!-- Surcharges PJSIP libres sur les 3 sections PJSIP -->
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">
Directives <code class="text-indigo-300">[endpoint]</code> libres
</label>
<textarea name="custom_endpoint_params" rows="4" placeholder="clé = valeur&#10;ex:&#10;100rel = yes&#10;Trust_id_outbound = yes"
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-xs text-white font-mono"></textarea>
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">
Directives <code class="text-emerald-300">[registration]</code> libres
</label>
<textarea name="custom_reg_params" rows="4" placeholder="clé = valeur&#10;ex:&#10;support_path = yes&#10;security_negotiation = no"
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-xs text-white font-mono"></textarea>
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">
Directives <code class="text-amber-300">[aor]</code> libres
</label>
<textarea name="custom_aor_params" rows="4" placeholder="clé = valeur&#10;ex:&#10;qualify_timeout = 3.0&#10;support_path = yes"
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-xs text-white font-mono"></textarea>
</div>
</div>
</div>
<div class="pt-4 border-t border-anthracite-700 flex items-center justify-between gap-2">
<span class="text-[11px] text-slate-400">Pensez à cliquer sur « Appliquer les configurations » après sauvegarde.</span>
<div class="flex gap-2">
<button type="button" onclick="closeModal('modal-trunk')" class="px-4 py-2 rounded-xl bg-anthracite-700 text-slate-200 text-xs font-semibold">Annuler</button>
<button type="submit" class="px-5 py-2 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-semibold shadow-lg shadow-indigo-600/25">Enregistrer le Trunk PJSIP</button>
</div>
</div>
</form>
</div>
</div>
<!-- MODALE CRUD SVI / IVR -->
<div id="modal-ivr" class="fixed inset-0 z-50 hidden items-center justify-center bg-black/75 backdrop-blur-sm p-3 sm:p-4 overflow-y-auto">
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl max-w-4xl w-full p-6 shadow-2xl space-y-5 my-auto max-h-[94vh] overflow-y-auto">
<div class="flex items-center justify-between pb-3 border-b border-anthracite-700">
<div class="flex items-center gap-2.5">
<div class="w-8 h-8 rounded-xl bg-indigo-500/20 text-indigo-400 flex items-center justify-center font-bold text-sm">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 100-6 3 3 0 000 6z"/>
</svg>
</div>
<div>
<h3 id="modal-ivr-title" class="text-base sm:text-lg font-bold text-white">Serveur Vocal Interactif (SVI / IVR)</h3>
<p class="text-[11px] text-slate-400">Configurez le message vocal d'accueil, le routage des touches DTMF et la tolérance aux erreurs.</p>
</div>
</div>
<button type="button" onclick="closeModal('modal-ivr')" class="text-slate-400 hover:text-white p-1.5 rounded-lg hover:bg-anthracite-700 transition">✕</button>
</div>
<form id="form-ivr" class="space-y-6">
<input type="hidden" name="id">
<!-- SECTION 1 : Paramètres Généraux & Annonce Vocale -->
<div class="p-4 rounded-xl bg-anthracite-900/60 border border-anthracite-700/80 space-y-4">
<div class="flex items-center gap-2 text-xs font-bold uppercase tracking-wider text-indigo-400">
<span class="w-2 h-2 rounded-full bg-indigo-500"></span>
<span>1. Informations Générales & Message d'Accueil</span>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-xs font-semibold text-slate-200 mb-1">
Nom du SVI <span class="text-indigo-400">*</span>
</label>
<input type="text" name="name" placeholder="ivr-accueil" required
class="w-full px-3 py-2 rounded-xl bg-anthracite-950 border border-anthracite-700 text-sm text-white font-mono focus:outline-none focus:border-indigo-500">
<p class="text-[11px] text-slate-400 mt-1">Identifiant unique du menu (ex: ivr-accueil, ivr-support, ivr-nuit).</p>
</div>
<div>
<label class="block text-xs font-semibold text-slate-200 mb-1">
Description
</label>
<input type="text" name="description" placeholder="Standard principal jour"
class="w-full px-3 py-2 rounded-xl bg-anthracite-950 border border-anthracite-700 text-sm text-white focus:outline-none focus:border-indigo-500">
<p class="text-[11px] text-slate-400 mt-1">Libellé explicatif pour les administrateurs.</p>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 pt-1">
<div>
<div class="flex items-center justify-between mb-1">
<label class="block text-xs font-semibold text-slate-200">
Message Audio d'Accueil (Annonce) <span class="text-indigo-400">*</span>
</label>
<button type="button" onclick="closeModal('modal-ivr'); openSoundModal();" class="text-[11px] text-amber-400 hover:text-amber-300 font-medium">+ Téléverser un son</button>
</div>
<select name="audio_file" id="ivr-audio-select" required
class="w-full px-3 py-2 rounded-xl bg-anthracite-950 border border-anthracite-700 text-sm text-white focus:outline-none focus:border-indigo-500">
</select>
<p class="text-[11px] text-slate-400 mt-1">Message vocal d'accueil joué à l'appelant énonçant les choix.</p>
</div>
<div class="flex flex-col justify-center">
<div class="p-3 rounded-xl bg-anthracite-950/70 border border-anthracite-700 flex items-center justify-between gap-3">
<div>
<div class="text-xs font-semibold text-white">Numérotation Directe des Postes</div>
<div class="text-[11px] text-slate-400">Autorise l'appelant à composer directement un poste interne (101, 102...) pendant l'annonce.</div>
</div>
<label class="relative inline-flex items-center cursor-pointer flex-shrink-0">
<input type="checkbox" name="direct_dial" id="ivr-direct-dial-checkbox" value="1" class="sr-only peer" checked>
<div class="w-11 h-6 bg-anthracite-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-indigo-600"></div>
</label>
</div>
</div>
</div>
</div>
<!-- SECTION 2 : Touches DTMF & Choix des Destinations -->
<div class="p-4 rounded-xl bg-anthracite-900/60 border border-anthracite-700/80 space-y-3">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2 text-xs font-bold uppercase tracking-wider text-indigo-400">
<span class="w-2 h-2 rounded-full bg-indigo-500"></span>
<span>2. Touches DTMF & Aiguillage des Choix</span>
</div>
<button type="button" onclick="addIvrOptionRow('', '')"
class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-semibold bg-indigo-600 hover:bg-indigo-500 text-white transition">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
</svg>
<span>Ajouter une touche</span>
</button>
</div>
<p class="text-[11px] text-slate-400">Définissez l'action exécutée lorsque l'appelant appuie sur une touche de son téléphone (0-9, *, #).</p>
<div id="ivr-options-container" class="space-y-2.5 max-h-60 overflow-y-auto pr-1"></div>
</div>
<!-- SECTION 3 : Sécurité & Gestion des Échecs (Invalid & Timeout) -->
<div class="p-4 rounded-xl bg-anthracite-900/60 border border-anthracite-700/80 space-y-4">
<div class="flex items-center gap-2 text-xs font-bold uppercase tracking-wider text-amber-400">
<span class="w-2 h-2 rounded-full bg-amber-400"></span>
<span>3. Sécurité & Gestion des Échecs (Erreurs & Non-Réponse)</span>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-5">
<!-- Colonne Choix Invalide -->
<div class="p-3.5 rounded-xl bg-anthracite-950/60 border border-anthracite-700 space-y-3">
<div class="text-xs font-bold text-white flex items-center gap-1.5">
<span class="text-red-400">⚠️</span> En cas de Touche Invalide
</div>
<div>
<label class="block text-[11px] font-semibold text-slate-300 mb-1">Nombre d'essais max avant échec</label>
<input type="number" name="invalid_retries" value="3" min="1" max="10" required
class="w-full px-3 py-1.5 rounded-lg bg-anthracite-900 border border-anthracite-700 text-xs text-white">
</div>
<div>
<label class="block text-[11px] font-semibold text-slate-300 mb-1">Message d'erreur vocal</label>
<select name="invalid_sound_id" id="ivr-invalid-sound-select"
class="w-full px-3 py-1.5 rounded-lg bg-anthracite-900 border border-anthracite-700 text-xs text-white">
</select>
</div>
<div>
<label class="block text-[11px] font-semibold text-slate-300 mb-1">Destination d'échec finale</label>
<select name="invalid_dest" id="ivr-invalid-dest-select"
class="w-full px-3 py-1.5 rounded-lg bg-anthracite-900 border border-anthracite-700 text-xs text-white">
</select>
</div>
</div>
<!-- Colonne Expiration / Timeout -->
<div class="p-3.5 rounded-xl bg-anthracite-950/60 border border-anthracite-700 space-y-3">
<div class="text-xs font-bold text-white flex items-center gap-1.5">
<span class="text-amber-400">⏱️</span> En cas de Timeout (Aucune touche pressée)
</div>
<div class="grid grid-cols-2 gap-2">
<div>
<label class="block text-[11px] font-semibold text-slate-300 mb-1">Délai d'attente (sec)</label>
<input type="number" name="timeout" value="5" min="2" max="60" required
class="w-full px-3 py-1.5 rounded-lg bg-anthracite-900 border border-anthracite-700 text-xs text-white">
</div>
<div>
<label class="block text-[11px] font-semibold text-slate-300 mb-1">Répétitions max</label>
<input type="number" name="timeout_retries" value="2" min="1" max="10" required
class="w-full px-3 py-1.5 rounded-lg bg-anthracite-900 border border-anthracite-700 text-xs text-white">
</div>
</div>
<div>
<label class="block text-[11px] font-semibold text-slate-300 mb-1">Message de relance (optionnel)</label>
<select name="timeout_sound_id" id="ivr-timeout-sound-select"
class="w-full px-3 py-1.5 rounded-lg bg-anthracite-900 border border-anthracite-700 text-xs text-white">
</select>
</div>
<div>
<label class="block text-[11px] font-semibold text-slate-300 mb-1">Destination d'échec finale</label>
<select name="timeout_dest" id="ivr-timeout-dest-select"
class="w-full px-3 py-1.5 rounded-lg bg-anthracite-900 border border-anthracite-700 text-xs text-white">
</select>
</div>
</div>
</div>
</div>
<div class="pt-4 border-t border-anthracite-700 flex items-center justify-between gap-3">
<span class="text-[11px] text-slate-400">Le dialplan Asterisk et la recharge PJSIP sont générés automatiquement à la validation.</span>
<div class="flex gap-2">
<button type="button" onclick="closeModal('modal-ivr')" class="px-4 py-2 rounded-xl bg-anthracite-700 hover:bg-anthracite-650 text-slate-200 text-xs font-semibold transition">Annuler</button>
<button type="submit" class="px-5 py-2 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-semibold shadow-lg shadow-indigo-600/25 transition">Enregistrer le SVI</button>
</div>
</div>
</form>
</div>
</div>
<!-- ========================================================= -->
<!-- MODALE : TÉLÉVERSEMENT & ÉDITION FICHIER SON -->
<!-- ========================================================= -->
<div id="modal-sound" class="fixed inset-0 z-50 hidden items-center justify-center bg-black/75 backdrop-blur-sm p-3 sm:p-4 overflow-y-auto">
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl max-w-xl w-full p-6 shadow-2xl space-y-4 my-auto">
<div class="flex items-center justify-between pb-3 border-b border-anthracite-700">
<div class="flex items-center gap-2">
<span class="w-2.5 h-2.5 rounded-full bg-amber-400"></span>
<h3 id="modal-sound-title" class="text-base sm:text-lg font-bold text-white">Ajouter un Fichier Son</h3>
</div>
<button type="button" onclick="closeModal('modal-sound')" class="text-slate-400 hover:text-white p-1">✕</button>
</div>
<form id="form-sound" class="space-y-4" enctype="multipart/form-data">
<input type="hidden" name="id">
<!-- 1. Catégorie de Site & Type d'usage -->
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3.5">
<div>
<div class="flex items-center justify-between mb-1">
<label class="block text-xs font-semibold text-slate-300">
Catégorie de site <span class="text-amber-400">*</span>
</label>
<button type="button" onclick="closeModal('modal-sound'); openSoundSitesModal();" class="text-[11px] text-amber-400 hover:text-amber-300 font-medium">+ Gérer les sites</button>
</div>
<select name="site_category" id="sound-input-site" required
class="w-full px-3 py-2 rounded-xl bg-anthracite-950 border border-anthracite-700 text-sm text-white focus:outline-none focus:border-amber-500">
</select>
<p class="text-[10px] text-slate-400 mt-1">Associe ce son à un site physique ou contexte client.</p>
</div>
<div>
<label class="block text-xs font-semibold text-slate-300 mb-1">
Type d'usage <span class="text-amber-400">*</span>
</label>
<select name="usage_type" id="sound-input-usage" required
class="w-full px-3 py-2 rounded-xl bg-anthracite-950 border border-anthracite-700 text-sm text-white focus:outline-none focus:border-amber-500">
<option value="announcement">Annonce d'accueil / Info</option>
<option value="moh">Musique d'attente (MoH)</option>
<option value="ivr">Menu SVI / Guide interactif</option>
<option value="congestion">Ligne occupée / Congestion</option>
<option value="custom">Autre / Personnalisé</option>
</select>
<p class="text-[10px] text-slate-400 mt-1">Détermine l'utilisation dans les routes, SVI et trunks.</p>
</div>
</div>
<!-- 2. Nom du fichier son & Description -->
<div class="space-y-3">
<div>
<label class="block text-xs font-semibold text-slate-300 mb-1">
Nom du fichier son <span class="text-amber-400">*</span>
</label>
<input type="text" name="name" id="sound-input-name" placeholder="ex: Message Accueil Standard" required
class="w-full px-3 py-2 rounded-xl bg-anthracite-950 border border-anthracite-700 text-sm text-white focus:outline-none focus:border-amber-500">
</div>
<div>
<label class="block text-xs font-semibold text-slate-300 mb-1">
Description
</label>
<input type="text" name="description" id="sound-input-desc" placeholder="ex: Diffusé aux correspondants externes lors des heures ouvrées"
class="w-full px-3 py-2 rounded-xl bg-anthracite-950 border border-anthracite-700 text-sm text-slate-200 focus:outline-none focus:border-amber-500">
</div>
</div>
<!-- 3. Fichier audio source (Upload) -->
<div id="sound-file-upload-block" class="space-y-1.5">
<label class="block text-xs font-semibold text-slate-300">
Fichier audio source (.wav, .mp3, .ogg, .m4a) <span class="text-amber-400">*</span>
</label>
<div class="relative border-2 border-dashed border-anthracite-600 hover:border-amber-500/70 rounded-xl p-4 text-center transition bg-anthracite-950/60 cursor-pointer"
onclick="document.getElementById('sound-audio-file-input').click()">
<input type="file" id="sound-audio-file-input" name="audio_file" accept="audio/*,.wav,.mp3,.ogg,.m4a,.flac" class="hidden"
onchange="handleSoundFileSelected(this)">
<div id="sound-upload-placeholder" class="space-y-1 text-slate-400">
<svg class="w-7 h-7 mx-auto text-amber-400/80" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"/>
</svg>
<div class="text-xs font-semibold text-slate-200">Cliquez pour choisir un fichier audio ou glissez-le ici</div>
<div class="text-[10px] text-slate-500">Converti automatiquement en WAV PCM 8kHz 16-bit + G.711a pour Asterisk</div>
</div>
<div id="sound-upload-preview" class="hidden space-y-1">
<div class="text-xs font-bold text-emerald-400" id="sound-upload-filename">fichier.wav</div>
<div class="text-[10px] text-slate-400" id="sound-upload-filesize">0 KB</div>
</div>
</div>
</div>
<div class="pt-3 border-t border-anthracite-700 flex justify-end gap-2.5">
<button type="button" onclick="closeModal('modal-sound')" class="px-4 py-2 rounded-xl bg-anthracite-700 hover:bg-anthracite-650 text-slate-200 text-xs font-semibold transition">
Annuler
</button>
<button type="submit" id="btn-submit-sound" class="px-5 py-2 rounded-xl bg-gradient-to-r from-amber-600 to-amber-500 hover:from-amber-500 hover:to-amber-400 text-white text-xs font-semibold shadow-md shadow-amber-600/20 transition">
Téléverser & Enregistrer
</button>
</div>
</form>
</div>
</div>
<!-- ========================================================= -->
<!-- MODALE : GESTION DES CATÉGORIES DE SITES -->
<!-- ========================================================= -->
<div id="modal-sound-sites" class="fixed inset-0 z-50 hidden items-center justify-center bg-black/75 backdrop-blur-sm p-3 sm:p-4 overflow-y-auto">
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl max-w-2xl w-full p-6 shadow-2xl space-y-5 my-auto">
<div class="flex items-center justify-between pb-3 border-b border-anthracite-700">
<div>
<h3 class="text-base sm:text-lg font-bold text-white">Gestion des Catégories de Sites</h3>
<p class="text-xs text-slate-300">Créez, renommez ou supprimez vos sites (Nice, Paris, etc.) pour organiser vos annonces et musiques d'attente.</p>
</div>
<button type="button" onclick="closeModal('modal-sound-sites')" class="text-slate-400 hover:text-white p-1">✕</button>
</div>
<!-- Formulaire d'ajout / modification de site -->
<form id="form-sound-site" class="p-4 rounded-xl bg-anthracite-950 border border-anthracite-700 space-y-3">
<input type="hidden" name="id" id="sound-site-id">
<div class="grid grid-cols-1 sm:grid-cols-12 gap-3">
<div class="sm:col-span-5">
<label class="block text-xs font-semibold text-slate-300 mb-1">Nom du site *</label>
<input type="text" name="name" id="sound-site-name" placeholder="ex: Site Nice, Site Paris..." required
class="w-full px-3 py-2 rounded-lg bg-anthracite-900 border border-anthracite-700 text-xs text-white focus:outline-none focus:border-indigo-500">
</div>
<div class="sm:col-span-5">
<label class="block text-xs font-semibold text-slate-300 mb-1">Description</label>
<input type="text" name="description" id="sound-site-desc" placeholder="ex: Bureau régional PACA"
class="w-full px-3 py-2 rounded-lg bg-anthracite-900 border border-anthracite-700 text-xs text-white focus:outline-none focus:border-indigo-500">
</div>
<div class="sm:col-span-2 flex items-end">
<button type="submit" id="btn-save-sound-site" class="w-full px-3 py-2 rounded-lg bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-semibold transition">
Enregistrer
</button>
</div>
</div>
</form>
<!-- Tableau des catégories existantes -->
<div class="border border-anthracite-700 rounded-xl overflow-hidden">
<table class="w-full text-left border-collapse">
<thead>
<tr class="bg-anthracite-950 text-[11px] font-semibold uppercase text-slate-400 border-b border-anthracite-700">
<th class="py-2.5 px-4">Nom du site</th>
<th class="py-2.5 px-4">Description</th>
<th class="py-2.5 px-4">Fichiers associés</th>
<th class="py-2.5 px-4 text-right">Actions</th>
</tr>
</thead>
<tbody id="sound-sites-tbody" class="divide-y divide-anthracite-700/60 text-xs"></tbody>
</table>
</div>
<div class="pt-2 flex justify-end">
<button type="button" onclick="closeModal('modal-sound-sites')" class="px-4 py-2 rounded-xl bg-anthracite-700 hover:bg-anthracite-600 text-slate-200 text-xs font-semibold transition">
Fermer
</button>
</div>
</div>
</div>
<!-- ========================================================= -->
<!-- MODALE : GÉNÉRATION LET'S ENCRYPT -->
<!-- ========================================================= -->
<div id="modal-cert-letsencrypt" class="fixed inset-0 z-50 hidden items-center justify-center bg-black/80 backdrop-blur-sm p-3 sm:p-4 overflow-y-auto">
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl max-w-xl w-full p-6 shadow-2xl space-y-5 my-auto">
<div class="flex items-center justify-between pb-3 border-b border-anthracite-700">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-xl bg-emerald-500/20 text-emerald-400 flex items-center justify-center font-bold">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/>
</svg>
</div>
<div>
<h3 class="text-base sm:text-lg font-bold text-white">Générer avec Let's Encrypt</h3>
<p class="text-xs text-slate-300">Émission automatique d'un certificat TLS gratuit via protocole ACME HTTP-01.</p>
</div>
</div>
<button type="button" onclick="closeModal('modal-cert-letsencrypt')" class="text-slate-400 hover:text-white p-1">✕</button>
</div>
<form id="form-cert-letsencrypt" class="space-y-4">
<div>
<label class="block text-xs font-medium text-slate-300 mb-1">Nom / Label du certificat</label>
<input type="text" name="name" placeholder="ex: Let's Encrypt PBX Principal"
class="w-full px-3 py-2 rounded-xl bg-anthracite-900 border border-anthracite-700 text-white text-xs focus:ring-2 focus:ring-emerald-500 focus:outline-none">
</div>
<div>
<label class="block text-xs font-medium text-slate-300 mb-1">Nom de Domaine FQDN *</label>
<input type="text" name="domain" required placeholder="ex: pbx.mondomaine.fr"
class="w-full px-3 py-2 rounded-xl bg-anthracite-900 border border-anthracite-700 text-white text-xs font-mono focus:ring-2 focus:ring-emerald-500 focus:outline-none">
<p class="text-[11px] text-slate-400 mt-1">Le domaine doit impérativement pointer vers l'adresse IP publique de ce serveur (Enregistrement DNS A) et le port 80 doit être ouvert.</p>
</div>
<div>
<label class="block text-xs font-medium text-slate-300 mb-1">Email de l'Administrateur *</label>
<input type="email" name="email" required placeholder="ex: admin@mondomaine.fr"
class="w-full px-3 py-2 rounded-xl bg-anthracite-900 border border-anthracite-700 text-white text-xs focus:ring-2 focus:ring-emerald-500 focus:outline-none">
<p class="text-[11px] text-slate-400 mt-1">Utilisé par l'autorité Let's Encrypt pour les rappels de renouvellement et alertes de sécurité.</p>
</div>
<div class="space-y-2 pt-2 border-t border-anthracite-700/60">
<label class="flex items-center gap-2.5 cursor-pointer text-xs text-slate-200">
<input type="checkbox" name="is_default" value="1" checked class="w-4 h-4 rounded text-emerald-600 bg-anthracite-900 border-anthracite-700 focus:ring-emerald-500">
<span class="font-semibold text-emerald-300">Activer immédiatement par défaut pour Asterisk PJSIP TLS & SRTP</span>
</label>
<label class="flex items-center gap-2.5 cursor-pointer text-xs text-slate-400">
<input type="checkbox" name="staging" value="1" class="w-4 h-4 rounded text-amber-600 bg-anthracite-900 border-anthracite-700 focus:ring-amber-500">
<span>Mode test Staging (recommandé pour tester sans consommer les quotas Let's Encrypt)</span>
</label>
</div>
<div id="letsencrypt-msg-box" class="hidden p-3 rounded-xl text-xs font-mono whitespace-pre-wrap leading-relaxed max-h-40 overflow-y-auto"></div>
<div class="pt-3 flex justify-end gap-3 border-t border-anthracite-700">
<button type="button" onclick="closeModal('modal-cert-letsencrypt')"
class="px-4 py-2 rounded-xl bg-anthracite-700 hover:bg-anthracite-600 text-slate-200 text-xs font-semibold transition">
Annuler
</button>
<button type="submit" id="btn-submit-letsencrypt"
class="px-5 py-2 rounded-xl bg-emerald-600 hover:bg-emerald-500 text-white text-xs font-semibold flex items-center gap-2 shadow-lg shadow-emerald-600/20 transition">
<span id="spinner-letsencrypt" class="hidden animate-spin">↻</span>
<span id="btn-text-letsencrypt">Lancer l'émission ACME</span>
</button>
</div>
</form>
</div>
</div>
<!-- ========================================================= -->
<!-- MODALE : IMPORT CERTIFICAT PERSONNALISÉ (DIGICERT, ETC.) -->
<!-- ========================================================= -->
<div id="modal-cert-custom" class="fixed inset-0 z-50 hidden items-center justify-center bg-black/80 backdrop-blur-sm p-3 sm:p-4 overflow-y-auto">
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl max-w-2xl w-full p-6 shadow-2xl space-y-5 my-auto">
<div class="flex items-center justify-between pb-3 border-b border-anthracite-700">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-xl bg-cyan-500/20 text-cyan-400 flex items-center justify-center font-bold">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"/>
</svg>
</div>
<div>
<h3 class="text-base sm:text-lg font-bold text-white">Importer un Certificat SSL / TLS</h3>
<p class="text-xs text-slate-300">Compatible avec DigiCert, Sectigo, OVH, Comodo, Certificats Wildcard (*.domaine.fr).</p>
</div>
</div>
<button type="button" onclick="closeModal('modal-cert-custom')" class="text-slate-400 hover:text-white p-1">✕</button>
</div>
<form id="form-cert-custom" class="space-y-4" enctype="multipart/form-data">
<div>
<label class="block text-xs font-medium text-slate-300 mb-1">Nom / Description du certificat</label>
<input type="text" name="name" placeholder="ex: DigiCert Wildcard SIP 2026"
class="w-full px-3 py-2 rounded-xl bg-anthracite-900 border border-anthracite-700 text-white text-xs focus:ring-2 focus:ring-cyan-500 focus:outline-none">
</div>
<!-- Certificat Public -->
<div class="space-y-1.5">
<div class="flex items-center justify-between">
<label class="block text-xs font-medium text-slate-300">Certificat Public (.crt / .pem) *</label>
<input type="file" id="file-cert-custom" name="cert_file" accept=".crt,.pem,.cer" class="text-[11px] text-slate-400 file:mr-2 file:py-1 file:px-2 file:rounded-lg file:border-0 file:text-[10px] file:bg-anthracite-700 file:text-slate-200 hover:file:bg-anthracite-600 cursor-pointer">
</div>
<textarea name="cert_pem" id="textarea-cert-pem" rows="4" placeholder="-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----"
class="w-full p-2.5 rounded-xl bg-anthracite-900 border border-anthracite-700 text-slate-200 text-xs font-mono focus:ring-2 focus:ring-cyan-500 focus:outline-none"></textarea>
</div>
<!-- Clé Privée -->
<div class="space-y-1.5">
<div class="flex items-center justify-between">
<label class="block text-xs font-medium text-slate-300">Clé Privée (.key / RSA / EC) *</label>
<input type="file" id="file-key-custom" name="key_file" accept=".key,.pem" class="text-[11px] text-slate-400 file:mr-2 file:py-1 file:px-2 file:rounded-lg file:border-0 file:text-[10px] file:bg-anthracite-700 file:text-slate-200 hover:file:bg-anthracite-600 cursor-pointer">
</div>
<textarea name="key_pem" id="textarea-key-pem" rows="4" placeholder="-----BEGIN PRIVATE KEY----- ... -----END PRIVATE KEY-----"
class="w-full p-2.5 rounded-xl bg-anthracite-900 border border-anthracite-700 text-slate-200 text-xs font-mono focus:ring-2 focus:ring-cyan-500 focus:outline-none"></textarea>
</div>
<!-- Chaîne Intermédiaire (Optionnelle) -->
<div class="space-y-1.5">
<div class="flex items-center justify-between">
<label class="block text-xs font-medium text-slate-300">Chaîne Intermédiaire / CA Bundle (Optionnel)</label>
<input type="file" id="file-chain-custom" name="chain_file" accept=".crt,.pem,.ca-bundle" class="text-[11px] text-slate-400 file:mr-2 file:py-1 file:px-2 file:rounded-lg file:border-0 file:text-[10px] file:bg-anthracite-700 file:text-slate-200 hover:file:bg-anthracite-600 cursor-pointer">
</div>
<textarea name="chain_pem" id="textarea-chain-pem" rows="3" placeholder="-----BEGIN CERTIFICATE----- (CA Intermédiaire) ... -----END CERTIFICATE-----"
class="w-full p-2.5 rounded-xl bg-anthracite-900 border border-anthracite-700 text-slate-200 text-xs font-mono focus:ring-2 focus:ring-cyan-500 focus:outline-none"></textarea>
</div>
<div class="pt-2 border-t border-anthracite-700/60">
<label class="flex items-center gap-2.5 cursor-pointer text-xs text-slate-200">
<input type="checkbox" name="is_default" value="1" checked class="w-4 h-4 rounded text-cyan-600 bg-anthracite-900 border-anthracite-700 focus:ring-cyan-500">
<span class="font-semibold text-cyan-300">Activer immédiatement par défaut pour le transport PJSIP TLS (5061)</span>
</label>
</div>
<div id="custom-cert-msg-box" class="hidden p-3 rounded-xl text-xs font-mono whitespace-pre-wrap leading-relaxed max-h-40 overflow-y-auto"></div>
<div class="pt-3 flex justify-end gap-3 border-t border-anthracite-700">
<button type="button" onclick="closeModal('modal-cert-custom')"
class="px-4 py-2 rounded-xl bg-anthracite-700 hover:bg-anthracite-600 text-slate-200 text-xs font-semibold transition">
Annuler
</button>
<button type="submit" id="btn-submit-custom-cert"
class="px-5 py-2 rounded-xl bg-cyan-600 hover:bg-cyan-500 text-white text-xs font-semibold flex items-center gap-2 shadow-lg shadow-cyan-600/20 transition">
<span id="spinner-custom-cert" class="hidden animate-spin">↻</span>
<span>Vérifier la Clé & Enregistrer</span>
</button>
</div>
</form>
</div>
</div>
<!-- Modal Édition d'un Code de Fonction -->
<div id="modal-edit-feature-code" class="fixed inset-0 z-50 hidden items-center justify-center bg-black/80 backdrop-blur-sm p-3 sm:p-4 overflow-y-auto">
<div class="relative w-full max-w-md rounded-2xl bg-anthracite-800 border border-anthracite-700 shadow-2xl p-6 space-y-4">
<div class="flex items-center justify-between pb-3 border-b border-anthracite-700">
<div class="flex items-center gap-2.5">
<div class="w-8 h-8 rounded-xl bg-indigo-500/20 text-indigo-400 flex items-center justify-center font-bold">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
</svg>
</div>
<div>
<h3 class="text-sm font-bold text-white">Personnaliser le Code de Fonction</h3>
<div id="modal-fc-service-name" class="text-[11px] text-slate-400">Service</div>
</div>
</div>
<button type="button" onclick="closeModal('modal-edit-feature-code')" class="text-slate-400 hover:text-white p-1">✕</button>
</div>
<form id="form-edit-feature-code" class="space-y-4">
<input type="hidden" id="edit-fc-id" name="id">
<div>
<label class="block text-xs font-medium text-slate-300 mb-1">Code / Préfixe de composition *</label>
<input type="text" id="edit-fc-code" name="code" required
class="w-full px-3 py-2 rounded-xl bg-anthracite-950 border border-anthracite-700 text-white font-mono text-sm focus:ring-2 focus:ring-indigo-500 focus:outline-none">
<p class="text-[11px] text-slate-400 mt-1">Généralement commence par <code class="text-indigo-300">*</code> ou <code class="text-indigo-300">**</code> (ex: <code class="text-indigo-300">*72</code>, <code class="text-indigo-300">**</code>).</p>
</div>
<div class="p-3 bg-anthracite-950 rounded-xl border border-anthracite-700/60 text-xs text-slate-300">
<div class="text-[10px] uppercase font-bold text-slate-400">Code standard recommandé</div>
<div id="modal-fc-default-code" class="font-mono text-cyan-400 text-sm mt-0.5">*72</div>
</div>
<div class="pt-3 flex justify-end gap-3 border-t border-anthracite-700">
<button type="button" onclick="closeModal('modal-edit-feature-code')"
class="px-4 py-2 rounded-xl bg-anthracite-700 hover:bg-anthracite-600 text-slate-200 text-xs font-semibold transition">
Annuler
</button>
<button type="submit" id="btn-submit-edit-fc"
class="px-5 py-2 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-semibold flex items-center gap-2 shadow-lg shadow-indigo-600/20 transition">
<span id="spinner-edit-fc" class="hidden animate-spin">↻</span>
<span>Enregistrer & Recharger</span>
</button>
</div>
</form>
</div>
</div>
<!-- MODAL : JOURNAL DES ÉVÉNEMENTS DE SÉCURITÉ ET ATTAQUES -->
<div id="modal-security-events" class="fixed inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-sm hidden p-4">
<div class="bg-anthracite-900 border border-anthracite-700 rounded-2xl w-full max-w-4xl max-h-[85vh] flex flex-col shadow-2xl overflow-hidden animate-fade-in">
<div class="px-6 py-4 border-b border-anthracite-700 flex items-center justify-between bg-anthracite-800">
<div class="flex items-center gap-3">
<span class="p-2 rounded-xl bg-rose-500/15 text-rose-400 border border-rose-500/30">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
</svg>
</span>
<div>
<h3 class="text-base font-bold text-white">Journal des Tentatives d'Intrusion Interceptées</h3>
<p class="text-xs text-slate-300">Événements de sécurité extraits en temps réel depuis Asterisk & Fail2ban</p>
</div>
</div>
<button type="button" onclick="closeModal('modal-security-events')" class="text-slate-400 hover:text-white text-xl p-1 transition">✕</button>
</div>
<div class="p-6 overflow-y-auto flex-1 space-y-4">
<div class="p-3.5 rounded-xl bg-anthracite-950 border border-anthracite-700 text-xs text-slate-300 flex items-center justify-between">
<div>
<b class="text-white">Fonctionnement du filtre :</b>
<span>Fail2ban analyse les échecs d'authentification SIP (mauvais mot de passe ou tentative de scan) et bannit temporairement l'IP source au bout de 3 tentatives.</span>
</div>
<span class="px-2.5 py-1 rounded-full text-[10px] font-bold bg-emerald-500/15 text-emerald-300 border border-emerald-500/30 whitespace-nowrap ml-3">Actif</span>
</div>
<div class="overflow-x-auto rounded-xl border border-anthracite-700">
<table class="w-full text-left border-collapse text-xs">
<thead>
<tr class="border-b border-anthracite-700 text-[11px] font-semibold uppercase tracking-wider text-slate-300 bg-anthracite-950/80">
<th class="py-2.5 px-3">Date / Heure</th>
<th class="py-2.5 px-3">Service</th>
<th class="py-2.5 px-3">Type d'incident</th>
<th class="py-2.5 px-3">Poste / Compte visé</th>
<th class="py-2.5 px-3">Adresse IP Source</th>
</tr>
</thead>
<tbody id="security-events-tbody" class="divide-y divide-anthracite-800 bg-anthracite-950/40">
</tbody>
</table>
<div id="security-events-empty" class="hidden p-8 text-center text-slate-400 text-xs">
Aucun événement de sécurité ou tentative suspecte détectée récemment.
</div>
</div>
</div>
<div class="px-6 py-3.5 border-t border-anthracite-700 flex justify-between items-center bg-anthracite-800/80">
<span class="text-[11px] text-slate-400 font-mono">Fichier source : /var/log/asterisk/security.log</span>
<div class="flex gap-2">
<button type="button" onclick="clearSecurityLog()" class="px-3 py-1.5 rounded-xl text-xs font-semibold bg-rose-600/20 hover:bg-rose-600/30 text-rose-300 border border-rose-500/30 transition flex items-center gap-1.5">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>
Vider le journal
</button>
<button type="button" onclick="openSecurityEventsModal()" class="px-3 py-1.5 rounded-xl text-xs font-semibold bg-anthracite-700 hover:bg-anthracite-600 text-white transition flex items-center gap-1.5">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
Rafraîchir
</button>
<button type="button" onclick="closeModal('modal-security-events')" class="px-4 py-1.5 rounded-xl text-xs font-semibold bg-indigo-600 hover:bg-indigo-500 text-white transition">
Fermer
</button>
</div>
</div>
</div>
</div>
<!-- ===================================================================== -->
<!-- MODAL : CONTACT ANNUAIRE (INTERNE / EXTERNE) -->
<!-- ===================================================================== -->
<div id="modal-phonebook-contact" class="fixed inset-0 z-50 hidden items-center justify-center bg-black/75 backdrop-blur-sm p-3 sm:p-4 overflow-y-auto">
<div class="bg-anthracite-900 border border-anthracite-700 rounded-2xl w-full max-w-lg shadow-2xl overflow-hidden my-6">
<div class="px-6 py-4 border-b border-anthracite-700/80 flex items-center justify-between bg-anthracite-800">
<div class="flex items-center gap-2">
<span class="w-2.5 h-2.5 rounded-full bg-teal-400"></span>
<h3 id="modal-phonebook-title" class="text-base font-bold text-white">Nouveau Contact Annuaire</h3>
</div>
<button type="button" onclick="closeModal('modal-phonebook-contact')" class="text-slate-400 hover:text-white transition">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
</button>
</div>
<form id="form-phonebook-contact" onsubmit="savePhonebookContact(event)" class="p-6 space-y-4">
<input type="hidden" name="id" value="">
<input type="hidden" name="extension_id" value="">
<input type="hidden" name="is_internal" value="0">
<input type="hidden" name="photo_url" id="phonebook-input-photo-url" value="">
<!-- Photo / Avatar preview & Upload -->
<div class="flex items-center gap-4 p-3.5 rounded-xl bg-anthracite-950 border border-anthracite-700">
<div id="phonebook-avatar-preview" class="w-14 h-14 rounded-full bg-teal-600/30 border border-teal-500/40 text-teal-300 flex items-center justify-center font-bold text-lg overflow-hidden shrink-0 shadow-inner">
?
</div>
<div class="flex-1 space-y-1">
<label class="block text-xs font-semibold text-slate-200">Photo / Avatar du Contact</label>
<p class="text-[11px] text-slate-400">Affiché sur l'interface et sur les téléphones IP à écran couleur (Yealink, Fanvil, Grandstream...).</p>
<div class="flex items-center gap-2 pt-1">
<label class="px-3 py-1 rounded-lg text-xs font-medium bg-anthracite-800 hover:bg-anthracite-750 text-slate-200 border border-anthracite-600 cursor-pointer transition">
<span>Choisir une photo...</span>
<input type="file" id="phonebook-photo-file" accept="image/jpeg,image/png,image/webp" onchange="uploadContactPhoto(this)" class="hidden">
</label>
<button type="button" id="phonebook-btn-clear-photo" onclick="clearContactPhoto()" class="text-xs text-rose-400 hover:text-rose-300 hidden">
Supprimer
</button>
</div>
</div>
</div>
<!-- Nom d'affichage -->
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Nom d'affichage <span class="text-rose-400">*</span></label>
<input type="text" name="display_name" required placeholder="Ex: Jean Dupont, Service Client, Taxi..."
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white focus:outline-none focus:border-teal-500">
</div>
<!-- Numéros de téléphone (Ligne 1 : Poste Interne & Mobile) -->
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Numéro Poste Interne</label>
<input type="text" name="internal_number" placeholder="Ex: 101, 200..."
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white font-mono focus:outline-none focus:border-teal-500">
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Téléphone Mobile</label>
<input type="text" name="mobile_number" placeholder="Ex: 06 12 34 56 78"
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white font-mono focus:outline-none focus:border-teal-500">
</div>
</div>
<!-- Numéros de téléphone (Ligne 2 : Autre Numéro & Email) -->
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Autre Numéro / Ligne Directe</label>
<input type="text" name="other_number" placeholder="Ex: 01 23 45 67 89"
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white font-mono focus:outline-none focus:border-teal-500">
</div>
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Adresse Email</label>
<input type="email" name="email" placeholder="contact@domaine.fr"
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white focus:outline-none focus:border-teal-500">
</div>
</div>
<!-- Groupe d'Annuaire -->
<div>
<label class="block text-xs font-medium text-slate-200 mb-1">Groupe d'Annuaire <span class="text-rose-400">*</span></label>
<input type="text" name="group_name" id="phonebook-input-group" list="phonebook-groups-datalist" required placeholder="Ex: Direction, Commercial, Support, Technique..."
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white focus:outline-none focus:border-teal-500">
<datalist id="phonebook-groups-datalist"></datalist>
<p class="text-[11px] text-slate-400 mt-1">Ce nom de groupe correspond au paramètre <code class="text-teal-300">?cgroup=...</code> dans l'URL de l'annuaire XML.</p>
</div>
<div class="pt-3 border-t border-anthracite-700 flex justify-end gap-2">
<button type="button" onclick="closeModal('modal-phonebook-contact')" class="px-4 py-2 rounded-xl text-xs font-medium bg-anthracite-800 hover:bg-anthracite-700 text-slate-300 transition">
Annuler
</button>
<button type="submit" class="px-4 py-2 rounded-xl text-xs font-bold bg-teal-600 hover:bg-teal-500 text-white shadow-lg shadow-teal-600/20 transition">
Enregistrer le Contact
</button>
</div>
</form>
</div>
</div>
<!-- ===================================================================== -->
<!-- MODALE : CONFIRMATION RESTAURATION DU SYSTÈME (THEME NEXUSPBX) -->
<!-- ===================================================================== -->
<div id="modal-restore-backup" class="fixed inset-0 z-50 hidden items-center justify-center bg-black/80 backdrop-blur-sm p-3 sm:p-4 overflow-y-auto">
<div class="bg-anthracite-900 border border-amber-500/40 rounded-2xl w-full max-w-lg shadow-2xl overflow-hidden my-6 animate-fadeIn">
<!-- Entête stylisée -->
<div class="px-6 py-5 border-b border-anthracite-800 bg-gradient-to-r from-amber-950/40 via-anthracite-900 to-anthracite-900 flex items-start justify-between">
<div class="flex items-center gap-3.5">
<div class="w-11 h-11 rounded-xl bg-amber-500/15 border border-amber-500/30 text-amber-400 flex items-center justify-center shrink-0 shadow-lg shadow-amber-950/50">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
</svg>
</div>
<div>
<div class="flex items-center gap-2">
<h3 id="modal-restore-title" class="text-base font-bold text-white">Restauration Complète du Système</h3>
<span id="restore-target-badge" class="px-2 py-0.5 rounded text-[10px] font-bold tracking-wide uppercase bg-amber-500/20 text-amber-300 border border-amber-500/30">Archive</span>
</div>
<p class="text-xs text-slate-400 mt-0.5">Disaster Recovery & Remise en état Asterisk</p>
</div>
</div>
<button type="button" onclick="closeRestoreModal()" class="text-slate-400 hover:text-white p-1.5 rounded-lg hover:bg-anthracite-800 transition">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
</button>
</div>
<!-- Corps de la modale -->
<div class="p-6 space-y-4">
<!-- Fichier cible -->
<div class="p-3.5 rounded-xl bg-anthracite-950/90 border border-anthracite-800 space-y-1.5">
<div class="text-[11px] font-semibold text-slate-400 flex items-center gap-1.5 uppercase tracking-wider">
<svg class="w-3.5 h-3.5 text-amber-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4"/></svg>
Archive sélectionnée pour la restauration
</div>
<div id="restore-target-name" class="font-mono text-xs font-semibold text-amber-300 break-all bg-black/40 px-3 py-2 rounded-lg border border-anthracite-800/80">
nom_de_l_archive.tar.gz
</div>
</div>
<!-- Ce que fait la restauration -->
<div class="space-y-2">
<div class="text-xs font-semibold text-slate-300">Cette action va restaurer et écraser :</div>
<ul class="text-xs text-slate-400 space-y-1.5 pl-1">
<li class="flex items-center gap-2">
<span class="w-1.5 h-1.5 rounded-full bg-cyan-400 shrink-0"></span>
<span><strong>Base SQLite</strong> : postes SIP, trunks, routes, SVI, téléavertissement, annuaires.</span>
</li>
<li class="flex items-center gap-2">
<span class="w-1.5 h-1.5 rounded-full bg-indigo-400 shrink-0"></span>
<span><strong>Médiathèque</strong> : jingles, musiques d'attente (MoH), annonces personnalisées.</span>
</li>
<li class="flex items-center gap-2">
<span class="w-1.5 h-1.5 rounded-full bg-amber-400 shrink-0"></span>
<span><strong>Messageries vocales</strong> : boîtes vocales, salutations enregistrées, PINs.</span>
</li>
<li class="flex items-center gap-2">
<span class="w-1.5 h-1.5 rounded-full bg-emerald-400 shrink-0"></span>
<span><strong>Moteur Asterisk</strong> : régénération du dialplan et rechargement PJSIP à chaud.</span>
</li>
</ul>
</div>
<!-- Avertissement -->
<div class="p-3 rounded-xl bg-amber-950/30 border border-amber-500/30 flex items-start gap-3">
<svg class="w-5 h-5 text-amber-400 shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
</svg>
<div class="text-xs text-amber-200/90 leading-relaxed">
<strong>Attention :</strong> Toutes les données et configurations modifiées après la date de cette sauvegarde seront définitivement remplacées.
</div>
</div>
<!-- Boutons d'action -->
<div class="pt-3 border-t border-anthracite-800 flex items-center justify-end gap-2.5">
<button type="button" id="btn-cancel-restore" onclick="closeRestoreModal()"
class="px-4 py-2.5 rounded-xl text-xs font-semibold bg-anthracite-800 hover:bg-anthracite-750 text-slate-300 border border-anthracite-700 transition">
Annuler
</button>
<button type="button" id="btn-confirm-restore" onclick="executePendingRestore()"
class="px-5 py-2.5 rounded-xl text-xs font-bold bg-gradient-to-r from-amber-600 via-red-600 to-red-700 hover:from-amber-500 hover:to-red-600 text-white shadow-lg shadow-red-900/40 transition flex items-center gap-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
<span>Confirmer la Restauration</span>
</button>
</div>
</div>
</div>
</div>
<!-- ===================================================================== -->
<!-- MODAL : CONFIRMATION MISE À JOUR SYSTEME (OTA 1-CLIC) -->
<!-- ===================================================================== -->
<div id="modal-confirm-update" class="fixed inset-0 z-50 hidden items-center justify-center bg-black/80 backdrop-blur-sm p-3 sm:p-4 overflow-y-auto">
<div class="bg-anthracite-900 border border-indigo-500/40 rounded-2xl w-full max-w-lg shadow-2xl overflow-hidden my-6 animate-fadeIn">
<!-- Entête stylisée -->
<div class="px-6 py-5 border-b border-anthracite-800 bg-gradient-to-r from-indigo-950/50 via-anthracite-900 to-anthracite-900 flex items-start justify-between">
<div class="flex items-center gap-3.5">
<div class="w-11 h-11 rounded-xl bg-indigo-500/15 border border-indigo-500/30 text-indigo-400 flex items-center justify-center shrink-0 shadow-lg shadow-indigo-950/50">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/>
</svg>
</div>
<div>
<div class="flex items-center gap-2">
<h3 class="text-base font-bold text-white">Mise à Jour du Système NexusPBX</h3>
<span id="update-modal-target-badge" class="px-2 py-0.5 rounded text-[10px] font-bold tracking-wide uppercase bg-indigo-500/20 text-indigo-300 border border-indigo-500/30">OTA 1-Clic</span>
</div>
<p class="text-xs text-slate-400 mt-0.5">Déploiement officiel et rechargement sans coupure</p>
</div>
</div>
<button type="button" onclick="closeUpdateConfirmModal()" class="text-slate-400 hover:text-white p-1.5 rounded-lg hover:bg-anthracite-800 transition">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
</button>
</div>
<!-- Corps de la modale -->
<div class="p-6 space-y-4">
<!-- Info cible -->
<div class="p-3.5 rounded-xl bg-anthracite-950/90 border border-anthracite-800 flex items-center justify-between">
<div>
<div class="text-[11px] font-semibold text-slate-400 uppercase tracking-wider">Version Cible</div>
<div id="update-modal-target-version" class="text-base font-bold text-emerald-400 font-mono mt-0.5">v...</div>
</div>
<div class="text-right">
<div class="text-[11px] font-semibold text-slate-400 uppercase tracking-wider">Version Actuelle</div>
<div id="update-modal-current-version" class="text-sm font-semibold text-slate-300 font-mono mt-0.5">v...</div>
</div>
</div>
<!-- Garanties & Sauvegarde automatique -->
<div class="p-3.5 rounded-xl bg-emerald-500/10 border border-emerald-500/20 text-xs text-emerald-300 flex items-start gap-3">
<svg class="w-5 h-5 text-emerald-400 shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"/>
</svg>
<div>
<strong class="font-bold text-emerald-200">Sauvegarde préventive automatique incluse :</strong>
<p class="mt-0.5 text-emerald-300/90 leading-relaxed">Une sauvegarde complète de votre base SQLite et de vos configurations Asterisk sera créée avant toute modification.</p>
</div>
</div>
<!-- Ce que fait la mise à jour -->
<div class="space-y-2">
<div class="text-xs font-semibold text-slate-300">Étapes automatiques du déploiement :</div>
<ul class="text-xs text-slate-400 space-y-1.5 pl-1">
<li class="flex items-center gap-2">
<span class="w-1.5 h-1.5 rounded-full bg-cyan-400 shrink-0"></span>
<span>Téléchargement des derniers fichiers et correctifs du dépôt officiel.</span>
</li>
<li class="flex items-center gap-2">
<span class="w-1.5 h-1.5 rounded-full bg-indigo-400 shrink-0"></span>
<span>Exécution des migrations SQLite et régénération des configurations Asterisk.</span>
</li>
<li class="flex items-center gap-2">
<span class="w-1.5 h-1.5 rounded-full bg-emerald-400 shrink-0"></span>
<span>Rechargement à chaud sans interruption des communications actives.</span>
</li>
</ul>
</div>
</div>
<!-- Boutons d'action -->
<div class="px-6 py-4 border-t border-anthracite-800 bg-anthracite-950/80 flex items-center justify-end gap-3">
<button type="button" onclick="closeUpdateConfirmModal()"
class="px-4 py-2.5 rounded-xl text-xs font-semibold bg-anthracite-800 hover:bg-anthracite-750 text-slate-300 border border-anthracite-700 transition">
Annuler
</button>
<button type="button" id="btn-confirm-update-action" onclick="executeSystemUpdate()"
class="px-5 py-2.5 rounded-xl text-xs font-bold bg-gradient-to-r from-indigo-600 to-indigo-500 hover:from-indigo-500 hover:to-indigo-400 text-white shadow-lg shadow-indigo-600/30 transition flex items-center gap-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/></svg>
<span>Lancer la mise à jour (1-Clic)</span>
</button>
</div>
</div>
</div>
<!-- ===================================================================== -->
<!-- MODAL : URL XML YEALINK REMOTE PHONEBOOK (cm_to_yl_ab.php) -->
<!-- ===================================================================== -->
<div id="modal-yealink-xml" class="fixed inset-0 z-50 hidden items-center justify-center bg-black/75 backdrop-blur-sm p-3 sm:p-4 overflow-y-auto">
<div class="bg-anthracite-900 border border-anthracite-700 rounded-2xl w-full max-w-2xl shadow-2xl overflow-hidden my-6">
<div class="px-6 py-4 border-b border-anthracite-700/80 flex items-center justify-between bg-anthracite-800">
<div class="flex items-center gap-2">
<span class="w-2.5 h-2.5 rounded-full bg-teal-400"></span>
<h3 class="text-base font-bold text-white">URL XML Téléphones IP (<code class="text-teal-300 font-mono">phonebook.php</code>)</h3>
</div>
<button type="button" onclick="closeModal('modal-yealink-xml')" class="text-slate-400 hover:text-white transition">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
</button>
</div>
<div class="p-6 space-y-4">
<div class="p-4 rounded-xl bg-teal-500/10 border border-teal-500/20 text-xs text-teal-200 space-y-1">
<div class="font-bold flex items-center gap-1.5">
<svg class="w-4 h-4 text-teal-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
Comment configurer votre téléphone IP (Yealink, Fanvil, Grandstream, Snom...) ?
</div>
<p class="text-slate-300">Dans l'interface web de votre téléphone (ex: Yealink T54W/T46S, Fanvil X-Series, Grandstream GRP, Snom D-Series) :</p>
<p class="font-mono text-teal-300 bg-anthracite-950/60 px-2 py-1 rounded">Directory &gt; Remote Phonebook &gt; Phonebook URL</p>
<p class="text-slate-300">Collez simplement l'URL correspondant au groupe souhaité ci-dessous. Le téléphone téléchargera et mettra à jour l'annuaire automatiquement.</p>
</div>
<!-- Sélecteur de Groupe -->
<div>
<label class="block text-xs font-semibold text-slate-200 mb-1">Sélectionner le Groupe d'Annuaire</label>
<select id="yealink-modal-group-select" onchange="updateYealinkXmlUrls()"
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white focus:outline-none focus:border-teal-500">
<option value="all">Tous les groupes (Annuaire complet de l'entreprise)</option>
</select>
</div>
<!-- URL 1 : Port 8080 (Format FreePBX classique HTTP) -->
<div class="space-y-1">
<div class="flex items-center justify-between text-xs">
<span class="font-semibold text-white flex items-center gap-1.5">
<span class="w-2 h-2 rounded-full bg-emerald-400"></span>
<span>URL Recommandée (HTTP Port 8080 - Compatible tous téléphones IP)</span>
</span>
<span class="text-[10px] text-emerald-400 font-mono font-bold">Sans certificat SSL requis</span>
</div>
<div class="flex items-center gap-2">
<input type="text" id="yealink-url-8080" readonly
class="flex-1 px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-xs font-mono text-teal-300 select-all">
<button type="button" onclick="copyYealinkUrl('yealink-url-8080', this)"
class="px-3.5 py-2 rounded-lg text-xs font-bold bg-teal-600 hover:bg-teal-500 text-white transition shrink-0 flex items-center gap-1">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"/></svg>
<span>Copier</span>
</button>
</div>
</div>
<!-- Info Mode Strict Port 80 -->
<div class="p-2.5 rounded-lg bg-emerald-500/10 border border-emerald-500/20 text-[11px] text-emerald-300 flex items-center gap-2">
<svg class="w-4 h-4 shrink-0 text-emerald-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"/></svg>
<span><strong>Sécurité :</strong> Le port 80 standard est réservé aux challenges Let's Encrypt Certbot. L'annuaire XML pour vos postes IP est servi sur le port dédié <strong>8080</strong> ou en <strong>HTTPS</strong> via <code class="text-teal-300 font-mono">phonebook.php</code>.</span>
</div>
<!-- URL 2 : Port 8443 / 443 HTTPS Chiffré -->
<div class="space-y-1">
<div class="flex items-center justify-between text-xs">
<span class="font-semibold text-slate-300 flex items-center gap-1.5">
<span class="w-2 h-2 rounded-full bg-indigo-400"></span>
<span>URL Chiffrée (HTTPS Port 8443 / 443)</span>
</span>
<span class="text-[10px] text-slate-400 font-mono">Nécessite certificat approuvé par le poste</span>
</div>
<div class="flex items-center gap-2">
<input type="text" id="yealink-url-https" readonly
class="flex-1 px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-xs font-mono text-slate-300 select-all">
<button type="button" onclick="copyYealinkUrl('yealink-url-https', this)"
class="px-3.5 py-2 rounded-lg text-xs font-semibold bg-anthracite-800 hover:bg-anthracite-750 text-slate-200 border border-anthracite-600 transition shrink-0">
Copier
</button>
</div>
</div>
<!-- Aperçu en direct du XML généré -->
<div class="space-y-1 pt-2 border-t border-anthracite-700/80">
<div class="flex items-center justify-between text-xs">
<span class="font-semibold text-slate-300">Aperçu en Direct du Flux XML (Standard Téléphones IP)</span>
<a id="yealink-open-browser-btn" href="#" target="_blank" class="text-teal-400 hover:text-teal-300 text-[11px] underline">
Ouvrir dans le navigateur ↗
</a>
</div>
<pre id="yealink-xml-preview" class="p-3 rounded-xl bg-anthracite-950 border border-anthracite-700 font-mono text-[11px] text-teal-300 overflow-x-auto max-h-48 whitespace-pre-wrap select-all">Chargement du flux XML...</pre>
</div>
<div class="pt-3 border-t border-anthracite-700 flex justify-end">
<button type="button" onclick="closeModal('modal-yealink-xml')" class="px-5 py-2 rounded-xl text-xs font-bold bg-indigo-600 hover:bg-indigo-500 text-white transition">
Fermer
</button>
</div>
</div>
</div>
</div>
<script src="/assets/app.js?v=<?= filemtime(__DIR__ . '/assets/app.js') ?>"></script>
</body>
</html>