Compare commits

...
6 Commits
5 changed files with 66 additions and 48 deletions

No files matched your search

+1
View File
@@ -2621,6 +2621,7 @@ try {
break; break;
case 'run_update': case 'run_update':
@set_time_limit(300);
$output = []; $output = [];
$code = 0; $code = 0;
exec("sudo /var/www/nexus-pbx/scripts/update.sh 2>&1", $output, $code); exec("sudo /var/www/nexus-pbx/scripts/update.sh 2>&1", $output, $code);
+44 -26
View File
@@ -6330,7 +6330,12 @@ async function checkSystemUpdate(notify = false) {
if (pingDot) pingDot.classList.remove('hidden'); if (pingDot) pingDot.classList.remove('hidden');
if (changelogList && Array.isArray(data.changelog) && data.changelog.length > 0) { if (changelogList && Array.isArray(data.changelog) && data.changelog.length > 0) {
changelogList.innerHTML = data.changelog.map(item => `<li>${escapeHtml(item)}</li>`).join(''); changelogList.innerHTML = data.changelog.flatMap(item => {
if (item && typeof item === 'object' && Array.isArray(item.changes)) {
return item.changes.map(c => `<li>${escapeHtml(String(c))}</li>`);
}
return [`<li>${escapeHtml(String(item ?? ''))}</li>`];
}).join('');
if (changelogContainer) changelogContainer.classList.remove('hidden'); if (changelogContainer) changelogContainer.classList.remove('hidden');
} }
} else { } else {
@@ -6412,11 +6417,29 @@ async function executeSystemUpdate() {
showToast("Mise à jour en cours d'exécution en arrière-plan...", 'info'); showToast("Mise à jour en cours d'exécution en arrière-plan...", 'info');
// Pause du polling métriques pour éviter un 502 Nginx pendant le reload php-fpm/nginx
stopMetricsPolling();
// Purge préventive du cache local // Purge préventive du cache local
localStorage.removeItem('nexus_update_last_check_ts'); localStorage.removeItem('nexus_update_last_check_ts');
localStorage.removeItem('nexus_update_cached_data'); localStorage.removeItem('nexus_update_cached_data');
function finalizeUpdateSuccess(newVer) { async function probeUpdateReady(maxAttempts = 6, delayMs = 1500) {
for (let i = 0; i < maxAttempts; i++) {
await new Promise(r => setTimeout(r, delayMs));
try {
const probeRes = await fetch('/api.php?action=check_update&_t=' + Date.now());
if (!probeRes.ok) continue;
const probeData = await probeRes.json();
if (probeData && probeData.success && probeData.current_version) {
return probeData;
}
} catch (_) {}
}
return null;
}
async function finalizeUpdateSuccess(newVer) {
newVer = String(newVer || '').replace(/^v/, ''); newVer = String(newVer || '').replace(/^v/, '');
localStorage.setItem('nexus_update_last_check_ts', Date.now().toString()); localStorage.setItem('nexus_update_last_check_ts', Date.now().toString());
localStorage.setItem('nexus_update_cached_data', JSON.stringify({ localStorage.setItem('nexus_update_cached_data', JSON.stringify({
@@ -6449,10 +6472,11 @@ async function executeSystemUpdate() {
btnModalConfirm.innerHTML = '✓ Mise à jour terminée ! Rechargement...'; btnModalConfirm.innerHTML = '✓ Mise à jour terminée ! Rechargement...';
} }
setTimeout(() => { // Attendre que le reload différé php-fpm/nginx soit terminé avant de recharger la page
closeUpdateConfirmModal(); await new Promise(r => setTimeout(r, 2500));
window.location.href = window.location.origin + window.location.pathname + '?_t=' + Date.now(); await probeUpdateReady(5, 1000);
}, 1800); closeUpdateConfirmModal();
window.location.href = window.location.origin + window.location.pathname + '?_t=' + Date.now();
} }
try { try {
@@ -6476,18 +6500,15 @@ async function executeSystemUpdate() {
} }
// Si la réponse n'était pas un JSON valide ou a été interrompue par le rechargement de php-fpm/nginx, // Si la réponse n'était pas un JSON valide ou a été interrompue par le rechargement de php-fpm/nginx,
// on sonde le serveur pour vérifier si la mise à jour a en réalité bien été appliquée sur le disque // on sonde le serveur à plusieurs reprises pour laisser Nginx/PHP-FPM terminer leur reload
await new Promise(r => setTimeout(r, 2000)); const probeData = await probeUpdateReady(6, 1500);
try { if (probeData) {
const probeRes = await fetch('/api.php?action=check_update&_t=' + Date.now()); await finalizeUpdateSuccess(probeData.current_version);
const probeData = await probeRes.json(); return;
if (probeData && probeData.success && probeData.current_version) { }
finalizeUpdateSuccess(probeData.current_version);
return;
}
} catch (_) {}
// En cas d'échec réel avéré // En cas d'échec réel avéré
startMetricsPolling();
closeUpdateConfirmModal(); closeUpdateConfirmModal();
const errMsg = (data && (data.output || data.message)) ? (data.output || data.message) : (rawText.slice(0, 150) || "Erreur inconnue"); const errMsg = (data && (data.output || data.message)) ? (data.output || data.message) : (rawText.slice(0, 150) || "Erreur inconnue");
alert("Erreur lors de la mise à jour :\n\n" + errMsg); alert("Erreur lors de la mise à jour :\n\n" + errMsg);
@@ -6503,17 +6524,14 @@ async function executeSystemUpdate() {
} catch (e) { } catch (e) {
// En cas d'interruption réseau du socket (Safari WebKit DOMException ou timeout), // En cas d'interruption réseau du socket (Safari WebKit DOMException ou timeout),
// on vérifie avant d'alerter si le serveur a quand même réussi à déployer les fichiers // on vérifie à plusieurs reprises si le serveur a quand même réussi à déployer les fichiers
await new Promise(r => setTimeout(r, 2000)); const probeData = await probeUpdateReady(6, 1500);
try { if (probeData) {
const probeRes = await fetch('/api.php?action=check_update&_t=' + Date.now()); await finalizeUpdateSuccess(probeData.current_version);
const probeData = await probeRes.json(); return;
if (probeData && probeData.success && probeData.current_version) { }
finalizeUpdateSuccess(probeData.current_version);
return;
}
} catch (_) {}
startMetricsPolling();
closeUpdateConfirmModal(); closeUpdateConfirmModal();
alert("Erreur réseau lors de la mise à jour : " + (e.message || "Connexion interrompue")); alert("Erreur réseau lors de la mise à jour : " + (e.message || "Connexion interrompue"));
if (btnRun) { if (btnRun) {
+12 -5
View File
@@ -261,7 +261,7 @@ if [ -t 1 ]; then
systemctl reload nginx 2>/dev/null || true systemctl reload nginx 2>/dev/null || true
fail2ban-client reload >/dev/null 2>&1 || true fail2ban-client reload >/dev/null 2>&1 || true
else else
(nohup bash -c "sleep 2 && (systemctl reload php8.4-fpm 2>/dev/null || systemctl reload php8.2-fpm 2>/dev/null || systemctl reload php*-fpm 2>/dev/null || true); systemctl reload nginx 2>/dev/null || true; fail2ban-client reload >/dev/null 2>&1 || true" >/dev/null 2>&1 &) (nohup bash -c "sleep 1 && (systemctl reload php8.4-fpm 2>/dev/null || systemctl reload php8.2-fpm 2>/dev/null || systemctl reload php*-fpm 2>/dev/null || true); systemctl reload nginx 2>/dev/null || true; fail2ban-client reload >/dev/null 2>&1 || true" </dev/null >/dev/null 2>&1 &)
fi fi
NEW_VER="Inconnue" NEW_VER="Inconnue"
@@ -279,10 +279,17 @@ echo ""
if [ -f "$APP_DIR/version.json" ]; then if [ -f "$APP_DIR/version.json" ]; then
echo -e "${C_CYAN}${C_BOLD}Nouveautés de cette version :${C_RESET}" echo -e "${C_CYAN}${C_BOLD}Nouveautés de cette version :${C_RESET}"
php -r ' php -r '
$v = json_decode(file_get_contents("/var/www/nexus-pbx/version.json"), true); $v = json_decode(@file_get_contents("/var/www/nexus-pbx/version.json"), true);
if (!empty($v["changelog"])) { if (!empty($v["changelog"]) && is_array($v["changelog"])) {
foreach ($v["changelog"] as $line) { $first = $v["changelog"][0];
echo " • " . $line . "\n"; if (is_array($first) && !empty($first["changes"])) {
foreach ($first["changes"] as $line) {
echo " • " . $line . "\n";
}
} else {
foreach ($v["changelog"] as $line) {
if (is_string($line)) echo " • " . $line . "\n";
}
} }
} }
' '
+3 -1
View File
@@ -95,12 +95,14 @@ class PhonebookManager
$existing = $findStmt->fetch(PDO::FETCH_ASSOC); $existing = $findStmt->fetch(PDO::FETCH_ASSOC);
if ($existing) { if ($existing) {
$existingEmail = trim((string)($existing['email'] ?? ''));
$finalEmail = $existingEmail !== '' ? $existingEmail : $email;
$updateStmt->execute([ $updateStmt->execute([
':id' => (int)$existing['id'], ':id' => (int)$existing['id'],
':ext_id' => $extId, ':ext_id' => $extId,
':name' => $extName, ':name' => $extName,
':num' => $extNum, ':num' => $extNum,
':email' => $email, ':email' => $finalEmail,
':grp' => $group, ':grp' => $group,
]); ]);
} else { } else {
+6 -16
View File
@@ -1,24 +1,14 @@
{ {
"version": "2.7.0", "version": "2.7.3",
"codename": "Pulsar UCP", "codename": "Pulsar UCP",
"release_date": "2026-11-12", "release_date": "2026-11-12",
"min_php_version": "8.2", "min_php_version": "8.2",
"changelog": [ "changelog": [
{ "Correctif v2.7.3 : conservation de l'adresse email renseignée sur un contact interne de l'Annuaire lors de la synchronisation des postes",
"version": "2.7.0", "Correctif v2.7.2 : formatage uniforme du changelog dans version.json (suppression de [object Object] dans la fiche de mise à jour)",
"date": "2026-11-12", "Correctif v2.7.1 : suppression de l'erreur Nginx 502 transitoire après mise à jour 1-Clic (pause du polling métriques, sonde multi-essais avant rechargement)",
"changes": [ "Nouveauté v2.7.0 : logo spécifique optionnel pour le portail UCP (thème clair), avec repli automatique sur le logo principal puis NexusPBX",
"Branding UCP : Ajout d'un logo spécifique optionnel pour le portail UCP (thème clair), avec repli automatique sur le logo principal puis sur le logo NexusPBX par défaut" "UI v2.6.7 : remplacement de l'ensemble des icônes emoji par des icônes vectorielles SVG uniformes sur Admin & UCP (sans modifier le menu latéral Admin)",
]
},
{
"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.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", "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)", "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)",