feat(ota): add 1-click update UI & API with changelog display

This commit is contained in:
sorcier committed 2026-11-04 01:20:38 +01:00
1 parent 2376e7eebb
commit 5d60e66518
3 files changed
+230 -1

No files matched your search

+58
View File
@@ -2231,6 +2231,64 @@ try {
echo json_encode($res);
break;
case 'check_update':
$appDir = dirname(__DIR__);
$localVerJson = file_exists("{$appDir}/version.json")
? json_decode((string)file_get_contents("{$appDir}/version.json"), true)
: ['version' => '1.2.0', 'changelog' => []];
$currentVer = $localVerJson['version'] ?? '1.2.0';
$remoteVer = $currentVer;
$remoteChangelog = [];
$remoteDate = '';
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) {
$remoteData = json_decode($remoteJsonRaw, true);
if (is_array($remoteData) && !empty($remoteData['version'])) {
$remoteVer = (string)$remoteData['version'];
$remoteChangelog = $remoteData['changelog'] ?? [];
$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"));
$remoteHash = trim((string)shell_exec("git --git-dir={$appDir}/.git rev-parse origin/main 2>/dev/null"));
if (!$hasUpdate && $localHash !== '' && $remoteHash !== '' && $localHash !== $remoteHash) {
$hasUpdate = true;
}
echo json_encode([
'success' => true,
'current_version' => $currentVer,
'latest_version' => $remoteVer,
'has_update' => $hasUpdate,
'changelog' => $hasUpdate ? $remoteChangelog : ($localVerJson['changelog'] ?? []),
'release_date' => $remoteDate ?: ($localVerJson['release_date'] ?? ''),
]);
break;
case 'run_update':
$output = [];
$code = 0;
exec("sudo /var/www/nexus-pbx/scripts/update.sh 2>&1", $output, $code);
$outputStr = implode("\n", $output);
$appDir = dirname(__DIR__);
$newVerJson = file_exists("{$appDir}/version.json")
? json_decode((string)file_get_contents("{$appDir}/version.json"), true)
: ['version' => '1.3.0'];
echo json_encode([
'success' => ($code === 0),
'new_version' => $newVerJson['version'] ?? '1.3.0',
'output' => $outputStr,
'message' => ($code === 0) ? "Mise à jour réussie !" : "Erreur lors de la mise à jour",
]);
break;
default:
http_response_code(400);
echo json_encode([
+130
View File
@@ -144,6 +144,7 @@ async function loadState() {
}
renderAll();
loadBackups();
checkSystemUpdate(false);
startMetricsPolling();
} catch (err) {
if (err.message !== 'Session expirée') {
@@ -5636,3 +5637,132 @@ function copyYealinkUrl(inputId, btn) {
showToast("Sélectionnez l'URL et appuyez sur Ctrl+C.", 'info');
});
}
// =========================================================================
// GESTIONNAIRE DE MISES À JOUR DU SYSTÈME NEXUSPBX (OTA)
// =========================================================================
async function checkSystemUpdate(notify = false) {
const btnCheck = document.getElementById('btn-check-update');
const btnRun = document.getElementById('btn-run-update');
const statusText = document.getElementById('update-status-text');
const lastChecked = document.getElementById('update-last-checked');
const changelogContainer = document.getElementById('update-changelog-container');
const changelogList = document.getElementById('update-changelog-list');
const pingDot = document.getElementById('update-ping');
const verTag = document.getElementById('system-version-tag');
const appBadge = document.getElementById('app-version-badge');
if (btnCheck) {
btnCheck.disabled = true;
btnCheck.classList.add('opacity-50');
}
try {
const res = await fetch('/api.php?action=check_update');
const data = await res.json();
if (lastChecked) {
const now = new Date();
lastChecked.textContent = `Dernière vérification : ${now.toLocaleTimeString()}`;
}
if (verTag && data.current_version) {
verTag.textContent = `v${data.current_version}`;
}
if (appBadge && data.current_version) {
appBadge.textContent = `v${data.current_version}`;
}
if (data.has_update) {
if (statusText) {
statusText.className = 'text-amber-400 font-bold';
statusText.innerHTML = `🚀 Nouvelle version disponible : <span class="underline">v${escapeHtml(data.latest_version)}</span> !`;
}
if (btnRun) {
btnRun.style.display = 'inline-flex';
btnRun.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 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/></svg><span>Mettre à jour vers v${escapeHtml(data.latest_version)} (1-Clic)</span>`;
}
if (pingDot) pingDot.classList.remove('hidden');
if (changelogList && Array.isArray(data.changelog) && data.changelog.length > 0) {
changelogList.innerHTML = data.changelog.map(item => `<li>${escapeHtml(item)}</li>`).join('');
if (changelogContainer) changelogContainer.classList.remove('hidden');
}
if (notify) {
showToast(`Nouvelle version NexusPBX v${data.latest_version} disponible !`, 'info');
}
} else {
if (statusText) {
statusText.className = 'text-emerald-400 font-bold';
statusText.textContent = `Votre PBX est à jour avec la dernière version officielle (v${data.current_version}).`;
}
if (btnRun) btnRun.style.display = 'none';
if (pingDot) pingDot.classList.add('hidden');
if (changelogContainer) changelogContainer.classList.add('hidden');
if (notify) {
showToast("Votre système NexusPBX est parfaitement à jour !", 'success');
}
}
} catch (e) {
if (notify) {
showToast("Erreur lors de la vérification des mises à jour : " + e.message, 'error');
}
} finally {
if (btnCheck) {
btnCheck.disabled = false;
btnCheck.classList.remove('opacity-50');
}
}
}
async function runSystemUpdate() {
if (!confirm("Voulez-vous lancer la mise à jour de NexusPBX ?\n\nUne sauvegarde préventive automatique de vos configurations et de votre base de données sera créée.")) {
return;
}
const btnRun = document.getElementById('btn-run-update');
const statusText = document.getElementById('update-status-text');
if (btnRun) {
btnRun.disabled = true;
btnRun.innerHTML = '<span class="animate-spin mr-1.5">↻</span> Mise à jour en cours...';
}
if (statusText) {
statusText.className = 'text-cyan-300 font-bold';
statusText.textContent = 'Mise à jour en cours : sauvegarde, git pull, migrations SQLite et rechargement des services...';
}
showToast("Mise à jour en cours d'exécution en arrière-plan...", 'info');
try {
const res = await fetch('/api.php?action=run_update', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({})
});
const data = await res.json();
if (data.success) {
showToast(`NexusPBX a été mis à jour avec succès en v${data.new_version} ! Rechargement...`, 'success');
if (statusText) {
statusText.className = 'text-emerald-400 font-bold';
statusText.textContent = `Mise à jour terminée avec succès (v${data.new_version}) ! Rechargement de la page...`;
}
setTimeout(() => {
window.location.reload();
}, 2500);
} else {
alert("Erreur lors de la mise à jour :\n\n" + (data.output || data.message));
if (btnRun) {
btnRun.disabled = false;
btnRun.innerHTML = 'Réessayer la mise à jour';
}
}
} catch (e) {
alert("Erreur réseau lors de la mise à jour : " + e.message);
if (btnRun) {
btnRun.disabled = false;
btnRun.innerHTML = 'Réessayer la mise à jour';
}
}
}
+42 -1
View File
@@ -207,7 +207,7 @@ $currentUser = Auth::getUsername();
<div class="font-black text-white text-2xl tracking-tight leading-none">
Nexus<span class="text-indigo-400">PBX</span>
</div>
<span class="px-1.5 py-0.5 text-[10px] font-mono font-bold tracking-wider rounded bg-indigo-500/20 text-indigo-300 border border-indigo-500/30">v1.2.0</span>
<span id="app-version-badge" class="px-1.5 py-0.5 text-[10px] font-mono font-bold tracking-wider rounded bg-indigo-500/20 text-indigo-300 border border-indigo-500/30">v1.3.0</span>
</div>
<div class="text-[10px] text-slate-400 font-medium tracking-wide mt-0.5">Moteur VoIP & Téléphonie IP</div>
</div>
@@ -1560,6 +1560,47 @@ $currentUser = Auth::getUsername();
</div>
</div>
<!-- Mises à Jour du Système NexusPBX (OTA) -->
<div class="bg-gradient-to-r from-anthracite-800 to-indigo-950/40 border border-indigo-500/30 rounded-2xl p-6 shadow-xl space-y-4">
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 pb-4 border-b border-anthracite-700/80">
<div>
<div class="flex items-center gap-2.5">
<span class="flex h-3 w-3 relative">
<span id="update-ping" class="hidden animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
<span id="update-dot" class="relative inline-flex rounded-full h-3 w-3 bg-emerald-500"></span>
</span>
<h3 class="text-base font-bold text-white">Mises à Jour du Système NexusPBX</h3>
<span id="system-version-tag" class="px-2 py-0.5 text-xs font-mono font-bold rounded-lg bg-indigo-500/20 text-indigo-300 border border-indigo-500/30">v1.3.0</span>
</div>
<p class="text-xs text-slate-300 mt-1">Vérifiez les nouvelles versions officielles et mettez à jour votre PBX en 1 clic sans coupure de service.</p>
</div>
<div class="flex items-center gap-2">
<button type="button" onclick="checkSystemUpdate(true)" id="btn-check-update"
class="inline-flex items-center gap-2 px-3.5 py-2 rounded-xl text-xs font-semibold bg-anthracite-900 hover:bg-anthracite-750 text-slate-200 border border-anthracite-700 transition">
<svg class="w-4 h-4 text-cyan-400" 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>Vérifier les mises à jour</span>
</button>
<button type="button" onclick="runSystemUpdate()" id="btn-run-update" style="display:none;"
class="inline-flex items-center gap-2 px-4 py-2 rounded-xl text-xs font-bold bg-emerald-600 hover:bg-emerald-500 text-white shadow-lg shadow-emerald-600/30 transition animate-pulse">
<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 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/></svg>
<span>Mettre à jour maintenant (1-Clic)</span>
</button>
</div>
</div>
<!-- État & Changelog -->
<div id="update-status-box" class="p-4 rounded-xl bg-anthracite-950/80 border border-anthracite-700 text-xs space-y-2">
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-1 text-slate-300">
<span>Statut : <strong id="update-status-text" class="text-emerald-400">Votre PBX est à jour avec la dernière version officielle.</strong></span>
<span id="update-last-checked" class="text-[11px] text-slate-500 font-mono">Dernière vérification : À l'instant</span>
</div>
<div id="update-changelog-container" class="hidden pt-2 border-t border-anthracite-800 space-y-1.5">
<span class="font-bold text-white text-[11px] uppercase tracking-wider block">Nouveautés de la version :</span>
<ul id="update-changelog-list" class="list-disc list-inside text-slate-300 space-y-1 text-[11px]"></ul>
</div>
</div>
</div>
<!-- Prévisualisation en direct des fichiers natifs Asterisk -->
<div class="grid grid-cols-1 xl:grid-cols-2 gap-6">
<div class="bg-anthracite-800 border border-anthracite-700 rounded-2xl overflow-hidden shadow-lg">