Compare commits

...
2 Commits
6 changed files with 203 additions and 18 deletions

No files matched your search

+1
View File
@@ -142,6 +142,7 @@ else
echo " -> Clonage du dépôt public officiel depuis https://git.sorcier-research.com..."
rm -rf "$TARGET_DIR"
git clone https://git.sorcier-research.com/sorcier/NexusPBX.git "$TARGET_DIR"
git config --system --add safe.directory "$TARGET_DIR" 2>/dev/null || true
cd "$TARGET_DIR"
fi
+20
View File
@@ -2242,6 +2242,25 @@ try {
$remoteChangelog = [];
$remoteDate = '';
// 1. Interrogation directe via HTTP Raw (indépendant de .git et ultra-rapide)
$rawHttp = @shell_exec("curl -k -s --connect-timeout 4 \"https://git.sorcier-research.com/sorcier/NexusPBX/raw/branch/main/version.json\" 2>/dev/null");
if (empty($rawHttp)) {
$ctx = stream_context_create([
'http' => ['timeout' => 4],
'ssl' => ['verify_peer' => false, 'verify_peer_name' => false]
]);
$rawHttp = @file_get_contents("https://git.sorcier-research.com/sorcier/NexusPBX/raw/branch/main/version.json", false, $ctx);
}
if (!empty($rawHttp)) {
$remoteData = json_decode((string)$rawHttp, true);
if (is_array($remoteData) && !empty($remoteData['version'])) {
$remoteVer = (string)$remoteData['version'];
$remoteChangelog = $remoteData['changelog'] ?? [];
$remoteDate = $remoteData['release_date'] ?? '';
}
} else {
// 2. Fallback local Git si indisponibilité du serveur HTTP
exec("git --git-dir={$appDir}/.git fetch origin main --quiet 2>/dev/null", $output, $code);
$remoteJsonRaw = shell_exec("git --git-dir={$appDir}/.git show origin/main:version.json 2>/dev/null");
if ($remoteJsonRaw) {
@@ -2252,6 +2271,7 @@ try {
$remoteDate = $remoteData['release_date'] ?? '';
}
}
}
$hasUpdate = version_compare($remoteVer, $currentVer, '>');
$localHash = trim((string)shell_exec("git --git-dir={$appDir}/.git rev-parse HEAD 2>/dev/null"));
+88 -13
View File
@@ -4945,28 +4945,93 @@ async function createBackupNow() {
}
}
async function confirmRestoreBackup(filename) {
const ok = confirm(`⚠️ ATTENTION — RESTAURATION COMPLÈTE DU SYSTÈME NEXUSPBX ⚠️\n\nVoulez-vous restaurer le système depuis l'archive :\n${filename} ?\n\nCette action va :\n1. Remplacer la base de données SQLite\n2. Restaurer tous les fichiers sons, annonces et musiques\n3. Restaurer toutes les boîtes vocales, salutations et PINs\n4. Recharger l'intégralité du PBX Asterisk\n\nConfirmez-vous la restauration immédiate ?`);
if (!ok) return;
let pendingRestoreAction = null;
let pendingRestoreInput = null;
showToast("Restauration du système en cours... Veuillez patienter.", "info");
function showRestoreModal({ title, filename, badgeText, onConfirm, inputEl = null }) {
const titleEl = document.getElementById('modal-restore-title');
if (titleEl) titleEl.textContent = title || "Restauration Complète du Système";
const nameEl = document.getElementById('restore-target-name');
if (nameEl) nameEl.textContent = filename || "Archive de sauvegarde";
const badgeEl = document.getElementById('restore-target-badge');
if (badgeEl) badgeEl.textContent = badgeText || "Archive";
const confirmBtn = document.getElementById('btn-confirm-restore');
const cancelBtn = document.getElementById('btn-cancel-restore');
if (confirmBtn) {
confirmBtn.disabled = false;
confirmBtn.innerHTML = `
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
<span>Confirmer la Restauration</span>
`;
}
if (cancelBtn) cancelBtn.disabled = false;
pendingRestoreAction = onConfirm;
pendingRestoreInput = inputEl;
openModal('modal-restore-backup');
}
function closeRestoreModal() {
closeModal('modal-restore-backup');
if (pendingRestoreInput) {
pendingRestoreInput.value = '';
pendingRestoreInput = null;
}
pendingRestoreAction = null;
}
async function executePendingRestore() {
if (!pendingRestoreAction) return;
const confirmBtn = document.getElementById('btn-confirm-restore');
const cancelBtn = document.getElementById('btn-cancel-restore');
if (confirmBtn) {
confirmBtn.disabled = true;
confirmBtn.innerHTML = `
<svg class="animate-spin w-4 h-4" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8H4z"></path></svg>
<span>Restauration en cours...</span>
`;
}
if (cancelBtn) cancelBtn.disabled = true;
try {
await pendingRestoreAction();
} catch (err) {
showToast("Erreur inattendue : " + err.message, "error");
closeRestoreModal();
}
}
function confirmRestoreBackup(filename) {
showRestoreModal({
title: "Restauration du Système NexusPBX",
filename: filename,
badgeText: "Archive Locale",
onConfirm: async () => {
showToast("Restauration du système en cours... Veuillez patienter.", "info");
try {
const res = await apiCall('restore_backup', { filename });
closeRestoreModal();
showToast(res.message || "Restauration terminée avec succès !", 'success');
setTimeout(() => {
window.location.reload();
}, 2000);
} catch (err) {
closeRestoreModal();
showToast("Échec de la restauration : " + err.message, 'error');
}
}
});
}
function triggerBackupUpload() {
document.getElementById('backup-upload-input')?.click();
}
async function handleBackupUpload(input) {
function handleBackupUpload(input) {
const file = input?.files?.[0];
if (!file) return;
@@ -4977,14 +5042,15 @@ async function handleBackupUpload(input) {
return;
}
const ok = confirm(`⚠️ RESTAURATION DISASTER RECOVERY VIA FICHIER TÉLÉVERSÉ ⚠️\n\nFichier sélectionné : ${file.name} (${(file.size / (1024 * 1024)).toFixed(2)} Mo)\n\nVoulez-vous restaurer immédiatement le système NexusPBX avec cette archive ?\nToutes les données actuelles seront remplacées.`);
if (!ok) {
input.value = '';
return;
}
const fileSizeMb = (file.size / (1024 * 1024)).toFixed(2);
showRestoreModal({
title: "Disaster Recovery — Archive Téléversée",
filename: `${file.name} (${fileSizeMb} Mo)`,
badgeText: "Fichier Téléversé",
inputEl: input,
onConfirm: async () => {
showToast("Téléversement et restauration de l'archive en cours...", "info");
const fd = new FormData();
fd.append('backup_file', file);
@@ -4994,6 +5060,7 @@ async function handleBackupUpload(input) {
body: fd
});
const res = await response.json();
closeRestoreModal();
if (!res.success) {
throw new Error(res.message || "Erreur lors de la restauration");
}
@@ -5002,11 +5069,12 @@ async function handleBackupUpload(input) {
window.location.reload();
}, 2000);
} catch (err) {
closeRestoreModal();
showToast("Échec de la restauration : " + err.message, 'error');
} finally {
input.value = '';
}
}
});
}
function downloadBackup(filename) {
window.location.href = `/api.php?action=download_backup&filename=${encodeURIComponent(filename)}`;
@@ -5677,11 +5745,17 @@ async function checkSystemUpdate(notify = false) {
return;
} catch (_) {}
}
} else {
localStorage.removeItem(STORAGE_KEY_TIME);
localStorage.removeItem(STORAGE_KEY_DATA);
}
let origBtnHtml = null;
if (btnCheck) {
btnCheck.disabled = true;
btnCheck.classList.add('opacity-50');
origBtnHtml = btnCheck.innerHTML;
btnCheck.innerHTML = `<svg class="animate-spin w-4 h-4 text-cyan-400" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8H4z"></path></svg><span>Vérification en cours...</span>`;
}
try {
@@ -5709,6 +5783,7 @@ async function checkSystemUpdate(notify = false) {
if (btnCheck) {
btnCheck.disabled = false;
btnCheck.classList.remove('opacity-50');
if (origBtnHtml) btnCheck.innerHTML = origBtnHtml;
}
}
+88
View File
@@ -4628,6 +4628,94 @@ $currentUser = Auth::getUsername();
</div>
</div>
<!-- ===================================================================== -->
<!-- MODALE : CONFIRMATION RESTAURATION DU SYSTÈME (THEME NEXUSPBX) -->
<!-- ===================================================================== -->
<div id="modal-restore-backup" class="fixed inset-0 z-50 hidden items-center justify-center bg-black/80 backdrop-blur-sm p-3 sm:p-4 overflow-y-auto">
<div class="bg-anthracite-900 border border-amber-500/40 rounded-2xl w-full max-w-lg shadow-2xl overflow-hidden my-6 animate-fadeIn">
<!-- Entête stylisée -->
<div class="px-6 py-5 border-b border-anthracite-800 bg-gradient-to-r from-amber-950/40 via-anthracite-900 to-anthracite-900 flex items-start justify-between">
<div class="flex items-center gap-3.5">
<div class="w-11 h-11 rounded-xl bg-amber-500/15 border border-amber-500/30 text-amber-400 flex items-center justify-center shrink-0 shadow-lg shadow-amber-950/50">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
</svg>
</div>
<div>
<div class="flex items-center gap-2">
<h3 id="modal-restore-title" class="text-base font-bold text-white">Restauration Complète du Système</h3>
<span id="restore-target-badge" class="px-2 py-0.5 rounded text-[10px] font-bold tracking-wide uppercase bg-amber-500/20 text-amber-300 border border-amber-500/30">Archive</span>
</div>
<p class="text-xs text-slate-400 mt-0.5">Disaster Recovery & Remise en état Asterisk</p>
</div>
</div>
<button type="button" onclick="closeRestoreModal()" class="text-slate-400 hover:text-white p-1.5 rounded-lg hover:bg-anthracite-800 transition">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
</button>
</div>
<!-- Corps de la modale -->
<div class="p-6 space-y-4">
<!-- Fichier cible -->
<div class="p-3.5 rounded-xl bg-anthracite-950/90 border border-anthracite-800 space-y-1.5">
<div class="text-[11px] font-semibold text-slate-400 flex items-center gap-1.5 uppercase tracking-wider">
<svg class="w-3.5 h-3.5 text-amber-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4"/></svg>
Archive sélectionnée pour la restauration
</div>
<div id="restore-target-name" class="font-mono text-xs font-semibold text-amber-300 break-all bg-black/40 px-3 py-2 rounded-lg border border-anthracite-800/80">
nom_de_l_archive.tar.gz
</div>
</div>
<!-- Ce que fait la restauration -->
<div class="space-y-2">
<div class="text-xs font-semibold text-slate-300">Cette action va restaurer et écraser :</div>
<ul class="text-xs text-slate-400 space-y-1.5 pl-1">
<li class="flex items-center gap-2">
<span class="w-1.5 h-1.5 rounded-full bg-cyan-400 shrink-0"></span>
<span><strong>Base SQLite</strong> : postes SIP, trunks, routes, SVI, téléavertissement, annuaires.</span>
</li>
<li class="flex items-center gap-2">
<span class="w-1.5 h-1.5 rounded-full bg-indigo-400 shrink-0"></span>
<span><strong>Médiathèque</strong> : jingles, musiques d'attente (MoH), annonces personnalisées.</span>
</li>
<li class="flex items-center gap-2">
<span class="w-1.5 h-1.5 rounded-full bg-amber-400 shrink-0"></span>
<span><strong>Messageries vocales</strong> : boîtes vocales, salutations enregistrées, PINs.</span>
</li>
<li class="flex items-center gap-2">
<span class="w-1.5 h-1.5 rounded-full bg-emerald-400 shrink-0"></span>
<span><strong>Moteur Asterisk</strong> : régénération du dialplan et rechargement PJSIP à chaud.</span>
</li>
</ul>
</div>
<!-- Avertissement -->
<div class="p-3 rounded-xl bg-amber-950/30 border border-amber-500/30 flex items-start gap-3">
<svg class="w-5 h-5 text-amber-400 shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
</svg>
<div class="text-xs text-amber-200/90 leading-relaxed">
<strong>Attention :</strong> Toutes les données et configurations modifiées après la date de cette sauvegarde seront définitivement remplacées.
</div>
</div>
<!-- Boutons d'action -->
<div class="pt-3 border-t border-anthracite-800 flex items-center justify-end gap-2.5">
<button type="button" id="btn-cancel-restore" onclick="closeRestoreModal()"
class="px-4 py-2.5 rounded-xl text-xs font-semibold bg-anthracite-800 hover:bg-anthracite-750 text-slate-300 border border-anthracite-700 transition">
Annuler
</button>
<button type="button" id="btn-confirm-restore" onclick="executePendingRestore()"
class="px-5 py-2.5 rounded-xl text-xs font-bold bg-gradient-to-r from-amber-600 via-red-600 to-red-700 hover:from-amber-500 hover:to-red-600 text-white shadow-lg shadow-red-900/40 transition flex items-center gap-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
<span>Confirmer la Restauration</span>
</button>
</div>
</div>
</div>
</div>
<!-- ===================================================================== -->
<!-- MODAL : URL XML YEALINK REMOTE PHONEBOOK (cm_to_yl_ab.php) -->
<!-- ===================================================================== -->
+1
View File
@@ -37,6 +37,7 @@ for arg in "$@"; do
done
cd "$APP_DIR" || exit 1
git config --system --add safe.directory "$APP_DIR" 2>/dev/null || true
git config --global --add safe.directory "$APP_DIR" 2>/dev/null || true
# Lecture de la version locale
+5 -5
View File
@@ -1,12 +1,12 @@
{
"version": "1.3.5",
"version": "1.3.7",
"codename": "Helios",
"release_date": "2026-11-04",
"min_php_version": "8.2",
"changelog": [
"Désactivation du bac à sable ProtectSystem=full sur PHP-FPM pour autoriser les écritures système lors des mises à jour",
"Sécurisation non-bloquante de la création des liens symboliques /usr/local/bin/ dans le script de mise à jour",
"Ajout automatique du paquet sshpass dans install.sh et le processus de mise à jour pour les sauvegardes distantes",
"Autorisation sudoers www-data automatique pour les mises à jour 1-clic depuis l'interface Web"
"Vérification ultra-rapide des mises à jour en direct via cURL CLI et flux HTTP sans dépendre de git ou de php-curl",
"Bouton 'Vérifier les mises à jour' forçant le rafraîchissement immédiat du cache navigateur (bypass 24h)",
"Nouvelle modale de confirmation de restauration système alignée sur le thème sombre NexusPBX",
"Remplacement des popups d'alerte natives du navigateur lors des restaurations locales et téléversées"
]
}