5684 lines
467 KiB
PHP
5684 lines
467 KiB
PHP
<?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'] ?? '2.0.0';
|
||
$appCodename = $appVerData['codename'] ?? 'Pulsar';
|
||
|
||
// 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 class="hidden sm:inline font-sans font-normal opacity-80">« <?= htmlspecialchars($appCodename) ?> »</span></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>
|
||
<button type="button" data-tab="ucp" onclick="switchTab('ucp')"
|
||
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>• Portail Utilisateur (UCP)</span>
|
||
<span id="nav-badge-ucp" class="px-1.5 py-0.2 text-[10px] rounded-full bg-anthracite-800 text-sky-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) ?> — <?= htmlspecialchars($appCodename) ?></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="nexus-ucp">Portail Utilisateur (UCP)</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>
|
||
|
||
<!-- Portail Utilisateur Isolé (UCP) & Fail2ban dédié -->
|
||
<div class="p-3.5 rounded-xl bg-anthracite-950 border border-anthracite-700 space-y-3">
|
||
<div class="flex items-start justify-between gap-3">
|
||
<div>
|
||
<span class="text-xs font-bold uppercase tracking-wider text-sky-300">Portail Utilisateur Isolé (UCP)</span>
|
||
<p class="text-[11px] text-slate-400 leading-tight mt-0.5">VHost Nginx séparé de l'Admin avec prison Fail2ban <code class="text-sky-300">[nexus-ucp]</code>.</p>
|
||
</div>
|
||
<label class="relative inline-flex items-center cursor-pointer shrink-0 mt-0.5">
|
||
<input type="checkbox" name="ucp_enabled" id="ucp_enabled" 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-sky-600"></div>
|
||
</label>
|
||
</div>
|
||
|
||
<div class="grid grid-cols-2 gap-2.5">
|
||
<div>
|
||
<label class="block text-[11px] font-medium text-slate-300 mb-1">Port HTTPS UCP</label>
|
||
<input type="number" name="ucp_https_port" id="ucp_https_port" value="8444" min="0" max="65535"
|
||
class="w-full px-2.5 py-1.5 rounded-lg bg-anthracite-900 border border-anthracite-700 text-xs text-white font-mono">
|
||
<p class="text-[10px] text-slate-400 mt-0.5">Défaut : 8444 (0 = désactivé)</p>
|
||
</div>
|
||
<div>
|
||
<label class="block text-[11px] font-medium text-slate-300 mb-1">Port HTTP UCP (Proxy)</label>
|
||
<input type="number" name="ucp_http_port" id="ucp_http_port" value="8084" min="0" max="65535"
|
||
class="w-full px-2.5 py-1.5 rounded-lg bg-anthracite-900 border border-anthracite-700 text-xs text-white font-mono">
|
||
<p class="text-[10px] text-slate-400 mt-0.5">Défaut : 8084 (0 = désactivé)</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="grid grid-cols-2 gap-2.5 pt-1 border-t border-anthracite-800">
|
||
<div>
|
||
<label class="block text-[11px] font-medium text-slate-300 mb-1">Max échecs UCP</label>
|
||
<select name="fail2ban_ucp_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 ban UCP</label>
|
||
<select name="fail2ban_ucp_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 UCP : PORTAIL UTILISATEUR & COMPTES -->
|
||
<!-- ========================================================= -->
|
||
<section id="tab-ucp" class="tab-panel hidden space-y-6">
|
||
<!-- En-tête & État d'exposition réseau UCP -->
|
||
<div class="rounded-2xl bg-anthracite-900 border border-anthracite-800 p-5 flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4">
|
||
<div class="flex items-start gap-4">
|
||
<div class="w-11 h-11 rounded-xl bg-sky-500/15 border border-sky-500/30 flex items-center justify-center shrink-0">
|
||
<svg class="w-6 h-6 text-sky-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
|
||
<path stroke-linecap="round" stroke-linejoin="round" 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>
|
||
</div>
|
||
<div>
|
||
<div class="flex items-center gap-2.5 flex-wrap">
|
||
<h3 class="text-base font-bold text-white">Portail Utilisateur (UCP)</h3>
|
||
<span id="ucp-service-status-badge" class="px-2 py-0.5 rounded-full text-[11px] font-semibold bg-emerald-500/15 text-emerald-300 border border-emerald-500/30">
|
||
Vérification...
|
||
</span>
|
||
</div>
|
||
<p class="text-xs text-slate-400 mt-1">
|
||
VirtualHost Nginx isolé sur <code class="text-sky-300">/var/www/nexus-pbx/ucp</code> — aucun accès à l'administration, protégé par Fail2ban (<code class="text-sky-300">nexus-ucp</code>).
|
||
</p>
|
||
<div id="ucp-access-urls" class="mt-2.5 flex flex-wrap items-center gap-2 text-xs"></div>
|
||
</div>
|
||
</div>
|
||
<div class="flex items-center gap-2.5 shrink-0">
|
||
<button type="button" onclick="switchTab('firewall')"
|
||
class="px-3.5 py-2 rounded-xl bg-anthracite-800 hover:bg-anthracite-750 text-slate-200 border border-anthracite-700 text-xs font-semibold transition flex items-center gap-2">
|
||
<svg class="w-4 h-4 text-emerald-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||
<path stroke-linecap="round" stroke-linejoin="round" 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" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||
</svg>
|
||
Configurer les Ports & Fail2ban
|
||
</button>
|
||
<button type="button" onclick="openUcpUserModal()"
|
||
class="px-4 py-2 rounded-xl bg-sky-600 hover:bg-sky-500 text-white text-xs font-bold shadow-lg shadow-sky-600/20 transition flex items-center gap-1.5">
|
||
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.2">
|
||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4" />
|
||
</svg>
|
||
Nouvel Utilisateur UCP
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tableau des utilisateurs UCP -->
|
||
<div class="rounded-2xl bg-anthracite-900 border border-anthracite-800 overflow-hidden">
|
||
<div class="px-5 py-4 border-b border-anthracite-800 flex items-center justify-between gap-3">
|
||
<div>
|
||
<h4 class="text-sm font-bold text-white">Comptes Portail Utilisateur</h4>
|
||
<p class="text-[11px] text-slate-400">Chaque compte ne peut gérer que les extensions SIP qui lui sont explicitement assignées.</p>
|
||
</div>
|
||
</div>
|
||
<div class="overflow-x-auto">
|
||
<table class="w-full text-left border-collapse">
|
||
<thead>
|
||
<tr class="border-b border-anthracite-800 bg-anthracite-950/60 text-[11px] font-semibold uppercase tracking-wider text-slate-400">
|
||
<th class="px-5 py-3">Identifiant</th>
|
||
<th class="px-5 py-3">Nom affiché & Email</th>
|
||
<th class="px-5 py-3">Extensions liées</th>
|
||
<th class="px-5 py-3">Statut</th>
|
||
<th class="px-5 py-3">Dernière connexion</th>
|
||
<th class="px-5 py-3 text-right">Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="ucp-users-table-body" class="divide-y divide-anthracite-800/70 text-xs">
|
||
<tr>
|
||
<td colspan="6" class="px-5 py-8 text-center text-slate-500">Chargement des utilisateurs UCP...</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</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-3 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>
|
||
<label class="block text-xs font-semibold text-slate-200 mb-1">RTP Timeout Mise en attente</label>
|
||
<input type="number" id="sip-rtp-timeout-hold" name="rtp_timeout_hold" value="0" min="0" max="3600"
|
||
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">Délai max d'un appel en attente sans flux RTP (0 = illimité / désactivé).</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 < 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 & 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ÉENREGISTREMENT POSTES (REGISTER 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 class="space-y-1">
|
||
<div class="flex items-center gap-2 mb-0.5">
|
||
<label for="cluster-blf-expiry" class="text-xs font-bold text-white cursor-pointer">Intervalle de réenregistrement des Postes (REGISTER)</label>
|
||
<span class="px-2 py-0.5 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 leading-relaxed">
|
||
Plafond d'expiration imposé aux téléphones pour le renouvellement de leur enregistrement SIP (<code class="text-cyan-300 font-mono text-[10px]">REGISTER</code>). En cas de bascule HA, ce délai force les postes à se réenregistrer rapidement sur le serveur actif (ex. toutes les 60s pour un plafond de 120s) afin de rétablir les appels entrants et sortants sans attendre (par défaut 1800s sans HA).
|
||
</p>
|
||
<p class="text-[10px] text-amber-400/90 leading-normal flex items-start gap-1.5 pt-0.5">
|
||
<span class="text-xs">ℹ️</span>
|
||
<span><strong>Voyants BLF (<code class="text-amber-300 font-mono text-[10px]">SUBSCRIBE</code>) :</strong> Ce paramètre ne gère pas les touches BLF. La fréquence de rafraîchissement des touches de supervision est décidée directement par le téléphone lui-même (ex. paramètre Yealink <code class="text-amber-300 font-mono text-[10px]">account.X.blf.subscribe_period = 120</code>).</span>
|
||
</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 & 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 & 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>
|
||
|
||
<label class="flex items-start gap-3 p-3.5 rounded-xl bg-anthracite-950 border border-anthracite-700 hover:border-cyan-500/50 cursor-pointer transition">
|
||
<input type="checkbox" name="groundwire_enabled" id="ext-groundwire-enabled" value="1"
|
||
class="mt-0.5 rounded bg-anthracite-900 border-anthracite-600 text-cyan-500 focus:ring-cyan-500">
|
||
<div class="leading-snug">
|
||
<div class="text-xs font-bold text-white flex items-center gap-2">
|
||
<span>📱 GroundWire XML</span>
|
||
<span class="px-1.5 py-0.5 rounded text-[10px] font-semibold bg-cyan-500/15 text-cyan-300 border border-cyan-500/30">Softphone Mobile UCP</span>
|
||
</div>
|
||
<div class="text-[11px] text-slate-400 mt-0.5">
|
||
Affiche les boutons d'auto-configuration Acrobits GroundWire (lien direct mobile + téléchargement XML) sur ce poste dans l'UCP.
|
||
</div>
|
||
</div>
|
||
</label>
|
||
|
||
<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 ou host=sip.operateur.com username=mon_user secret=mon_secret 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 ex: 100rel = yes 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 ex: support_path = yes 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 ex: qualify_timeout = 3.0 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 > Remote Phonebook > 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>
|
||
|
||
<!-- ===================================================================== -->
|
||
<!-- MODAL : CONFIRMATION DE REDÉMARRAGE POSTE SIP -->
|
||
<!-- ===================================================================== -->
|
||
<div id="modal-confirm-reboot-extension" 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 -->
|
||
<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="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>
|
||
</div>
|
||
<div>
|
||
<div class="flex items-center gap-2">
|
||
<h3 class="text-base font-bold text-white">Redémarrer le Téléphone IP</h3>
|
||
<span 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">SIP NOTIFY</span>
|
||
</div>
|
||
<p class="text-xs text-slate-400 mt-0.5">
|
||
Poste <span id="reboot-modal-ext-num" class="font-mono font-bold text-indigo-300"></span> — <span id="reboot-modal-ext-name" class="font-medium text-slate-200"></span>
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<button type="button" onclick="closeRebootModal()" 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 -->
|
||
<div class="p-6 space-y-4">
|
||
<!-- Indicateur de vérification de l'état en direct -->
|
||
<div id="reboot-modal-checking" class="flex items-center gap-2.5 p-3 rounded-xl bg-anthracite-950 border border-anthracite-800 text-xs text-slate-400">
|
||
<svg class="w-4 h-4 text-indigo-400 animate-spin shrink-0" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8z"></path></svg>
|
||
<span>Vérification des appels en cours sur ce combiné...</span>
|
||
</div>
|
||
|
||
<!-- ALERTE ROUGE : Appel / conversation en cours -->
|
||
<div id="reboot-modal-call-warning" class="hidden p-4 rounded-xl bg-rose-500/15 border-2 border-rose-500/60 space-y-3">
|
||
<div class="flex items-center gap-2 text-rose-400 font-bold text-sm">
|
||
<svg class="w-5 h-5 shrink-0" 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>ATTENTION : CONVERSATION EN COURS SUR CE POSTE !</span>
|
||
</div>
|
||
<p class="text-xs text-rose-200">
|
||
Ce combiné est actuellement <strong class="underline">en communication active</strong>.
|
||
</p>
|
||
<div id="reboot-modal-call-details" class="p-2.5 rounded-lg bg-black/50 border border-rose-500/40 text-xs font-mono text-rose-300"></div>
|
||
<div class="p-3 rounded-lg bg-rose-950/60 border border-rose-500/40 text-xs font-semibold text-rose-200 flex items-start gap-2">
|
||
<span class="text-base leading-none">⚠️</span>
|
||
<span>Si vous confirmez le redémarrage, <strong>la conversation téléphonique sera immédiatement coupée</strong> pour votre interlocuteur !</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Information normale (quand pas d'appel) -->
|
||
<div id="reboot-modal-normal-info" class="hidden space-y-3">
|
||
<div class="p-3.5 rounded-xl bg-anthracite-950 border border-anthracite-800 text-xs text-slate-300 space-y-2">
|
||
<div class="flex items-center gap-2 text-emerald-400 font-semibold">
|
||
<span class="w-2 h-2 rounded-full bg-emerald-400"></span>
|
||
<span>Aucune conversation en cours (poste libre)</span>
|
||
</div>
|
||
<p class="text-slate-400">
|
||
Un ordre SIP NOTIFY standard (<code class="text-amber-300 font-mono">check-sync;reboot=true</code>) va être envoyé au téléphone.
|
||
</p>
|
||
</div>
|
||
<div class="p-3.5 rounded-xl bg-amber-500/10 border border-amber-500/20 text-xs text-amber-200 space-y-1">
|
||
<div class="font-bold">Effet du redémarrage :</div>
|
||
<p class="text-slate-300">Le téléphone va redémarrer, recharger son provisioning et synchroniser ses touches BLF (interruption d'environ 30 à 45 secondes).</p>
|
||
</div>
|
||
</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="closeRebootModal()"
|
||
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-reboot-action" onclick="confirmRebootExtension()"
|
||
class="px-5 py-2.5 rounded-xl text-xs font-bold bg-amber-600 hover:bg-amber-500 text-white shadow-lg shadow-amber-600/30 transition flex items-center gap-2">
|
||
<span id="reboot-confirm-btn-label">Confirmer le redémarrage</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ========================================================= -->
|
||
<!-- MODALE UTILISATEUR UCP (CRÉATION / ÉDITION) -->
|
||
<!-- ========================================================= -->
|
||
<div id="modal-ucp-user" class="fixed inset-0 z-50 hidden items-center justify-center bg-black/75 backdrop-blur-sm p-4">
|
||
<div class="w-full max-w-xl rounded-2xl bg-anthracite-900 border border-anthracite-700 shadow-2xl overflow-hidden">
|
||
<div class="px-6 py-4 border-b border-anthracite-800 flex items-center justify-between bg-anthracite-950/60">
|
||
<div>
|
||
<h3 id="ucp-modal-title" class="text-sm font-bold text-white">Nouvel utilisateur UCP</h3>
|
||
<p class="text-[11px] text-slate-400">Attribuez des identifiants et une ou plusieurs extensions SIP</p>
|
||
</div>
|
||
<button type="button" onclick="closeUcpUserModal()" class="text-slate-400 hover:text-white p-1.5 rounded-lg hover:bg-anthracite-800 transition">✕</button>
|
||
</div>
|
||
<form id="form-ucp-user" onsubmit="saveUcpUser(event)" class="p-6 space-y-4 max-h-[75vh] overflow-y-auto">
|
||
<input type="hidden" id="ucp-user-id" value="0">
|
||
|
||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||
<div>
|
||
<label class="block text-xs font-semibold text-slate-300 mb-1.5">Identifiant de connexion <span class="text-rose-400">*</span></label>
|
||
<input type="text" id="ucp-user-username" required autocomplete="off"
|
||
placeholder="ex: j.dupont"
|
||
class="w-full px-3.5 py-2 rounded-xl bg-anthracite-950 border border-anthracite-700 text-sm text-white focus:border-sky-500 focus:outline-none">
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-semibold text-slate-300 mb-1.5">Nom complet affiché</label>
|
||
<input type="text" id="ucp-user-display-name" autocomplete="off"
|
||
placeholder="ex: Jean Dupont"
|
||
class="w-full px-3.5 py-2 rounded-xl bg-anthracite-950 border border-anthracite-700 text-sm text-white focus:border-sky-500 focus:outline-none">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||
<div>
|
||
<label class="block text-xs font-semibold text-slate-300 mb-1.5">Adresse Email</label>
|
||
<input type="email" id="ucp-user-email" autocomplete="off"
|
||
placeholder="jean.dupont@entreprise.fr"
|
||
class="w-full px-3.5 py-2 rounded-xl bg-anthracite-950 border border-anthracite-700 text-sm text-white focus:border-sky-500 focus:outline-none">
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-semibold text-slate-300 mb-1.5">
|
||
Mot de passe <span id="ucp-password-hint" class="text-[11px] font-normal text-slate-400">(min. 6 car.)</span>
|
||
</label>
|
||
<input type="password" id="ucp-user-password" autocomplete="new-password"
|
||
placeholder="••••••••"
|
||
class="w-full px-3.5 py-2 rounded-xl bg-anthracite-950 border border-anthracite-700 text-sm text-white focus:border-sky-500 focus:outline-none">
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<div class="flex items-center justify-between mb-2">
|
||
<label class="text-xs font-semibold text-slate-300">Extensions SIP autorisées</label>
|
||
<span class="text-[11px] text-slate-400">Cochez un ou plusieurs postes</span>
|
||
</div>
|
||
<div id="ucp-user-extensions-container"
|
||
class="grid grid-cols-1 sm:grid-cols-2 gap-2 max-h-48 overflow-y-auto p-3 rounded-xl bg-anthracite-950 border border-anthracite-800">
|
||
</div>
|
||
</div>
|
||
|
||
<label class="flex items-center gap-2.5 cursor-pointer select-none pt-1">
|
||
<input type="checkbox" id="ucp-user-enabled" checked
|
||
class="w-4 h-4 rounded border-anthracite-600 bg-anthracite-950 text-sky-500 focus:ring-sky-500">
|
||
<span class="text-xs font-medium text-slate-300">Compte actif (autoriser la connexion au portail UCP)</span>
|
||
</label>
|
||
|
||
<div class="pt-3 border-t border-anthracite-800 flex items-center justify-end gap-3">
|
||
<button type="button" onclick="closeUcpUserModal()"
|
||
class="px-4 py-2 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="submit" id="btn-save-ucp-user"
|
||
class="px-5 py-2 rounded-xl text-xs font-bold bg-sky-600 hover:bg-sky-500 text-white shadow-lg shadow-sky-600/20 transition">
|
||
Enregistrer l'utilisateur
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="/assets/app.js?v=<?= filemtime(__DIR__ . '/assets/app.js') ?>"></script>
|
||
</body>
|
||
</html>
|