v2.6.7: Replace Admin & UCP emojis with uniform Heroicons SVG icons
This commit is contained in:
1 parent
70a021ef63
commit
ddbaabb657
5 files changed
+198
-124
No files matched your search
+74
-49
@@ -28,6 +28,31 @@ const chartHistory = {
|
||||
|
||||
let metricsTimer = null;
|
||||
|
||||
function adminIcon(name, cls = 'w-3.5 h-3.5') {
|
||||
const paths = {
|
||||
shield: '<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75m-3-7.036A11.959 11.959 0 013.598 6 11.99 11.99 0 003 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285z"/>',
|
||||
lock: '<path stroke-linecap="round" stroke-linejoin="round" d="M16.5 10.5V6.75a4.5 4.5 0 10-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H6.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z"/>',
|
||||
unlock: '<path stroke-linecap="round" stroke-linejoin="round" d="M13.5 10.5V6.75a4.5 4.5 0 119 0v3.75M3.75 21.75h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H3.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z"/>',
|
||||
warning: '<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"/>',
|
||||
phone: '<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 6.75c0 8.284 6.716 15 15 15h2.25a2.25 2.25 0 002.25-2.25v-1.372c0-.516-.351-.966-.852-1.091l-4.423-1.106c-.44-.11-.902.055-1.173.417l-.97 1.293c-.282.376-.769.542-1.21.38a12.035 12.035 0 01-7.143-7.143c-.162-.441.004-.928.38-1.21l1.293-.97c.363-.271.527-.734.417-1.173L6.963 3.102a1.125 1.125 0 00-1.091-.852H4.5A2.25 2.25 0 002.25 4.5v2.25z"/>',
|
||||
mobile: '<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 1.5H8.25A2.25 2.25 0 006 3.75v16.5a2.25 2.25 0 002.25 2.25h7.5A2.25 2.25 0 0018 20.25V3.75a2.25 2.25 0 00-2.25-2.25H13.5m-3 0V3h3V1.5m-3 0h3m-3 18.75h3"/>',
|
||||
building: '<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 21h16.5M4.5 3h15M5.25 3v18m13.5-18v18M9 6.75h1.5m-1.5 3h1.5m-1.5 3h1.5m3-6H15m-1.5 3H15m-1.5 3H15M9 21v-3.375c0-.621.504-1.125 1.125-1.125h3.75c.621 0 1.125.504 1.125 1.125V21"/>',
|
||||
voicemail: '<path stroke-linecap="round" stroke-linejoin="round" d="M21.75 9v.906a2.25 2.25 0 01-1.183 1.981l-6.478 3.488M2.25 9v.906a2.25 2.25 0 001.183 1.981l6.478 3.488m8.839 2.51l-4.66-2.51m0 0l-1.023-.55a2.25 2.25 0 00-2.134 0l-1.022.55m0 0l-4.661 2.51m16.5 1.615a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V8.844a2.25 2.25 0 011.183-1.98l7.5-4.04a2.25 2.25 0 012.134 0l7.5 4.04a2.25 2.25 0 011.183 1.98V19.5z"/>',
|
||||
music: '<path stroke-linecap="round" stroke-linejoin="round" d="M9 9l10.5-3m0 6.553v3.75a2.25 2.25 0 01-1.632 2.163l-1.32.377a1.803 1.803 0 11-.99-3.467l2.31-.66a2.25 2.25 0 001.632-2.163zm0 0V2.25L9 5.25v10.303m0 0v3.75a2.25 2.25 0 01-1.632 2.163l-1.32.377a1.803 1.803 0 01-.99-3.467l2.31-.66A2.25 2.25 0 009 15.553z"/>',
|
||||
refresh: '<path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"/>',
|
||||
trash: '<path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"/>',
|
||||
tag: '<path stroke-linecap="round" stroke-linejoin="round" d="M9.568 3H5.25A2.25 2.25 0 003 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.095 18.095 0 005.223-5.223c.542-.827.369-1.908-.33-2.607L11.16 3.66A2.25 2.25 0 009.568 3z"/>',
|
||||
ban: '<path stroke-linecap="round" stroke-linejoin="round" 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"/>',
|
||||
speaker: '<path stroke-linecap="round" stroke-linejoin="round" d="M19.114 5.636a9 9 0 010 12.728M16.463 8.288a5.25 5.25 0 010 7.424M6.75 8.25l4.72-4.72a.75.75 0 011.28.53v15.88a.75.75 0 01-1.28.53l-4.72-4.72H4.51c-.88 0-1.704-.507-1.938-1.354A9.01 9.01 0 012.25 12c0-.83.112-1.633.322-2.396C2.806 8.756 3.63 8.25 4.51 8.25H6.75z"/>',
|
||||
rocket: '<path stroke-linecap="round" stroke-linejoin="round" d="M15.59 14.37a6 6 0 01-5.84 7.38v-4.8m5.84-2.58a14.98 14.98 0 006.16-12.12A14.98 14.98 0 009.631 8.41m5.96 5.96a14.926 14.926 0 01-5.841 2.58m-.119-8.54a6 6 0 00-7.381 5.84h4.8m2.581-5.84a14.927 14.927 0 00-2.58 5.84m2.699 2.7c-.103.021-.207.041-.311.06a15.09 15.09 0 01-2.448-2.448 14.9 14.9 0 01.06-.312m-2.24 2.39a4.493 4.493 0 00-1.757 4.306 4.493 4.493 0 004.306-1.758M16.5 9a1.5 1.5 0 11-3 0 1.5 1.5 0 013 0z"/>',
|
||||
emergency: '<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m0-10.036A11.959 11.959 0 013.598 6 11.99 11.99 0 003 9.75c0 5.592 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.57-.598-3.75h-.152c-3.196 0-6.1-1.249-8.25-3.286zm0 13.036h.008v.008H12v-.008z"/>',
|
||||
clock: '<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z"/>',
|
||||
globe: '<path stroke-linecap="round" stroke-linejoin="round" d="M12 21a9.004 9.004 0 008.716-6.747M12 21a9.004 9.004 0 01-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3"/>',
|
||||
dotGreen: '<circle cx="12" cy="12" r="5" fill="currentColor" stroke="none"/>',
|
||||
};
|
||||
return `<svg class="${cls} shrink-0 inline-block" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24">${paths[name] || paths.tag}</svg>`;
|
||||
}
|
||||
|
||||
// --- Notifications Toast Tailwind ---
|
||||
function showToast(message, type = 'success') {
|
||||
const container = document.getElementById('toast-container');
|
||||
@@ -765,11 +790,11 @@ function renderDashboardPhonesTable() {
|
||||
const encMode = Number(ext.encryption ?? (isTls ? 1 : 0));
|
||||
let srtpBadge = '';
|
||||
if (encMode === 2) {
|
||||
srtpBadge = `<span class="inline-flex items-center gap-1 px-1.5 py-0.2 rounded text-[10px] font-semibold bg-emerald-500/15 text-emerald-300 border border-emerald-500/30" title="Chiffrement média SRTP obligatoire (AES-128)">🛡️ SRTP Forcé</span>`;
|
||||
srtpBadge = `<span class="inline-flex items-center gap-1 px-1.5 py-0.2 rounded text-[10px] font-semibold bg-emerald-500/15 text-emerald-300 border border-emerald-500/30" title="Chiffrement média SRTP obligatoire (AES-128)">${adminIcon('shield', 'w-3 h-3')} SRTP Forcé</span>`;
|
||||
} else if (encMode === 1) {
|
||||
srtpBadge = `<span class="inline-flex items-center gap-1 px-1.5 py-0.2 rounded text-[10px] font-semibold bg-cyan-500/15 text-cyan-300 border border-cyan-500/30" title="Chiffrement média SRTP négociable si supporté par le poste">🛡️ SRTP Négociable</span>`;
|
||||
srtpBadge = `<span class="inline-flex items-center gap-1 px-1.5 py-0.2 rounded text-[10px] font-semibold bg-cyan-500/15 text-cyan-300 border border-cyan-500/30" title="Chiffrement média SRTP négociable si supporté par le poste">${adminIcon('shield', 'w-3 h-3')} SRTP Négociable</span>`;
|
||||
} else {
|
||||
srtpBadge = `<span class="inline-flex items-center gap-1 px-1.5 py-0.2 rounded text-[10px] font-medium bg-anthracite-900 text-slate-400 border border-anthracite-700" title="Voix en clair (RTP standard)">🔈 RTP Clair</span>`;
|
||||
srtpBadge = `<span class="inline-flex items-center gap-1 px-1.5 py-0.2 rounded text-[10px] font-medium bg-anthracite-900 text-slate-400 border border-anthracite-700" title="Voix en clair (RTP standard)">${adminIcon('speaker', 'w-3 h-3')} RTP Clair</span>`;
|
||||
}
|
||||
|
||||
// Détection dynamique du mode réel négocié en direct par le téléphone
|
||||
@@ -782,21 +807,21 @@ function renderDashboardPhonesTable() {
|
||||
if (actTr === 'tls') {
|
||||
trPill = `
|
||||
<span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-bold bg-emerald-500/20 text-emerald-300 border border-emerald-500/40 shadow-sm shadow-emerald-500/10">
|
||||
🔒 TLS Actif (Chiffré)
|
||||
${adminIcon('lock', 'w-3 h-3')} TLS Actif (Chiffré)
|
||||
</span>
|
||||
`;
|
||||
profileNote = `<span class="${isTls ? 'text-emerald-300 font-semibold' : 'text-slate-300'}">${isTls ? 'TLS Strict' : 'UDP Interne'}</span>`;
|
||||
} else if (actTr === 'wss') {
|
||||
trPill = `
|
||||
<span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-bold bg-cyan-500/20 text-cyan-300 border border-cyan-500/40">
|
||||
🔒 WSS WebRTC
|
||||
${adminIcon('lock', 'w-3 h-3')} WSS WebRTC
|
||||
</span>
|
||||
`;
|
||||
profileNote = `<span class="text-cyan-300 font-semibold">WebRTC</span>`;
|
||||
} else if (actTr === 'tcp') {
|
||||
trPill = `
|
||||
<span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-semibold bg-indigo-500/20 text-indigo-300 border border-indigo-500/35">
|
||||
🔓 TCP Actif (Clair)
|
||||
${adminIcon('unlock', 'w-3 h-3')} TCP Actif (Clair)
|
||||
</span>
|
||||
`;
|
||||
profileNote = `<span class="text-slate-300">${isTls ? 'TLS Strict' : 'UDP Interne'}</span>`;
|
||||
@@ -804,10 +829,10 @@ function renderDashboardPhonesTable() {
|
||||
// UDP
|
||||
trPill = `
|
||||
<span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-semibold ${isTls ? 'bg-amber-500/20 text-amber-300 border border-amber-500/40' : 'bg-anthracite-750 text-slate-200 border border-anthracite-600'}">
|
||||
🔓 UDP Actif (Clair)
|
||||
${adminIcon('unlock', 'w-3 h-3')} UDP Actif (Clair)
|
||||
</span>
|
||||
`;
|
||||
profileNote = isTls ? '<span class="text-amber-400 font-semibold">⚠️ Exige TLS</span>' : '<span class="text-slate-400">UDP Interne</span>';
|
||||
profileNote = isTls ? `<span class="text-amber-400 font-semibold inline-flex items-center gap-1">${adminIcon('warning', 'w-3 h-3')} Exige TLS</span>` : '<span class="text-slate-400">UDP Interne</span>';
|
||||
}
|
||||
|
||||
modeBadge = `
|
||||
@@ -853,7 +878,7 @@ function renderDashboardPhonesTable() {
|
||||
contactHtml = `
|
||||
<div>
|
||||
<div class="font-mono text-xs text-slate-200 font-semibold">${escapeHtml(cleanIp)}</div>
|
||||
${ua ? `<div class="text-[11px] text-indigo-300 font-medium truncate max-w-[240px]" title="${ua}">📱 ${ua}</div>` : ''}
|
||||
${ua ? `<div class="text-[11px] text-indigo-300 font-medium truncate max-w-[240px]" title="${ua}">${adminIcon('mobile', 'w-3 h-3 text-indigo-400')} ${ua}</div>` : ''}
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
@@ -909,10 +934,10 @@ function renderExtensionsTable() {
|
||||
encLabel = '<span class="text-[10px] text-slate-400">RTP Clair</span>';
|
||||
}
|
||||
const badge = isTls
|
||||
? `<span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-semibold bg-emerald-500/15 text-emerald-300 border border-emerald-500/30">🔒 TLS (${encLabel})</span>`
|
||||
? `<span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-semibold bg-emerald-500/15 text-emerald-300 border border-emerald-500/30">${adminIcon('lock', 'w-3.5 h-3.5 text-emerald-400')} TLS (${encLabel})</span>`
|
||||
: (encMode > 0
|
||||
? `<span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-semibold bg-indigo-500/15 text-indigo-300 border border-indigo-500/30">🛡️ UDP (${encLabel})</span>`
|
||||
: `<span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-semibold bg-anthracite-750 text-slate-200 border border-anthracite-600">🏢 UDP (RTP Clair)</span>`);
|
||||
? `<span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-semibold bg-indigo-500/15 text-indigo-300 border border-indigo-500/30">${adminIcon('shield', 'w-3.5 h-3.5 text-indigo-400')} UDP (${encLabel})</span>`
|
||||
: `<span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-semibold bg-anthracite-750 text-slate-200 border border-anthracite-600">${adminIcon('building', 'w-3.5 h-3.5 text-slate-400')} UDP (RTP Clair)</span>`);
|
||||
|
||||
const actTr = c ? (c.active_transport || 'udp').toUpperCase() : '';
|
||||
const stLower = c ? String(c.status).toLowerCase() : '';
|
||||
@@ -935,7 +960,7 @@ function renderExtensionsTable() {
|
||||
: `<span class="text-slate-500 text-xs italic">Aucun</span>`;
|
||||
|
||||
const vmBadge = Number(ext.voicemail_enabled) === 1
|
||||
? `<span class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] font-semibold bg-amber-500/15 text-amber-300 border border-amber-500/30" title="Boîte vocale active (${ext.ring_time || 20}s)">📟 Boîte</span>`
|
||||
? `<span class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] font-semibold bg-amber-500/15 text-amber-300 border border-amber-500/30" title="Boîte vocale active (${ext.ring_time || 20}s)">${adminIcon('voicemail', 'w-3 h-3')} Boîte</span>`
|
||||
: '';
|
||||
const gwBadge = Number(ext.groundwire_enabled) === 1
|
||||
? `<span class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] font-semibold bg-cyan-500/15 text-cyan-300 border border-cyan-500/30" title="Provisionnement XML GroundWire activé dans l'UCP">GroundWire</span>`
|
||||
@@ -1256,7 +1281,7 @@ function renderGroupsTable() {
|
||||
? members.map(m => `<span class="px-2 py-0.5 rounded bg-indigo-500/20 text-indigo-200 border border-indigo-500/30 font-mono text-xs font-semibold">${escapeHtml(m)}</span>`).join(' ')
|
||||
: '<span class="text-slate-500 text-xs italic">Aucun membre</span>';
|
||||
const failoverLabel = String(grp.failover_dest || '').startsWith('vm-')
|
||||
? `<span class="px-2 py-0.5 rounded bg-violet-500/20 text-violet-200 border border-violet-500/30 font-mono text-xs font-semibold">📬 Répondeur ${escapeHtml(String(grp.failover_dest).slice(3))}</span>`
|
||||
? `<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded bg-violet-500/20 text-violet-200 border border-violet-500/30 font-mono text-xs font-semibold">${adminIcon('voicemail', 'w-3.5 h-3.5')} Répondeur ${escapeHtml(String(grp.failover_dest).slice(3))}</span>`
|
||||
: `<code class="text-slate-300">${escapeHtml(grp.failover_dest)}</code>`;
|
||||
|
||||
return `
|
||||
@@ -1773,7 +1798,7 @@ function renderTrunksTable() {
|
||||
|
||||
const mohSound = (appState.sounds || []).find(s => Number(s.id) === Number(t.moh_sound_id));
|
||||
const mohBadge = mohSound
|
||||
? `<span class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded bg-amber-500/15 text-amber-300 border border-amber-500/30 text-[10px] font-semibold">🎵 ${escapeHtml(mohSound.name)}</span>`
|
||||
? `<span class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded bg-amber-500/15 text-amber-300 border border-amber-500/30 text-[10px] font-semibold">${adminIcon('music', 'w-3 h-3')} ${escapeHtml(mohSound.name)}</span>`
|
||||
: `<span class="text-[10px] text-slate-500">MoH : Globale</span>`;
|
||||
|
||||
return `
|
||||
@@ -1803,12 +1828,12 @@ function renderTrunksTable() {
|
||||
${t.cid_name_prefix ? `<span class="px-1.5 py-0.5 rounded bg-emerald-500/20 text-emerald-300 text-[10px] font-mono mr-1 font-semibold">CID: ${escapeHtml(t.cid_name_prefix)}</span>` : ''}
|
||||
${didCount > 0 ? `<span class="text-indigo-300 font-semibold">${didCount} règle(s) entrante(s)</span>` : 'Tous numéros (Any)'}
|
||||
${blockCount > 0 ? `<span class="ml-1 px-1.5 py-0.5 rounded bg-amber-500/20 text-amber-300 text-[10px] font-semibold">${blockCount} blocage(s) CID</span>` : ''}
|
||||
${tcCount > 0 ? `<span class="ml-1 px-1.5 py-0.5 rounded bg-amber-500/20 text-amber-300 text-[10px] font-semibold">⏱️ ${tcCount} horaire(s)</span>` : ''}
|
||||
${tcCount > 0 ? `<span class="ml-1 inline-flex items-center gap-1 px-1.5 py-0.5 rounded bg-amber-500/20 text-amber-300 text-[10px] font-semibold">${adminIcon('clock', 'w-3 h-3')} ${tcCount} horaire(s)</span>` : ''}
|
||||
</div>
|
||||
<div class="mt-1">${mohBadge}</div>
|
||||
</td>
|
||||
<td class="py-3.5 px-4 text-right space-x-2">
|
||||
${wantsReg ? `<button type="button" onclick="forceRegisterTrunk('${escapeHtml(slug)}-reg')" title="Forcer l'envoi immédiat d'un SIP REGISTER vers l'opérateur pour reprendre la ligne" class="px-2 py-1 rounded-lg bg-cyan-500/15 hover:bg-cyan-500/25 border border-cyan-500/30 text-xs font-semibold text-cyan-300 transition">🔄 Re-Register</button>` : ''}
|
||||
${wantsReg ? `<button type="button" onclick="forceRegisterTrunk('${escapeHtml(slug)}-reg')" title="Forcer l'envoi immédiat d'un SIP REGISTER vers l'opérateur pour reprendre la ligne" class="inline-flex items-center gap-1 px-2 py-1 rounded-lg bg-cyan-500/15 hover:bg-cyan-500/25 border border-cyan-500/30 text-xs font-semibold text-cyan-300 transition">${adminIcon('refresh', 'w-3.5 h-3.5')} Re-Register</button>` : ''}
|
||||
<button type="button" onclick="editTrunk(${Number(t.id)}, 'inbound')" class="text-xs font-semibold text-emerald-300 hover:text-emerald-200">Routes Entrantes</button>
|
||||
<button type="button" onclick="editTrunk(${Number(t.id)})" class="text-xs font-semibold text-indigo-300 hover:text-indigo-200">Modifier</button>
|
||||
<button type="button" onclick="deleteTrunk(${Number(t.id)})" class="text-xs font-semibold text-red-400 hover:text-red-300">Supprimer</button>
|
||||
@@ -2230,11 +2255,11 @@ function addTrunkInboundRouteRow(cid = 'Any', did = 'Any', description = '', des
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-span-2 flex items-center justify-end gap-1.5">
|
||||
<button type="button" class="btn-toggle-time px-2 py-1.5 rounded-lg text-xs font-semibold border transition" title="Configurer les horaires d'ouverture (Mode Jour / Nuit)">
|
||||
⏱️ <span class="hidden sm:inline">Horaires</span>
|
||||
<button type="button" class="btn-toggle-time inline-flex items-center gap-1 px-2 py-1.5 rounded-lg text-xs font-semibold border transition" title="Configurer les horaires d'ouverture (Mode Jour / Nuit)">
|
||||
${adminIcon('clock', 'w-3.5 h-3.5')} <span class="hidden sm:inline">Horaires</span>
|
||||
</button>
|
||||
<button type="button" title="Dupliquer cette règle" class="btn-dup-route p-1.5 rounded-lg bg-anthracite-950 hover:bg-indigo-500/20 text-slate-300 hover:text-indigo-300 border border-anthracite-700 text-xs">⧉</button>
|
||||
<button type="button" title="Supprimer cette règle" class="btn-del-route p-1.5 rounded-lg bg-anthracite-950 hover:bg-red-500/20 text-slate-300 hover:text-red-300 border border-anthracite-700 text-xs">🗑️</button>
|
||||
<button type="button" title="Supprimer cette règle" class="btn-del-route p-1.5 rounded-lg bg-anthracite-950 hover:bg-red-500/20 text-slate-300 hover:text-red-300 border border-anthracite-700 text-xs">${adminIcon('trash', 'w-3.5 h-3.5')}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2278,7 +2303,7 @@ function addTrunkInboundRouteRow(cid = 'Any', did = 'Any', description = '', des
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2 pt-2 border-t border-anthracite-800 text-xs">
|
||||
<label class="text-[11px] font-semibold text-slate-300 flex items-center gap-1">
|
||||
<span>🏷️ Préfixe CID spécifique à cette règle :</span>
|
||||
<span>${adminIcon('tag', 'w-3.5 h-3.5 text-amber-400')} Préfixe CID spécifique à cette règle :</span>
|
||||
</label>
|
||||
<input type="text" placeholder="ex: [Ligne-1] (vide = hériter du Trunk)" value="${escapeHtml(tcCidPrefix)}"
|
||||
class="trunk-did-prefix px-2.5 py-1 rounded bg-anthracite-900 border border-anthracite-700 text-xs text-white font-mono w-56 focus:outline-none focus:border-indigo-500">
|
||||
@@ -2307,7 +2332,7 @@ function addTrunkInboundRouteRow(cid = 'Any', did = 'Any', description = '', des
|
||||
if (isEnabled) {
|
||||
toggleBtn.className = 'btn-toggle-time px-2 py-1.5 rounded-lg text-xs font-semibold bg-amber-500/20 text-amber-300 border border-amber-500/40 transition shadow-sm';
|
||||
const daysLabel = tcWeekdaysSelect.value === 'mon-fri' ? 'Lun-Ven' : (tcWeekdaysSelect.value === 'mon-sat' ? 'Lun-Sam' : (tcWeekdaysSelect.value === '*' ? '7j/7' : 'Sam-Dim'));
|
||||
badgeContainer.innerHTML = `<span class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] font-semibold bg-amber-500/15 text-amber-300 border border-amber-500/30">⏱️ ${escapeHtml(tcStartInput.value)}-${escapeHtml(tcFinishInput.value)} (${escapeHtml(daysLabel)})</span>`;
|
||||
badgeContainer.innerHTML = `<span class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] font-semibold bg-amber-500/15 text-amber-300 border border-amber-500/30">${adminIcon('clock', 'w-3 h-3')} ${escapeHtml(tcStartInput.value)}-${escapeHtml(tcFinishInput.value)} (${escapeHtml(daysLabel)})</span>`;
|
||||
} else {
|
||||
toggleBtn.className = 'btn-toggle-time px-2 py-1.5 rounded-lg text-xs font-semibold bg-anthracite-950 hover:bg-anthracite-900 text-slate-400 hover:text-white border border-anthracite-700 transition';
|
||||
badgeContainer.innerHTML = '';
|
||||
@@ -2717,10 +2742,10 @@ function openTrunkModal(trunk = null) {
|
||||
if (mohSelect) {
|
||||
const mohSounds = (appState.sounds || []).filter(s => s.usage_type === 'moh');
|
||||
const curMohId = trunk ? Number(trunk.moh_sound_id || 0) : 0;
|
||||
let opts = `<option value="0" ${curMohId === 0 ? 'selected' : ''}>🌐 Hériter de la musique générale par défaut</option>`;
|
||||
let opts = `<option value="0" ${curMohId === 0 ? 'selected' : ''}>Hériter de la musique générale par défaut</option>`;
|
||||
mohSounds.forEach(s => {
|
||||
const isSel = (Number(s.id) === curMohId) ? 'selected' : '';
|
||||
opts += `<option value="${s.id}" ${isSel}>🎵 ${escapeHtml(s.name)} (${escapeHtml(s.site_category || 'Global')})</option>`;
|
||||
opts += `<option value="${s.id}" ${isSel}>${escapeHtml(s.name)} (${escapeHtml(s.site_category || 'Global')})</option>`;
|
||||
});
|
||||
mohSelect.innerHTML = opts;
|
||||
}
|
||||
@@ -2821,7 +2846,7 @@ function renderOutboundRoutesTable() {
|
||||
<span class="text-[10px] text-slate-400">${tIdx + 1}.</span>${escapeHtml(t)}
|
||||
</span>
|
||||
`).join(' <span class="text-slate-500 text-xs">→</span> ')
|
||||
: `<span class="text-amber-400 text-xs font-semibold">⚠️ Aucun Trunk associé</span>`;
|
||||
: `<span class="text-amber-400 text-xs font-semibold">${adminIcon('warning', 'w-3.5 h-3.5')} Aucun Trunk associé</span>`;
|
||||
|
||||
const isRestricted = r.permission_mode === 'restricted';
|
||||
const allowedGrps = Array.isArray(r.allowed_groups) ? r.allowed_groups : [];
|
||||
@@ -2846,7 +2871,7 @@ function renderOutboundRoutesTable() {
|
||||
const patPreview = patterns.slice(0, 2).map(p => escapeHtml(p.match_pattern || '')).filter(Boolean).join(', ');
|
||||
|
||||
const rTypeBadge = r.route_type === 'emergency'
|
||||
? `<span class="px-2 py-0.5 rounded-full text-[10px] font-bold bg-amber-500/20 text-amber-300 border border-amber-500/40">🚑 Urgence</span>`
|
||||
? `<span class="px-2 py-0.5 rounded-full text-[10px] font-bold bg-amber-500/20 text-amber-300 border border-amber-500/40">${adminIcon('emergency', 'w-3 h-3')} Urgence</span>`
|
||||
: (r.route_type === 'intracompany'
|
||||
? `<span class="px-2 py-0.5 rounded-full text-[10px] font-bold bg-cyan-500/20 text-cyan-300 border border-cyan-500/40">Intra</span>`
|
||||
: `<span class="px-2 py-0.5 rounded-full text-[10px] font-medium bg-anthracite-950 text-slate-300 border border-anthracite-700">Standard</span>`);
|
||||
@@ -2941,19 +2966,19 @@ function openOutboundRouteModal(route = null) {
|
||||
if (congSel) {
|
||||
const curCong = route ? route.congestion_dest : 'all-circuits-busy';
|
||||
let congOpts = `
|
||||
<optgroup label="📢 Messages Vocaux (Recommandé)">
|
||||
<option value="all-circuits-busy" ${(curCong === 'all-circuits-busy' || (!route && curCong !== 'terminate-congestion')) ? 'selected' : ''}>📢 Message vocal standard (« Toutes les lignes sont occupées »)</option>
|
||||
<optgroup label="Messages Vocaux (Recommandé)">
|
||||
<option value="all-circuits-busy" ${(curCong === 'all-circuits-busy' || (!route && curCong !== 'terminate-congestion')) ? 'selected' : ''}>Message vocal standard (« Toutes les lignes sont occupées »)</option>
|
||||
`;
|
||||
const annSounds = (appState.sounds || []).filter(s => s.usage_type !== 'moh');
|
||||
if (annSounds.length > 0) {
|
||||
annSounds.forEach(s => {
|
||||
const val = `sound-${s.id}`;
|
||||
congOpts += `<option value="${val}" ${curCong === val ? 'selected' : ''}>🎙️ Annonce : ${escapeHtml(s.name)}</option>`;
|
||||
congOpts += `<option value="${val}" ${curCong === val ? 'selected' : ''}>Annonce : ${escapeHtml(s.name)}</option>`;
|
||||
});
|
||||
}
|
||||
congOpts += `
|
||||
</optgroup>
|
||||
<optgroup label="📴 Tonalités & Raccrochage">
|
||||
<optgroup label="Tonalités & Raccrochage">
|
||||
<option value="terminate-congestion" ${curCong === 'terminate-congestion' ? 'selected' : ''}>Tonalité de Congestion (Bip rapide réseau saturé)</option>
|
||||
<option value="terminate-busy" ${curCong === 'terminate-busy' ? 'selected' : ''}>Tonalité d'Occupation (Bip occupé)</option>
|
||||
<option value="terminate-hangup" ${curCong === 'terminate-hangup' ? 'selected' : ''}>Raccrocher immédiatement</option>
|
||||
@@ -2967,7 +2992,7 @@ function openOutboundRouteModal(route = null) {
|
||||
if (mohSel) {
|
||||
const curMoh = route ? (route.moh_class || 'default') : 'default';
|
||||
const mohSounds = (appState.sounds || []).filter(s => s.usage_type === 'moh');
|
||||
let mohOpts = `<option value="default" ${curMoh === 'default' ? 'selected' : ''}>🌐 Hériter du Trunk / Global (Recommandé)</option>`;
|
||||
let mohOpts = `<option value="default" ${curMoh === 'default' ? 'selected' : ''}>Hériter du Trunk / Global (Recommandé)</option>`;
|
||||
mohSounds.forEach(s => {
|
||||
const sSlug = slugifyTrunkName(s.site_category || 'global');
|
||||
const cName = (sSlug === 'global' || sSlug === 'default') ? 'default' : `moh-${sSlug}`;
|
||||
@@ -3092,7 +3117,7 @@ function addOutboundTrunkSeqRow(selectedTrunk = '') {
|
||||
<select class="ob-trunk-select flex-1 px-3 py-2 rounded-lg bg-anthracite-900 border border-anthracite-700 text-xs text-white font-medium">
|
||||
${optsHtml || '<option value="">Aucun Trunk disponible (Créez un Trunk)</option>'}
|
||||
</select>
|
||||
<button type="button" class="btn-del-trunk-row p-2 rounded-lg bg-anthracite-900 hover:bg-red-500/20 text-slate-400 hover:text-red-300 border border-anthracite-700 text-xs">🗑️</button>
|
||||
<button type="button" class="btn-del-trunk-row p-2 rounded-lg bg-anthracite-900 hover:bg-red-500/20 text-slate-400 hover:text-red-300 border border-anthracite-700 text-xs">${adminIcon('trash', 'w-3.5 h-3.5')}</button>
|
||||
`;
|
||||
|
||||
row.querySelector('.btn-del-trunk-row').addEventListener('click', () => {
|
||||
@@ -3139,7 +3164,7 @@ function addOutboundPatternRow(prepend = '', prefix = '', match = '', cid = '')
|
||||
class="ob-pat-cid w-full px-2.5 py-1.5 rounded-lg bg-anthracite-950 border border-anthracite-700 text-xs text-indigo-300 font-mono">
|
||||
</div>
|
||||
<div class="col-span-1 text-right">
|
||||
<button type="button" class="btn-del-pat p-1.5 rounded-lg bg-anthracite-950 hover:bg-red-500/20 text-slate-400 hover:text-red-300 border border-anthracite-700 text-xs">🗑️</button>
|
||||
<button type="button" class="btn-del-pat p-1.5 rounded-lg bg-anthracite-950 hover:bg-red-500/20 text-slate-400 hover:text-red-300 border border-anthracite-700 text-xs">${adminIcon('trash', 'w-3.5 h-3.5')}</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -3322,8 +3347,8 @@ function renderIvrsTable() {
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-3.5 px-4 text-xs text-slate-300">
|
||||
<div>⏱️ Attente : <strong class="text-white">${Number(ivr.timeout)}s</strong> <span class="text-slate-400">(${Number(ivr.timeout_retries || 2)} rép)</span></div>
|
||||
<div class="text-[11px] text-slate-400 mt-0.5">⚠️ Erreur : max <strong class="text-slate-200">${Number(ivr.invalid_retries || 3)}</strong> essais</div>
|
||||
<div class="inline-flex items-center gap-1">${adminIcon('clock', 'w-3.5 h-3.5 text-amber-400')} Attente : <strong class="text-white">${Number(ivr.timeout)}s</strong> <span class="text-slate-400">(${Number(ivr.timeout_retries || 2)} rép)</span></div>
|
||||
<div class="text-[11px] text-slate-400 mt-0.5">${adminIcon('warning', 'w-3.5 h-3.5 text-amber-400')} Erreur : max <strong class="text-slate-200">${Number(ivr.invalid_retries || 3)}</strong> essais</div>
|
||||
</td>
|
||||
<td class="py-3.5 px-4">
|
||||
<span class="inline-flex items-center gap-1 px-2.5 py-1 rounded-lg bg-indigo-500/10 text-indigo-300 border border-indigo-500/30 text-xs font-mono font-bold" title="Composer ce code depuis un poste interne pour tester le SVI">
|
||||
@@ -3397,7 +3422,7 @@ function openIvrModal(ivr = null) {
|
||||
let audioHtml = '';
|
||||
|
||||
if (sounds.length > 0) {
|
||||
audioHtml += `<optgroup label="📂 Fichiers sons de la médiathèque">`;
|
||||
audioHtml += `<optgroup label="Fichiers sons de la médiathèque">`;
|
||||
sounds.forEach(s => {
|
||||
const val = `custom/${s.slug}`;
|
||||
const isSel = curAudio === val || curAudio === s.slug || curAudio === String(s.id);
|
||||
@@ -3407,7 +3432,7 @@ function openIvrModal(ivr = null) {
|
||||
}
|
||||
|
||||
audioHtml += `
|
||||
<optgroup label="🔔 Messages système Asterisk par défaut">
|
||||
<optgroup label="Messages système Asterisk par défaut">
|
||||
<option value="demo-instruct" ${curAudio === 'demo-instruct' || !curAudio ? 'selected' : ''}>demo-instruct (Instructions d'accueil)</option>
|
||||
<option value="hello-world" ${curAudio === 'hello-world' ? 'selected' : ''}>hello-world (Message d'accueil basique)</option>
|
||||
<option value="beep" ${curAudio === 'beep' ? 'selected' : ''}>beep (Simple bip sonore)</option>
|
||||
@@ -5629,7 +5654,7 @@ function renderFirewall() {
|
||||
return `
|
||||
<tr class="hover:bg-anthracite-750 transition">
|
||||
<td class="px-4 py-3 font-mono font-bold text-white text-sm">
|
||||
<span class="text-rose-400">⛔ ${escapeHtml(b.ip)}</span>
|
||||
<span class="text-rose-400">${adminIcon('ban', 'w-3.5 h-3.5')} ${escapeHtml(b.ip)}</span>
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<span class="px-2 py-0.5 rounded-full text-[10px] font-bold ${jailClass}">${escapeHtml(b.jail_label || b.jail)}</span>
|
||||
@@ -6260,7 +6285,7 @@ async function checkSystemUpdate(notify = false) {
|
||||
|
||||
if (statusText) {
|
||||
statusText.className = 'text-amber-400 font-bold';
|
||||
statusText.innerHTML = `🚀 Nouvelle version disponible : <span class="underline">v${escapeHtml(data.latest_version)}</span> !`;
|
||||
statusText.innerHTML = `${adminIcon('rocket', 'w-3.5 h-3.5 text-indigo-300')} Nouvelle version disponible : <span class="underline">v${escapeHtml(data.latest_version)}</span> !`;
|
||||
}
|
||||
if (btnRun) {
|
||||
btnRun.style.display = 'inline-flex';
|
||||
@@ -6911,13 +6936,13 @@ async function refreshClusterStatus() {
|
||||
if (svcKeepalived) {
|
||||
if (!st.keepalived_installed) {
|
||||
svcKeepalived.className = 'font-mono text-amber-400 font-bold';
|
||||
svcKeepalived.textContent = '⚠️ Non installé';
|
||||
svcKeepalived.innerHTML = adminIcon('warning', 'w-3 h-3 text-amber-400') + ' Non installé';
|
||||
} else if (st.keepalived_running) {
|
||||
svcKeepalived.className = 'font-mono text-emerald-400 font-bold';
|
||||
svcKeepalived.textContent = '🟢 Actif (En cours)';
|
||||
svcKeepalived.innerHTML = '<span class="inline-block w-2 h-2 rounded-full bg-emerald-400 mr-1.5"></span>Actif (En cours)';
|
||||
} else {
|
||||
svcKeepalived.className = 'font-mono text-slate-400';
|
||||
svcKeepalived.textContent = '⚪ Arrêté';
|
||||
svcKeepalived.innerHTML = '<span class="inline-block w-2 h-2 rounded-full bg-slate-400 mr-1.5"></span>Arrêté';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6925,13 +6950,13 @@ async function refreshClusterStatus() {
|
||||
if (svcAsterisk) {
|
||||
if (st.asterisk_healthy) {
|
||||
svcAsterisk.className = 'font-mono text-emerald-400 font-bold';
|
||||
svcAsterisk.textContent = '🟢 En ligne (Ping Pong OK)';
|
||||
svcAsterisk.innerHTML = '<span class="inline-block w-2 h-2 rounded-full bg-emerald-400 mr-1.5"></span>En ligne (Ping Pong OK)';
|
||||
} else if (st.asterisk_running) {
|
||||
svcAsterisk.className = 'font-mono text-amber-400 font-bold';
|
||||
svcAsterisk.textContent = '🟡 En cours (Ping en attente)';
|
||||
svcAsterisk.innerHTML = '<span class="inline-block w-2 h-2 rounded-full bg-amber-400 mr-1.5"></span>En cours (Ping en attente)';
|
||||
} else {
|
||||
svcAsterisk.className = 'font-mono text-red-400 font-bold';
|
||||
svcAsterisk.textContent = '🔴 Arrêté';
|
||||
svcAsterisk.innerHTML = '<span class="inline-block w-2 h-2 rounded-full bg-rose-400 mr-1.5"></span>Arrêté';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6941,10 +6966,10 @@ async function refreshClusterStatus() {
|
||||
svcPeer.textContent = 'Non configuré';
|
||||
svcPeer.className = 'font-mono text-slate-500';
|
||||
} else if (st.peer_reachable) {
|
||||
svcPeer.textContent = `🟢 Joignable (${st.peer_ping_ms !== null ? st.peer_ping_ms.toFixed(1) + 'ms' : 'OK'})`;
|
||||
svcPeer.innerHTML = `<span class="inline-block w-2 h-2 rounded-full bg-emerald-400 mr-1.5"></span>Joignable (${st.peer_ping_ms !== null ? st.peer_ping_ms.toFixed(1) + 'ms' : 'OK'})`;
|
||||
svcPeer.className = 'font-mono text-emerald-400 font-bold';
|
||||
} else {
|
||||
svcPeer.textContent = '🔴 Injoignable (Timeout)';
|
||||
svcPeer.innerHTML = '<span class="inline-block w-2 h-2 rounded-full bg-rose-400 mr-1.5"></span>Injoignable (Timeout)';
|
||||
svcPeer.className = 'font-mono text-red-400 font-bold';
|
||||
}
|
||||
}
|
||||
@@ -7088,11 +7113,11 @@ function renderUcpTab() {
|
||||
const links = [];
|
||||
if (httpsPort > 0) {
|
||||
const url = `https://${host}:${httpsPort}/`;
|
||||
links.push(`<a href="${escapeHtml(url)}" target="_blank" rel="noopener" class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-lg bg-emerald-500/10 hover:bg-emerald-500/20 text-emerald-300 border border-emerald-500/30 font-mono transition">🔒 ${escapeHtml(url)}</a>`);
|
||||
links.push(`<a href="${escapeHtml(url)}" target="_blank" rel="noopener" class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-lg bg-emerald-500/10 hover:bg-emerald-500/20 text-emerald-300 border border-emerald-500/30 font-mono transition">${adminIcon('lock', 'w-3.5 h-3.5')} ${escapeHtml(url)}</a>`);
|
||||
}
|
||||
if (httpPort > 0) {
|
||||
const url = `http://${host}:${httpPort}/`;
|
||||
links.push(`<a href="${escapeHtml(url)}" target="_blank" rel="noopener" class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-lg bg-sky-500/10 hover:bg-sky-500/20 text-sky-300 border border-sky-500/30 font-mono transition">🌐 ${escapeHtml(url)} (Reverse Proxy)</a>`);
|
||||
links.push(`<a href="${escapeHtml(url)}" target="_blank" rel="noopener" class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-lg bg-sky-500/10 hover:bg-sky-500/20 text-sky-300 border border-sky-500/30 font-mono transition">${adminIcon('globe', 'w-3.5 h-3.5')} ${escapeHtml(url)} (Reverse Proxy)</a>`);
|
||||
}
|
||||
urlsBox.innerHTML = links.length ? links.join('') : '<span class="text-amber-400">Aucun port d’écoute configuré.</span>';
|
||||
}
|
||||
|
||||
+37
-36
@@ -41,7 +41,7 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
|
||||
<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>
|
||||
<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>
|
||||
@@ -537,7 +537,7 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
|
||||
<!-- 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><svg class="w-4 h-4 shrink-0 inline-block" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M15.59 14.37a6 6 0 01-5.84 7.38v-4.8m5.84-2.58a14.98 14.98 0 006.16-12.12A14.98 14.98 0 009.631 8.41m5.96 5.96a14.926 14.926 0 01-5.841 2.58m-.119-8.54a6 6 0 00-7.381 5.84h4.8m2.581-5.84a14.927 14.927 0 00-2.58 5.84m2.699 2.7c-.103.021-.207.041-.311.06a15.09 15.09 0 01-2.448-2.448 14.9 14.9 0 01.06-.312m-2.24 2.39a4.493 4.493 0 00-1.757 4.306 4.493 4.493 0 004.306-1.758M16.5 9a1.5 1.5 0 11-3 0 1.5 1.5 0 013 0z"/></svg></span>
|
||||
<span id="header-update-text">Mise à jour dispo</span>
|
||||
</button>
|
||||
|
||||
@@ -1383,14 +1383,14 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
|
||||
<!-- 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="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"><svg class="w-4 h-4 shrink-0 inline-block" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M10.34 15.84c-.688-.06-1.386-.09-2.09-.09H7.5a4.5 4.5 0 110-9h.75c.704 0 1.402-.03 2.09-.09m0 9.18c.253.962.584 1.892.985 2.783.247.55.06 1.21-.463 1.511l-.657.38c-.551.318-1.26.117-1.527-.461a20.845 20.845 0 01-1.44-4.282m3.102.069a18.03 18.03 0 01-.59-4.59c0-1.586.205-3.124.59-4.59m0 9.18a23.848 23.848 0 018.835 2.535M10.34 6.66a23.847 23.847 0 008.835-2.535m0 0A23.74 23.74 0 0018.795 3m.38 1.125a23.91 23.91 0 011.014 5.395m-1.014 8.855c-.118.38-.245.754-.38 1.125m.38-1.125a23.91 23.91 0 001.014-5.395m0-3.46c.495.413.811 1.035.811 1.73 0 .695-.316 1.317-.811 1.73m0-3.46a24.347 24.347 0 010 3.46"/></svg></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="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"><svg class="w-4 h-4 shrink-0 inline-block" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 6.75c0 8.284 6.716 15 15 15h2.25a2.25 2.25 0 002.25-2.25v-1.372c0-.516-.351-.966-.852-1.091l-4.423-1.106c-.44-.11-.902.055-1.173.417l-.97 1.293c-.282.376-.769.542-1.21.38a12.035 12.035 0 01-7.143-7.143c-.162-.441.004-.928.38-1.21l1.293-.97c.363-.271.527-.734.417-1.173L6.963 3.102a1.125 1.125 0 00-1.091-.852H4.5A2.25 2.25 0 002.25 4.5v2.25z"/></svg></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>
|
||||
@@ -2482,8 +2482,9 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
|
||||
<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 class="text-[11px] text-slate-400 pt-1 flex items-start gap-1.5">
|
||||
<svg class="w-3.5 h-3.5 shrink-0 text-amber-400 mt-0.5" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 18v-5.25m0 0a6.01 6.01 0 001.5-.189m-1.5.189a6.01 6.01 0 01-1.5-.189m3.75 7.478a12.06 12.06 0 01-4.5 0m3.75 2.383a14.406 14.406 0 01-3 0M14.25 18v-.192c0-.983.658-1.823 1.508-2.316a7.5 7.5 0 10-7.517 0c.85.493 1.509 1.333 1.509 2.316V18"/></svg>
|
||||
<span>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> !</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2559,7 +2560,7 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
|
||||
<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>
|
||||
<svg class="w-3.5 h-3.5 shrink-0 inline-block" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z"/></svg><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"
|
||||
@@ -2575,7 +2576,7 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
|
||||
<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>
|
||||
<svg class="w-3.5 h-3.5 shrink-0 inline-block" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"/></svg><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>
|
||||
@@ -3068,9 +3069,9 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
|
||||
<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>
|
||||
class="px-2.5 py-2 rounded-xl bg-anthracite-900 border border-anthracite-700 text-slate-300 hover:text-white transition"><svg class="w-3.5 h-3.5 shrink-0 inline-block" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z"/></svg></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>
|
||||
class="px-2.5 py-2 rounded-xl bg-anthracite-900 border border-anthracite-700 text-slate-300 hover:text-white transition"><svg class="w-3.5 h-3.5 shrink-0 inline-block" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75"/></svg></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -3159,7 +3160,7 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
|
||||
</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>.
|
||||
<svg class="w-4 h-4 shrink-0 inline-block text-amber-400" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 18v-5.25m0 0a6.01 6.01 0 001.5-.189m-1.5.189a6.01 6.01 0 01-1.5-.189m3.75 7.478a12.06 12.06 0 01-4.5 0m3.75 2.383a14.406 14.406 0 01-3 0M14.25 18v-.192c0-.983.658-1.823 1.508-2.316a7.5 7.5 0 10-7.517 0c.85.493 1.509 1.333 1.509 2.316V18"/></svg> <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>
|
||||
@@ -3181,11 +3182,11 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
|
||||
<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
|
||||
<span class="inline-flex items-center gap-1.5"><svg class="w-3.5 h-3.5 shrink-0 inline-block" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z"/><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/></svg><span>Général & SIP</span></span>
|
||||
</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 class="inline-flex items-center gap-1.5"><svg class="w-3.5 h-3.5 shrink-0 inline-block" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M21.75 9v.906a2.25 2.25 0 01-1.183 1.981l-6.478 3.488M2.25 9v.906a2.25 2.25 0 001.183 1.981l6.478 3.488m8.839 2.51l-4.66-2.51m0 0l-1.023-.55a2.25 2.25 0 00-2.134 0l-1.022.55m0 0l-4.661 2.51m16.5 1.615a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V8.844a2.25 2.25 0 011.183-1.98l7.5-4.04a2.25 2.25 0 012.134 0l7.5 4.04a2.25 2.25 0 011.183 1.98V19.5z"/></svg><span>Messagerie Vocale</span></span>
|
||||
<span id="badge-ext-vm-status" class="w-2 h-2 rounded-full bg-slate-500"></span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -3377,7 +3378,7 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
|
||||
|
||||
<!-- 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>
|
||||
<svg class="w-4 h-4 shrink-0 inline-block text-amber-400" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 18v-5.25m0 0a6.01 6.01 0 001.5-.189m-1.5.189a6.01 6.01 0 01-1.5-.189m3.75 7.478a12.06 12.06 0 01-4.5 0m3.75 2.383a14.406 14.406 0 01-3 0M14.25 18v-.192c0-.983.658-1.823 1.508-2.316a7.5 7.5 0 10-7.517 0c.85.493 1.509 1.333 1.509 2.316V18"/></svg>
|
||||
<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.
|
||||
@@ -3711,11 +3712,11 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
|
||||
</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>
|
||||
<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"><span class="inline-flex items-center gap-1"><svg class="w-3.5 h-3.5 shrink-0 inline-block text-rose-400" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m0-10.036A11.959 11.959 0 013.598 6 11.99 11.99 0 003 9.75c0 5.592 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.57-.598-3.75h-.152c-3.196 0-6.1-1.249-8.25-3.286zm0 13.036h.008v.008H12v-.008z"/></svg><span>Urgence</span></span></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>
|
||||
<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-flex items-center gap-1"><svg class="w-3.5 h-3.5 shrink-0 text-cyan-400" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 21h16.5M4.5 3h15M5.25 3v18m13.5-18v18M9 6.75h1.5m-1.5 3h1.5m-1.5 3h1.5m3-6H15m-1.5 3H15m-1.5 3H15M9 21v-3.375c0-.621.504-1.125 1.125-1.125h3.75c.621 0 1.125.504 1.125 1.125V21"/></svg><span>Intra-Company</span></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -3757,11 +3758,11 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-slate-200 mb-1">🎵 Musique d'attente (MoH)</label>
|
||||
<label class="block text-xs font-medium text-slate-200 mb-1"><span class="inline-flex items-center gap-1.5"><svg class="w-3.5 h-3.5 shrink-0 inline-block text-sky-400" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M9 9l10.5-3m0 6.553v3.75a2.25 2.25 0 01-1.632 2.163l-1.32.377a1.803 1.803 0 11-.99-3.467l2.31-.66a2.25 2.25 0 001.632-2.163zm0 0V2.25L9 5.25v10.303m0 0v3.75a2.25 2.25 0 01-1.632 2.163l-1.32.377a1.803 1.803 0 01-.99-3.467l2.31-.66A2.25 2.25 0 009 15.553z"/></svg><span>Musique d'attente (MoH)</span></span></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>
|
||||
<label class="block text-xs font-medium text-slate-200 mb-1"><span class="inline-flex items-center gap-1.5"><svg class="w-4 h-4 shrink-0 inline-block" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M10.34 15.84c-.688-.06-1.386-.09-2.09-.09H7.5a4.5 4.5 0 110-9h.75c.704 0 1.402-.03 2.09-.09m0 9.18c.253.962.584 1.892.985 2.783.247.55.06 1.21-.463 1.511l-.657.38c-.551.318-1.26.117-1.527-.461a20.845 20.845 0 01-1.44-4.282m3.102.069a18.03 18.03 0 01-.59-4.59c0-1.586.205-3.124.59-4.59m0 9.18a23.848 23.848 0 018.835 2.535M10.34 6.66a23.847 23.847 0 008.835-2.535m0 0A23.74 23.74 0 0018.795 3m.38 1.125a23.91 23.91 0 011.014 5.395m-1.014 8.855c-.118.38-.245.754-.38 1.125m.38-1.125a23.91 23.91 0 001.014-5.395m0-3.46c.495.413.811 1.035.811 1.73 0 .695-.316 1.317-.811 1.73m0-3.46a24.347 24.347 0 010 3.46"/></svg><span>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>
|
||||
@@ -3772,7 +3773,7 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
|
||||
<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>
|
||||
<div class="text-xs font-bold uppercase tracking-wider text-emerald-300"><span class="inline-flex items-center gap-1.5"><svg class="w-3.5 h-3.5 shrink-0 inline-block" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z"/></svg><span>Masques de numérotation pré-définis (1 clic)</span></span></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()"
|
||||
@@ -3788,19 +3789,19 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
|
||||
</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)
|
||||
<span class="inline-block w-2 h-2 rounded-full bg-emerald-400 mr-1.5"></span>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)
|
||||
<span class="inline-flex items-center gap-1.5"><svg class="w-3.5 h-3.5 shrink-0 inline-block text-rose-400" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m0-10.036A11.959 11.959 0 013.598 6 11.99 11.99 0 003 9.75c0 5.592 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.57-.598-3.75h-.152c-3.196 0-6.1-1.249-8.25-3.286zm0 13.036h.008v.008H12v-.008z"/></svg><span>Numéros d'Urgence (15, 17, 18, 112, 114)</span></span>
|
||||
</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.)
|
||||
<span class="inline-flex items-center gap-1.5"><svg class="w-3.5 h-3.5 shrink-0 inline-block text-cyan-400" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 21a9.004 9.004 0 008.716-6.747M12 21a9.004 9.004 0 01-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3"/></svg><span>International (00. & +X.)</span></span>
|
||||
</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.)
|
||||
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 inline-flex items-center gap-1.5">
|
||||
<svg class="w-3.5 h-3.5 shrink-0 text-purple-400" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.499a.562.562 0 011.04 0l2.125 5.111a.563.563 0 00.475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 00-.182.557l1.285 5.385a.562.562 0 01-.84.61l-4.725-2.885a.563.563 0 00-.586 0L6.982 20.54a.562.562 0 01-.84-.61l1.285-5.386a.562.562 0 00-.182-.557l-4.204-3.602a.563.563 0 01.321-.988l5.518-.442a.563.563 0 00.475-.345L11.48 3.5z"/></svg><span>Tout autoriser (X.)</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -3851,7 +3852,7 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
|
||||
<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>
|
||||
<div class="text-xs font-bold uppercase tracking-wider text-cyan-300"><span class="inline-flex items-center gap-1.5"><svg class="w-3.5 h-3.5 shrink-0 inline-block text-cyan-400" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M18 18.72a9.094 9.094 0 003.741-.479 3 3 0 00-4.682-2.72m.94 3.198l.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0112 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 016 18.719m12 0a5.971 5.971 0 00-.941-3.197m0 0A5.995 5.995 0 0012 12.75a5.995 5.995 0 00-5.058 2.772m0 0a3 3 0 00-4.681 2.72 8.986 8.986 0 003.74.477m.94-3.197a5.971 5.971 0 00-.94 3.197M15 6.75a3 3 0 11-6 0 3 3 0 016 0z"/></svg><span>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();"
|
||||
@@ -3866,7 +3867,7 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
|
||||
<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>
|
||||
<div class="text-xs font-bold uppercase tracking-wider text-emerald-300"><span class="inline-flex items-center gap-1.5"><svg class="w-3.5 h-3.5 shrink-0 inline-block text-emerald-400" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M10.5 1.5H8.25A2.25 2.25 0 006 3.75v16.5a2.25 2.25 0 002.25 2.25h7.5A2.25 2.25 0 0018 20.25V3.75a2.25 2.25 0 00-2.25-2.25H13.5m-3 0V3h3V1.5m-3 0h3m-3 18.75h3"/></svg><span>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>
|
||||
@@ -3938,7 +3939,7 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
|
||||
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>
|
||||
<span class="inline-flex items-center gap-1.5"><svg class="w-3.5 h-3.5 shrink-0 inline-block" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M21.75 9v.906a2.25 2.25 0 01-1.183 1.981l-6.478 3.488M2.25 9v.906a2.25 2.25 0 001.183 1.981l6.478 3.488m8.839 2.51l-4.66-2.51m0 0l-1.023-.55a2.25 2.25 0 00-2.134 0l-1.022.55m0 0l-4.661 2.51m16.5 1.615a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V8.844a2.25 2.25 0 011.183-1.98l7.5-4.04a2.25 2.25 0 012.134 0l7.5 4.04a2.25 2.25 0 011.183 1.98V19.5z"/></svg><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)
|
||||
@@ -4044,7 +4045,7 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
|
||||
<!-- 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>
|
||||
<span class="inline-flex items-center gap-1.5"><svg class="w-4 h-4 shrink-0 inline-block text-amber-400" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 18v-5.25m0 0a6.01 6.01 0 001.5-.189m-1.5.189a6.01 6.01 0 01-1.5-.189m3.75 7.478a12.06 12.06 0 01-4.5 0m3.75 2.383a14.406 14.406 0 01-3 0M14.25 18v-.192c0-.983.658-1.823 1.508-2.316a7.5 7.5 0 10-7.517 0c.85.493 1.509 1.333 1.509 2.316V18"/></svg> 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) :
|
||||
@@ -4181,7 +4182,7 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
|
||||
<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="inline-flex items-center gap-1.5"><svg class="w-4 h-4 shrink-0 inline-block" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 6.75c0 8.284 6.716 15 15 15h2.25a2.25 2.25 0 002.25-2.25v-1.372c0-.516-.351-.966-.852-1.091l-4.423-1.106c-.44-.11-.902.055-1.173.417l-.97 1.293c-.282.376-.769.542-1.21.38a12.035 12.035 0 01-7.143-7.143c-.162-.441.004-.928.38-1.21l1.293-.97c.363-.271.527-.734.417-1.173L6.963 3.102a1.125 1.125 0 00-1.091-.852H4.5A2.25 2.25 0 002.25 4.5v2.25z"/></svg><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">
|
||||
@@ -4216,7 +4217,7 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
|
||||
<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>
|
||||
<option value="0">Hériter de la musique générale par défaut</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -4225,7 +4226,7 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
|
||||
<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="inline-flex items-center gap-1.5"><svg class="w-3.5 h-3.5 shrink-0 inline-block text-amber-400" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M9.568 3H5.25A2.25 2.25 0 003 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.095 18.095 0 005.223-5.223c.542-.827.369-1.908-.33-2.607L11.16 3.66A2.25 2.25 0 009.568 3z"/></svg><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">
|
||||
@@ -4251,7 +4252,7 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
|
||||
<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...)
|
||||
<span class="inline-flex items-center gap-1.5"><svg class="w-3.5 h-3.5 shrink-0 inline-block" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75m-3-7.036A11.959 11.959 0 013.598 6 11.99 11.99 0 003 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285z"/></svg><span>+ Ajouter Pack Blocage Démarchage</span></span> (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">
|
||||
@@ -4263,7 +4264,7 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
|
||||
<!-- 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...)"
|
||||
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
|
||||
@@ -4651,7 +4652,7 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
|
||||
<!-- 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
|
||||
<span class="inline-flex items-center gap-1.5"><svg class="w-3.5 h-3.5 shrink-0 inline-block text-amber-400" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"/></svg><span>En cas de Touche Invalide</span></span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -4678,7 +4679,7 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
|
||||
<!-- 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)
|
||||
<svg class="w-4 h-4 shrink-0 text-amber-400" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z"/></svg> En cas de Timeout (Aucune touche pressée)
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
@@ -5564,7 +5565,7 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
|
||||
</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>
|
||||
<svg class="w-3.5 h-3.5 shrink-0 inline-block text-amber-400" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"/></svg>
|
||||
<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>
|
||||
|
||||
Reference in new issue
Block a user