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>';
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user