diff --git a/public/assets/app.js b/public/assets/app.js
index 3b90078..c45f5ed 100644
--- a/public/assets/app.js
+++ b/public/assets/app.js
@@ -28,6 +28,31 @@ const chartHistory = {
let metricsTimer = null;
+function adminIcon(name, cls = 'w-3.5 h-3.5') {
+ const paths = {
+ shield: ' ',
+ lock: ' ',
+ unlock: ' ',
+ warning: ' ',
+ phone: ' ',
+ mobile: ' ',
+ building: ' ',
+ voicemail: ' ',
+ music: ' ',
+ refresh: ' ',
+ trash: ' ',
+ tag: ' ',
+ ban: ' ',
+ speaker: ' ',
+ rocket: ' ',
+ emergency: ' ',
+ clock: ' ',
+ globe: ' ',
+ dotGreen: ' ',
+ };
+ return `${paths[name] || paths.tag} `;
+}
+
// --- 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 = `🛡️ SRTP Forcé `;
+ srtpBadge = `${adminIcon('shield', 'w-3 h-3')} SRTP Forcé `;
} else if (encMode === 1) {
- srtpBadge = `🛡️ SRTP Négociable `;
+ srtpBadge = `${adminIcon('shield', 'w-3 h-3')} SRTP Négociable `;
} else {
- srtpBadge = `🔈 RTP Clair `;
+ srtpBadge = `${adminIcon('speaker', 'w-3 h-3')} RTP Clair `;
}
// 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 = `
- 🔒 TLS Actif (Chiffré)
+ ${adminIcon('lock', 'w-3 h-3')} TLS Actif (Chiffré)
`;
profileNote = `${isTls ? 'TLS Strict' : 'UDP Interne'} `;
} else if (actTr === 'wss') {
trPill = `
- 🔒 WSS WebRTC
+ ${adminIcon('lock', 'w-3 h-3')} WSS WebRTC
`;
profileNote = `WebRTC `;
} else if (actTr === 'tcp') {
trPill = `
- 🔓 TCP Actif (Clair)
+ ${adminIcon('unlock', 'w-3 h-3')} TCP Actif (Clair)
`;
profileNote = `${isTls ? 'TLS Strict' : 'UDP Interne'} `;
@@ -804,10 +829,10 @@ function renderDashboardPhonesTable() {
// UDP
trPill = `
- 🔓 UDP Actif (Clair)
+ ${adminIcon('unlock', 'w-3 h-3')} UDP Actif (Clair)
`;
- profileNote = isTls ? '⚠️ Exige TLS ' : 'UDP Interne ';
+ profileNote = isTls ? `${adminIcon('warning', 'w-3 h-3')} Exige TLS ` : 'UDP Interne ';
}
modeBadge = `
@@ -853,7 +878,7 @@ function renderDashboardPhonesTable() {
contactHtml = `
${escapeHtml(cleanIp)}
- ${ua ? `
📱 ${ua}
` : ''}
+ ${ua ? `
${adminIcon('mobile', 'w-3 h-3 text-indigo-400')} ${ua}
` : ''}
`;
} else {
@@ -909,10 +934,10 @@ function renderExtensionsTable() {
encLabel = 'RTP Clair ';
}
const badge = isTls
- ? `🔒 TLS (${encLabel}) `
+ ? `${adminIcon('lock', 'w-3.5 h-3.5 text-emerald-400')} TLS (${encLabel}) `
: (encMode > 0
- ? `🛡️ UDP (${encLabel}) `
- : `🏢 UDP (RTP Clair) `);
+ ? `${adminIcon('shield', 'w-3.5 h-3.5 text-indigo-400')} UDP (${encLabel}) `
+ : `${adminIcon('building', 'w-3.5 h-3.5 text-slate-400')} UDP (RTP Clair) `);
const actTr = c ? (c.active_transport || 'udp').toUpperCase() : '';
const stLower = c ? String(c.status).toLowerCase() : '';
@@ -935,7 +960,7 @@ function renderExtensionsTable() {
: `Aucun `;
const vmBadge = Number(ext.voicemail_enabled) === 1
- ? `📟 Boîte `
+ ? `${adminIcon('voicemail', 'w-3 h-3')} Boîte `
: '';
const gwBadge = Number(ext.groundwire_enabled) === 1
? `GroundWire `
@@ -1256,7 +1281,7 @@ function renderGroupsTable() {
? members.map(m => `${escapeHtml(m)} `).join(' ')
: 'Aucun membre ';
const failoverLabel = String(grp.failover_dest || '').startsWith('vm-')
- ? `📬 Répondeur ${escapeHtml(String(grp.failover_dest).slice(3))} `
+ ? `${adminIcon('voicemail', 'w-3.5 h-3.5')} Répondeur ${escapeHtml(String(grp.failover_dest).slice(3))} `
: `${escapeHtml(grp.failover_dest)}`;
return `
@@ -1773,7 +1798,7 @@ function renderTrunksTable() {
const mohSound = (appState.sounds || []).find(s => Number(s.id) === Number(t.moh_sound_id));
const mohBadge = mohSound
- ? `🎵 ${escapeHtml(mohSound.name)} `
+ ? `${adminIcon('music', 'w-3 h-3')} ${escapeHtml(mohSound.name)} `
: `MoH : Globale `;
return `
@@ -1803,12 +1828,12 @@ function renderTrunksTable() {
${t.cid_name_prefix ? `CID: ${escapeHtml(t.cid_name_prefix)} ` : ''}
${didCount > 0 ? `${didCount} règle(s) entrante(s) ` : 'Tous numéros (Any)'}
${blockCount > 0 ? `${blockCount} blocage(s) CID ` : ''}
- ${tcCount > 0 ? `⏱️ ${tcCount} horaire(s) ` : ''}
+ ${tcCount > 0 ? `${adminIcon('clock', 'w-3 h-3')} ${tcCount} horaire(s) ` : ''}
${mohBadge}
- ${wantsReg ? `🔄 Re-Register ` : ''}
+ ${wantsReg ? `${adminIcon('refresh', 'w-3.5 h-3.5')} Re-Register ` : ''}
Routes Entrantes
Modifier
Supprimer
@@ -2230,11 +2255,11 @@ function addTrunkInboundRouteRow(cid = 'Any', did = 'Any', description = '', des
-
- ⏱️ Horaires
+
+ ${adminIcon('clock', 'w-3.5 h-3.5')} Horaires
⧉
- 🗑️
+ ${adminIcon('trash', 'w-3.5 h-3.5')}
@@ -2278,7 +2303,7 @@ function addTrunkInboundRouteRow(cid = 'Any', did = 'Any', description = '', des
- 🏷️ Préfixe CID spécifique à cette règle :
+ ${adminIcon('tag', 'w-3.5 h-3.5 text-amber-400')} Préfixe CID spécifique à cette règle :
@@ -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 = `⏱️ ${escapeHtml(tcStartInput.value)}-${escapeHtml(tcFinishInput.value)} (${escapeHtml(daysLabel)}) `;
+ badgeContainer.innerHTML = `${adminIcon('clock', 'w-3 h-3')} ${escapeHtml(tcStartInput.value)}-${escapeHtml(tcFinishInput.value)} (${escapeHtml(daysLabel)}) `;
} 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 = `🌐 Hériter de la musique générale par défaut `;
+ let opts = `Hériter de la musique générale par défaut `;
mohSounds.forEach(s => {
const isSel = (Number(s.id) === curMohId) ? 'selected' : '';
- opts += `🎵 ${escapeHtml(s.name)} (${escapeHtml(s.site_category || 'Global')}) `;
+ opts += `${escapeHtml(s.name)} (${escapeHtml(s.site_category || 'Global')}) `;
});
mohSelect.innerHTML = opts;
}
@@ -2821,7 +2846,7 @@ function renderOutboundRoutesTable() {
${tIdx + 1}. ${escapeHtml(t)}
`).join(' → ')
- : `⚠️ Aucun Trunk associé `;
+ : `${adminIcon('warning', 'w-3.5 h-3.5')} Aucun Trunk associé `;
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'
- ? `🚑 Urgence `
+ ? `${adminIcon('emergency', 'w-3 h-3')} Urgence `
: (r.route_type === 'intracompany'
? `Intra `
: `Standard `);
@@ -2941,19 +2966,19 @@ function openOutboundRouteModal(route = null) {
if (congSel) {
const curCong = route ? route.congestion_dest : 'all-circuits-busy';
let congOpts = `
-
- 📢 Message vocal standard (« Toutes les lignes sont occupées »)
+
+ Message vocal standard (« Toutes les lignes sont occupées »)
`;
const annSounds = (appState.sounds || []).filter(s => s.usage_type !== 'moh');
if (annSounds.length > 0) {
annSounds.forEach(s => {
const val = `sound-${s.id}`;
- congOpts += `🎙️ Annonce : ${escapeHtml(s.name)} `;
+ congOpts += `Annonce : ${escapeHtml(s.name)} `;
});
}
congOpts += `
-
+
Tonalité de Congestion (Bip rapide réseau saturé)
Tonalité d'Occupation (Bip occupé)
Raccrocher immédiatement
@@ -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 = `🌐 Hériter du Trunk / Global (Recommandé) `;
+ let mohOpts = `Hériter du Trunk / Global (Recommandé) `;
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 = '') {
${optsHtml || 'Aucun Trunk disponible (Créez un Trunk) '}
- 🗑️
+ ${adminIcon('trash', 'w-3.5 h-3.5')}
`;
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">
- 🗑️
+ ${adminIcon('trash', 'w-3.5 h-3.5')}
`;
@@ -3322,8 +3347,8 @@ function renderIvrsTable() {
- ⏱️ Attente : ${Number(ivr.timeout)}s (${Number(ivr.timeout_retries || 2)} rép)
- ⚠️ Erreur : max ${Number(ivr.invalid_retries || 3)} essais
+ ${adminIcon('clock', 'w-3.5 h-3.5 text-amber-400')} Attente : ${Number(ivr.timeout)}s (${Number(ivr.timeout_retries || 2)} rép)
+ ${adminIcon('warning', 'w-3.5 h-3.5 text-amber-400')} Erreur : max ${Number(ivr.invalid_retries || 3)} essais
@@ -3397,7 +3422,7 @@ function openIvrModal(ivr = null) {
let audioHtml = '';
if (sounds.length > 0) {
- audioHtml += ``;
+ audioHtml += ``;
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 += `
-
+
demo-instruct (Instructions d'accueil)
hello-world (Message d'accueil basique)
beep (Simple bip sonore)
@@ -5629,7 +5654,7 @@ function renderFirewall() {
return `
- ⛔ ${escapeHtml(b.ip)}
+ ${adminIcon('ban', 'w-3.5 h-3.5')} ${escapeHtml(b.ip)}
${escapeHtml(b.jail_label || b.jail)}
@@ -6260,7 +6285,7 @@ async function checkSystemUpdate(notify = false) {
if (statusText) {
statusText.className = 'text-amber-400 font-bold';
- statusText.innerHTML = `🚀 Nouvelle version disponible : v${escapeHtml(data.latest_version)} !`;
+ statusText.innerHTML = `${adminIcon('rocket', 'w-3.5 h-3.5 text-indigo-300')} Nouvelle version disponible : v${escapeHtml(data.latest_version)} !`;
}
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 = ' Actif (En cours)';
} else {
svcKeepalived.className = 'font-mono text-slate-400';
- svcKeepalived.textContent = '⚪ Arrêté';
+ svcKeepalived.innerHTML = ' 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 = ' 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 = ' En cours (Ping en attente)';
} else {
svcAsterisk.className = 'font-mono text-red-400 font-bold';
- svcAsterisk.textContent = '🔴 Arrêté';
+ svcAsterisk.innerHTML = ' 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 = ` 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 = ' 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(`🔒 ${escapeHtml(url)} `);
+ links.push(`${adminIcon('lock', 'w-3.5 h-3.5')} ${escapeHtml(url)} `);
}
if (httpPort > 0) {
const url = `http://${host}:${httpPort}/`;
- links.push(`🌐 ${escapeHtml(url)} (Reverse Proxy) `);
+ links.push(`${adminIcon('globe', 'w-3.5 h-3.5')} ${escapeHtml(url)} (Reverse Proxy) `);
}
urlsBox.innerHTML = links.length ? links.join('') : 'Aucun port d’écoute configuré. ';
}
diff --git a/public/index.php b/public/index.php
index 08fc338..b37e137 100644
--- a/public/index.php
+++ b/public/index.php
@@ -41,7 +41,7 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
- = $isDevEnv ? '🛠️ [DEV] ' : '' ?>NexusPBX — Supervision & Téléphonie IP • Powered by Asterisk
+ = $isDevEnv ? '[DEV] ' : '' ?>NexusPBX — Supervision & Téléphonie IP • Powered by Asterisk
@@ -537,7 +537,7 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
- 🚀
+
@@ -1383,14 +1383,14 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
-
📢
+
Annonce Générale de Groupe (Paging)
Composez le numéro du groupe configuré (ex: 100 ). Les téléphones cibles décrochent automatiquement sur haut-parleur après un bip d'avertissement sonore.
-
📞
+
Interphonie Directe 1-à-1 (*80 + poste)
Composez *80 suivi du numéro de l'extension (ex: *80101 ) pour ouvrir directement le haut-parleur et le micro du poste en conversation duplex.
@@ -2482,8 +2482,9 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
Ligne (Line) Line 1
Extension (Pickup) **
-
- 💡 Quand le poste surveillé sonne, sa touche clignote : un simple appui dessus intercepte l'appel immédiatement via le préfixe
** !
+
+
+
Quand le poste surveillé sonne, sa touche clignote : un simple appui dessus intercepte l'appel immédiatement via le préfixe ** !
@@ -2559,7 +2560,7 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
↻
- 🔍 Détecter l'IP publique
+ Détecter l'IP publique
Réseaux Locaux (Local Networks - CIDR) *
- ➕ Ajouter un réseau local
+ Ajouter un réseau local
@@ -3068,9 +3069,9 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
🎲
+ class="px-2.5 py-2 rounded-xl bg-anthracite-900 border border-anthracite-700 text-slate-300 hover:text-white transition">
📋
+ class="px-2.5 py-2 rounded-xl bg-anthracite-900 border border-anthracite-700 text-slate-300 hover:text-white transition">
@@ -3159,7 +3160,7 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
- 💡
Conseil d'Architecture : Vos téléphones IP (Yealink, Groundwire) et vos Trunks SIP doivent pointer sur l'adresse
IP Virtuelle (VIP) .
+
Conseil d'Architecture : Vos téléphones IP (Yealink, Groundwire) et vos Trunks SIP doivent pointer sur l'adresse
IP Virtuelle (VIP) .
@@ -3181,11 +3182,11 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
- ⚙️ Général & SIP
+ Général & SIP
- 📟 Messagerie Vocale
+ Messagerie Vocale
@@ -3377,7 +3378,7 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
@@ -3757,11 +3758,11 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
-
🎵 Musique d'attente (MoH)
+
Musique d'attente (MoH)
-
📢 Si Trunks indisponibles (Échec / Congestion)
+
Si Trunks indisponibles (Échec / Congestion)
@@ -3772,7 +3773,7 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
-
🪄 Masques de numérotation pré-définis (1 clic)
+
Masques de numérotation pré-définis (1 clic)
Ajoutez instantanément les masques standards ou créez vos propres règles (prepend) + préfixe | [match pattern / ID appelant].
- 🟢 Numéros Verts / Gratuits (080[0-9]XXXXXX)
+ Numéros Verts / Gratuits (080[0-9]XXXXXX)
- 🚑 Numéros d'Urgence (15, 17, 18, 112, 114)
+ Numéros d'Urgence (15, 17, 18, 112, 114)
- 🌍 International (00. & +X.)
+ International (00. & +X.)
- ⭐ 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">
+ Tout autoriser (X.)
@@ -3851,7 +3852,7 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
-
👥 Groupes d'Extensions Autorisés (ex: Groupe Nice, Groupe Paris)
+
Groupes d'Extensions Autorisés (ex: Groupe Nice, Groupe Paris)
Tous les postes rattachés à un groupe d'extensions coché ci-dessous auront le droit d'appeler via cette route sortante.
-
📱 Extension Routing (Postes individuels Autorisés ↔ Bloqués)
+
Extension Routing (Postes individuels Autorisés ↔ Bloqués)
Cliquez sur un poste pour le déplacer entre Allowed Extensions et Blocked Extensions . Si un poste est bloqué ici, l'appel passera automatiquement à la Route Sortante suivante.
@@ -3938,7 +3939,7 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
Trunk avec Enregistrement (REGISTER + Auth)
- 📟 Passerelle FXO / Ligne Box (Grandstream, Cisco SPA, AudioCodes)
+ Passerelle FXO / Ligne Box (Grandstream, Cisco SPA, AudioCodes)
Peering IP / SBC / Passerelle (Sans REGISTER)
@@ -4044,7 +4045,7 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
-
💡 Guide Rapide : Paramétrage d'une passerelle FXO (Grandstream HT813/HT503, Cisco SPA3102...)
+
Guide Rapide : Paramétrage d'une passerelle FXO (Grandstream HT813/HT503, Cisco SPA3102...)
Dans l'interface web de votre passerelle (onglet FXO Port Grandstream ou Line 1/PSTN 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')
- 📞 Options d'appel Asterisk (Asterisk Trunk Dial Options)
+ Options d'appel Asterisk (Asterisk Trunk Dial Options)
FreePBX Dial Options
@@ -4216,7 +4217,7 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
- 🌐 Hériter de la musique générale par défaut
+ Hériter de la musique générale par défaut
@@ -4225,7 +4226,7 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
- 🏷️ Préfixe Nom Appelant (CID Name Prefix)
+ Préfixe Nom Appelant (CID Name Prefix)
FreePBX CID Prefix
@@ -4251,7 +4252,7 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
- 🛡️ + Ajouter Pack Blocage Démarchage (0162, 0163, 0270, 0377, 0424, 0568, 0948...)
+ + Ajouter Pack Blocage Démarchage (0162, 0163, 0270, 0377, 0424, 0568, 0948...)
@@ -4263,7 +4264,7 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
Syntaxe Asterisk :
Any = Tous •
_0162XXXXXX = Plage 0162 •
0612345678 = Numéro exact
@@ -4651,7 +4652,7 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
-
⚠️ En cas de Touche Invalide
+
En cas de Touche Invalide
@@ -4678,7 +4679,7 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
-
⏱️ En cas de Timeout (Aucune touche pressée)
+
En cas de Timeout (Aucune touche pressée)
@@ -5564,7 +5565,7 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
-
⚠️
+
Si vous confirmez le redémarrage, la conversation téléphonique sera immédiatement coupée pour votre interlocuteur !
diff --git a/ucp/assets/ucp.js b/ucp/assets/ucp.js
index b77ed35..db18f80 100644
--- a/ucp/assets/ucp.js
+++ b/ucp/assets/ucp.js
@@ -25,6 +25,28 @@ function escapeHtml(str) {
.replace(/'/g, ''');
}
+function ucpIcon(name, cls = 'w-4 h-4') {
+ const paths = {
+ voicemail: '
',
+ bolt: '
',
+ calendar: '
',
+ clock: '
',
+ download: '
',
+ trash: '
',
+ phone: '
',
+ mobile: '
',
+ mail: '
',
+ pencil: '
',
+ forward: '
',
+ pickup: '
',
+ dnd: '
',
+ shield: '
',
+ wrench: '
',
+ tag: '
',
+ };
+ return `
${paths[name] || paths.tag} `;
+}
+
function showUcpToast(message, type = 'info') {
const container = document.getElementById('ucp-toast-container');
if (!container) return;
@@ -232,14 +254,14 @@ function buildForwardOptions(extObj, selectedValue) {
if (Number(extObj.voicemail_enabled) === 1) {
const vmVal = `vm-${extObj.extension}`;
- html += `
📬 Ma messagerie vocale (${escapeHtml(extObj.extension)}) `;
+ html += `
Ma messagerie vocale (${escapeHtml(extObj.extension)}) `;
}
const others = (ucpState.forward_targets || []).filter(t => t.extension !== extObj.extension);
if (others.length > 0) {
html += `
`;
others.forEach(t => {
- html += `📱 ${escapeHtml(t.extension)} — ${escapeHtml(t.name)} `;
+ html += `${escapeHtml(t.extension)} — ${escapeHtml(t.name)} `;
});
html += ` `;
}
@@ -249,7 +271,7 @@ function buildForwardOptions(extObj, selectedValue) {
html += `
`;
otherVms.forEach(t => {
const vVal = `vm-${t.extension}`;
- html += `📬 Répondeur ${escapeHtml(t.extension)} — ${escapeHtml(t.name)} `;
+ html += `Répondeur ${escapeHtml(t.extension)} — ${escapeHtml(t.name)} `;
});
html += ` `;
}
@@ -286,7 +308,8 @@ function renderExtensionsGrid() {
const vmPill = Number(ext.voicemail_enabled) === 1
? `
- 📬 ${ext.vm_inbox_count} nouveau(x)
+ ${ucpIcon('voicemail', 'w-3.5 h-3.5')}
+ ${ext.vm_inbox_count} nouveau(x)
`
: '';
@@ -374,13 +397,13 @@ function renderExtensionsGrid() {
@@ -546,21 +569,25 @@ function renderVoicemailsList() {
${escapeHtml(msg.callerid)}
${badge}
-
- 📅 ${escapeHtml(msg.date_human)} • ⏱️ ${formatDuration(msg.duration)}
+
+ ${ucpIcon('calendar', 'w-3.5 h-3.5 text-slate-400')} ${escapeHtml(msg.date_human)}
+ •
+ ${ucpIcon('clock', 'w-3.5 h-3.5 text-slate-400')} ${formatDuration(msg.duration)}
@@ -722,7 +749,7 @@ async function loadPersonalCdr() {
${dirBadge}
${stBadge}
-
⏱ ${durText}
+
${ucpIcon('clock', 'w-3.5 h-3.5 text-slate-400')} ${durText}
${formatInlineParty(srcName, srcNum)}
@@ -864,17 +891,18 @@ function renderPhonebookGrid() {
${escapeHtml(c.group_name || 'Général')}
- ${c.internal_number ? `
📱 Interne : ${escapeHtml(c.internal_number)}
` : ''}
- ${c.mobile_number ? `
📲 Mobile : ${escapeHtml(c.mobile_number)}
` : ''}
- ${c.other_number ? `
☎️ Autre : ${escapeHtml(c.other_number)}
` : ''}
- ${c.email ? `
✉️ ${escapeHtml(c.email)}
` : ''}
+ ${c.internal_number ? `
${ucpIcon('phone', 'w-3.5 h-3.5 text-indigo-500')} Interne : ${escapeHtml(c.internal_number)}
` : ''}
+ ${c.mobile_number ? `
${ucpIcon('mobile', 'w-3.5 h-3.5 text-emerald-500')} Mobile : ${escapeHtml(c.mobile_number)}
` : ''}
+ ${c.other_number ? `
${ucpIcon('phone', 'w-3.5 h-3.5 text-slate-400')} Autre : ${escapeHtml(c.other_number)}
` : ''}
+ ${c.email ? `
${ucpIcon('mail', 'w-3.5 h-3.5 text-slate-400')} ${escapeHtml(c.email)}
` : ''}
${c.is_mine ? `
- ✏️ Modifier ma photo / mon e-mail
+ class="w-full py-1.5 px-3 rounded-xl bg-indigo-50 hover:bg-indigo-100 text-indigo-700 text-xs font-semibold border border-indigo-200 transition inline-flex items-center justify-center gap-1.5">
+ ${ucpIcon('pencil', 'w-3.5 h-3.5')}
+ Modifier ma photo / mon e-mail
` : ''}
@@ -996,7 +1024,7 @@ function renderFeatureGuide() {
quickBox.innerHTML = `
- 📬
+ ${ucpIcon('voicemail', 'w-4 h-4 text-indigo-600')}
Utiliser la Messagerie Vocale
@@ -1009,7 +1037,7 @@ function renderFeatureGuide() {
- ↪️
+ ${ucpIcon('forward', 'w-4 h-4 text-amber-600')}
Gérer Renvois & Ne Pas Déranger
@@ -1022,7 +1050,7 @@ function renderFeatureGuide() {
- 🎧
+ ${ucpIcon('pickup', 'w-4 h-4 text-emerald-600')}
Interception & Prise d'Appel
@@ -1049,12 +1077,12 @@ function renderFeatureGuide() {
}
const catMeta = {
- voicemail: { label: 'Messagerie Vocale', icon: '📬', badge: 'bg-indigo-50 text-indigo-700 border-indigo-200', header: 'bg-indigo-50/60 border-indigo-100 text-indigo-950' },
- forward: { label: "Renvois d'Appel", icon: '↪️', badge: 'bg-amber-50 text-amber-800 border-amber-200', header: 'bg-amber-50/60 border-amber-100 text-amber-950' },
- dnd: { label: 'Ne Pas Déranger (DND)', icon: '🔕', badge: 'bg-rose-50 text-rose-700 border-rose-200', header: 'bg-rose-50/60 border-rose-100 text-rose-950' },
- pickup: { label: "Interception d'Appels", icon: '📲', badge: 'bg-emerald-50 text-emerald-700 border-emerald-200', header: 'bg-emerald-50/60 border-emerald-100 text-emerald-950' },
- security: { label: 'Liste Noire & Blocage Appels', icon: '🛡️', badge: 'bg-slate-100 text-slate-700 border-slate-300', header: 'bg-slate-100/80 border-slate-200 text-slate-900' },
- tools: { label: 'Outils, Tests Audio & Services', icon: '🛠️', badge: 'bg-sky-50 text-sky-700 border-sky-200', header: 'bg-sky-50/60 border-sky-100 text-sky-950' },
+ voicemail: { label: 'Messagerie Vocale', icon: ucpIcon('voicemail', 'w-4 h-4 text-indigo-600'), badge: 'bg-indigo-50 text-indigo-700 border-indigo-200', header: 'bg-indigo-50/60 border-indigo-100 text-indigo-950' },
+ forward: { label: "Renvois d'Appel", icon: ucpIcon('forward', 'w-4 h-4 text-amber-600'), badge: 'bg-amber-50 text-amber-800 border-amber-200', header: 'bg-amber-50/60 border-amber-100 text-amber-950' },
+ dnd: { label: 'Ne Pas Déranger (DND)', icon: ucpIcon('dnd', 'w-4 h-4 text-rose-600'), badge: 'bg-rose-50 text-rose-700 border-rose-200', header: 'bg-rose-50/60 border-rose-100 text-rose-950' },
+ pickup: { label: "Interception d'Appels", icon: ucpIcon('pickup', 'w-4 h-4 text-emerald-600'), badge: 'bg-emerald-50 text-emerald-700 border-emerald-200', header: 'bg-emerald-50/60 border-emerald-100 text-emerald-950' },
+ security: { label: 'Liste Noire & Blocage Appels', icon: ucpIcon('shield', 'w-4 h-4 text-slate-600'), badge: 'bg-slate-100 text-slate-700 border-slate-300', header: 'bg-slate-100/80 border-slate-200 text-slate-900' },
+ tools: { label: 'Outils, Tests Audio & Services', icon: ucpIcon('wrench', 'w-4 h-4 text-sky-600'), badge: 'bg-sky-50 text-sky-700 border-sky-200', header: 'bg-sky-50/60 border-sky-100 text-sky-950' },
};
const orderedCats = ['voicemail', 'forward', 'dnd', 'pickup', 'security', 'tools'];
@@ -1071,7 +1099,7 @@ function renderFeatureGuide() {
];
catBox.innerHTML = catKeys.map(catKey => {
- const meta = catMeta[catKey] || { label: catKey, icon: '📌', badge: 'bg-slate-100 text-slate-700 border-slate-300', header: 'bg-slate-50 border-slate-200 text-slate-900' };
+ const meta = catMeta[catKey] || { label: catKey, icon: ucpIcon('tag', 'w-4 h-4 text-slate-500'), badge: 'bg-slate-100 text-slate-700 border-slate-300', header: 'bg-slate-50 border-slate-200 text-slate-900' };
const items = grouped.get(catKey) || [];
const rows = items.map(item => `
diff --git a/ucp/index.php b/ucp/index.php
index d2c791d..64b9a02 100644
--- a/ucp/index.php
+++ b/ucp/index.php
@@ -122,7 +122,9 @@ $csrfToken = UcpAuth::getCsrfToken();
-
🔒
+
Portail Utilisateur désactivé
Ce service a été temporairement désactivé par votre administrateur système.
@@ -216,29 +218,35 @@ $csrfToken = UcpAuth::getCsrfToken();
- 📱 Mes Postes & Renvois
+
+ Mes Postes & Renvois
0
- 📬 Messagerie Vocale
+
+ Messagerie Vocale
0
- 📞 Mes Appels
+
+ Mes Appels
- 📖 Annuaire & Profil
+
+ Annuaire & Profil
- 📋 Notice & Codes
+
+ Notice & Codes
- 🔐 Mon Compte
+
+ Mon Compte
@@ -451,7 +459,10 @@ $csrfToken = UcpAuth::getCsrfToken();
-
💡 Astuce : composez ces codes directement sur votre combiné IP, ou pilotez DND / renvois / messagerie depuis le portail UCP.
+
+
+
Astuce : composez ces codes directement sur votre combiné IP, ou pilotez DND / renvois / messagerie depuis le portail UCP.
+
= htmlspecialchars($companyName ?: 'NexusPBX') ?> — Document interne
@@ -531,7 +542,8 @@ $csrfToken = UcpAuth::getCsrfToken();
?
- 📷 Choisir une photo
+
+ Choisir une photo
JPG, PNG ou WebP • 5 Mo max.
diff --git a/version.json b/version.json
index 297dc13..2e427c2 100644
--- a/version.json
+++ b/version.json
@@ -1,9 +1,17 @@
{
- "version": "2.6.6",
+ "version": "2.6.7",
"codename": "Pulsar UCP",
"release_date": "2026-11-12",
"min_php_version": "8.2",
"changelog": [
+ {
+ "version": "2.6.7",
+ "date": "2026-11-12",
+ "changes": [
+ "UI Admin & UCP : Remplacement de l'ensemble des icônes emoji par des icônes vectorielles SVG uniformes (style Heroicons), sans modifier le menu latéral Admin",
+ "Correction d'un problème de syntaxe JS sur le badge d'avertissement TLS dans app.js"
+ ]
+ },
"UI v2.6.6 : menu déroulant personnalisé sombre avec icônes SVG Heroicons pour toutes les destinations d'appel (sans emojis natifs)",
"UI v2.6.5 : refonte des intitulés de destinations audio (Musique d'attente & Annonce vocale en boucle continue), nouveau wallpaper clair UCP et placeholder générique",
"Correctif v2.6.4 : prise en charge multi-groupes dans l'annuaire UCP & Admin (un poste appartenant à plusieurs groupes apparaît bien dans chacun des onglets correspondants)",