diff --git a/VERSION b/VERSION index f8e233b..9ab8337 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.9.0 +1.9.1 diff --git a/changelog.json b/changelog.json index 0542337..df4cbbd 100644 --- a/changelog.json +++ b/changelog.json @@ -1,5 +1,24 @@ { "versions": [ + { + "version": "1.9.1", + "date": "2026-05-19", + "changes": { + "features": [ + "Admin: nouvelle section 'Réseau Tailscale' affichant statut de connexion, IP tailnet, hostname et serveur Headscale", + "Admin: bouton 'Actualiser' pour rafraîchir les infos Tailscale, et bloc déroulant pour consulter les 50 dernières lignes du log bootstrap", + "launcher.php: nouvelles actions get_tailscale_info et get_tailscale_log" + ], + "improvements": [ + "Vérification visuelle immédiate de l'état d'enrôlement Tailscale sans avoir à passer en SSH" + ], + "fixes": [], + "compatibility": [ + "Aucun impact sur les capteurs sans Tailscale: la section affiche 'Non installé' avec un message d'invite à mettre à jour" + ] + }, + "notes": "Complète la v1.9.0 (enrôlement automatique) avec la visibilité UI nécessaire pour valider/diagnostiquer la connexion Tailscale sur chaque capteur depuis l'admin web." + }, { "version": "1.9.0", "date": "2026-05-19", diff --git a/html/admin.html b/html/admin.html index cb24e89..769dc70 100755 --- a/html/admin.html +++ b/html/admin.html @@ -339,6 +339,45 @@ + +
+
+

Réseau Tailscale

+
+
+ État de la connexion au tailnet AirCarto + +
+
+
+
Statut
+
Chargement…
+ +
IP tailnet
+
+ +
Hostname
+
+ +
Serveur
+
+
+ +
+ Logs bootstrap (cliquer pour ouvrir) +
Chargement…
+
+
+
+
+
+
@@ -702,6 +741,9 @@ window.onload = function() { // Load firmware version loadFirmwareVersion(); + // Load Tailscale connection info + refreshTailscaleInfo(); + } //end window.onload @@ -2225,6 +2267,73 @@ function loadFirmwareVersion() { }); } +function refreshTailscaleInfo() { + $.ajax({ + url: 'launcher.php?type=get_tailscale_info', + dataType: 'json', + method: 'GET', + cache: false, + success: function(response) { + const statusBadge = document.getElementById('tailscaleStatus'); + const ipEl = document.getElementById('tailscaleIp'); + const hostEl = document.getElementById('tailscaleHostname'); + const serverEl = document.getElementById('tailscaleLoginServer'); + const msgEl = document.getElementById('tailscaleMessage'); + + serverEl.textContent = response.login_server || '—'; + + if (!response.installed) { + statusBadge.textContent = 'Non installé'; + statusBadge.className = 'badge bg-secondary'; + ipEl.textContent = '—'; + hostEl.textContent = '—'; + msgEl.style.display = 'block'; + msgEl.textContent = response.message || 'Tailscale non installé.'; + } else if (response.connected) { + statusBadge.textContent = '✓ Connecté'; + statusBadge.className = 'badge bg-success'; + ipEl.textContent = response.ip || '—'; + hostEl.textContent = response.hostname || '—'; + msgEl.style.display = 'none'; + } else { + statusBadge.textContent = '✗ Déconnecté'; + statusBadge.className = 'badge bg-danger'; + ipEl.textContent = '—'; + hostEl.textContent = '—'; + msgEl.style.display = 'block'; + msgEl.textContent = "Tailscale est installé mais n'est pas connecté au tailnet. Vérifier le log bootstrap ci-dessous ou relancer un Update firmware."; + } + + refreshTailscaleLog(); + }, + error: function() { + const statusBadge = document.getElementById('tailscaleStatus'); + statusBadge.textContent = 'Erreur'; + statusBadge.className = 'badge bg-warning'; + } + }); +} + +function refreshTailscaleLog() { + $.ajax({ + url: 'launcher.php?type=get_tailscale_log', + dataType: 'json', + method: 'GET', + cache: false, + success: function(response) { + const logEl = document.getElementById('tailscaleLog'); + if (response.success && response.log) { + logEl.textContent = response.log; + } else { + logEl.textContent = response.message || '(log vide)'; + } + }, + error: function() { + document.getElementById('tailscaleLog').textContent = '(erreur de chargement du log)'; + } + }); +} + function showChangelogModal() { const modal = new bootstrap.Modal(document.getElementById('changelogModal')); modal.show(); diff --git a/html/launcher.php b/html/launcher.php index 094d3c9..e4a8cd2 100755 --- a/html/launcher.php +++ b/html/launcher.php @@ -2050,6 +2050,69 @@ if ($type == "get_changelog") { } } +// Get Tailscale connection info (status, IP, hostname, login server) +// Used by the "Réseau Tailscale" card on admin.html. +if ($type == "get_tailscale_info") { + $login_server = 'https://headscale.aircarto.fr'; + $tailscale_bin = '/usr/bin/tailscale'; + + if (!file_exists($tailscale_bin)) { + echo json_encode([ + 'installed' => false, + 'connected' => false, + 'ip' => '', + 'hostname' => '', + 'login_server' => $login_server, + 'message' => 'Tailscale non installé sur ce capteur (mettre à jour vers v1.9.0+).' + ]); + return; + } + + // tailscaled socket is root-owned, so we need sudo (NOPASSWD rule added in v1.9.0). + $ip = trim(shell_exec("sudo $tailscale_bin ip -4 2>/dev/null") ?? ''); + $hostname = ''; + $status_raw = shell_exec("sudo $tailscale_bin status 2>/dev/null") ?? ''; + + if ($ip !== '' && $status_raw !== '') { + foreach (explode("\n", $status_raw) as $line) { + if (strpos($line, $ip) === 0) { + $parts = preg_split('/\s+/', trim($line)); + $hostname = $parts[1] ?? ''; + break; + } + } + } + + echo json_encode([ + 'installed' => true, + 'connected' => $ip !== '', + 'ip' => $ip, + 'hostname' => $hostname, + 'login_server' => $login_server + ]); + return; +} + +// Get last lines of the Tailscale bootstrap log +if ($type == "get_tailscale_log") { + $logFile = '/var/www/nebuleair_pro_4g/logs/tailscale_bootstrap.log'; + if (!file_exists($logFile)) { + echo json_encode([ + 'success' => false, + 'log' => '', + 'message' => "Pas encore de log (bootstrap jamais exécuté). Le log apparaîtra au prochain update ou reboot." + ]); + return; + } + // tail -n 50 equivalent + $output = shell_exec("tail -n 50 " . escapeshellarg($logFile) . " 2>/dev/null") ?? ''; + echo json_encode([ + 'success' => true, + 'log' => $output + ]); + return; +} + // Get current CPU power mode if ($type == "get_cpu_power_mode") { try {