v1.9.1: Admin UI - Section Reseau Tailscale (statut, IP, hostname, logs)
- admin.html: nouveau card 'Reseau Tailscale' avec statut connecte/deconnecte, IP tailnet, hostname, serveur Headscale et bouton Actualiser - admin.html: bloc deroulant pour consulter les 50 dernieres lignes du log bootstrap (logs/tailscale_bootstrap.log) - launcher.php: nouvelles actions get_tailscale_info (status + IP + hostname via sudo tailscale ip/status) et get_tailscale_log (tail -n 50) Complete la v1.9.0 avec la visibilite UI necessaire pour valider/diagnostiquer la connexion Tailscale sur chaque capteur sans avoir a passer en SSH. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,24 @@
|
|||||||
{
|
{
|
||||||
"versions": [
|
"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",
|
"version": "1.9.0",
|
||||||
"date": "2026-05-19",
|
"date": "2026-05-19",
|
||||||
|
|||||||
109
html/admin.html
109
html/admin.html
@@ -339,6 +339,45 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- TAILSCALE SECTION -->
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-lg-8 col-12">
|
||||||
|
<h4 class="mt-4">Réseau Tailscale</h4>
|
||||||
|
<div id="tailscale-card" class="card">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<span class="fw-bold">État de la connexion au tailnet AirCarto</span>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-primary" onclick="refreshTailscaleInfo()">
|
||||||
|
<svg width="14" height="14" fill="currentColor" class="bi bi-arrow-clockwise" viewBox="0 0 16 16">
|
||||||
|
<path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z"/>
|
||||||
|
<path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z"/>
|
||||||
|
</svg>
|
||||||
|
Actualiser
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<dl class="row mb-2">
|
||||||
|
<dt class="col-sm-3 text-muted">Statut</dt>
|
||||||
|
<dd class="col-sm-9"><span id="tailscaleStatus" class="badge bg-secondary">Chargement…</span></dd>
|
||||||
|
|
||||||
|
<dt class="col-sm-3 text-muted">IP tailnet</dt>
|
||||||
|
<dd class="col-sm-9"><code id="tailscaleIp">—</code></dd>
|
||||||
|
|
||||||
|
<dt class="col-sm-3 text-muted">Hostname</dt>
|
||||||
|
<dd class="col-sm-9"><code id="tailscaleHostname">—</code></dd>
|
||||||
|
|
||||||
|
<dt class="col-sm-3 text-muted">Serveur</dt>
|
||||||
|
<dd class="col-sm-9"><code id="tailscaleLoginServer">—</code></dd>
|
||||||
|
</dl>
|
||||||
|
<div id="tailscaleMessage" class="alert alert-warning small mb-2" style="display: none;"></div>
|
||||||
|
<details>
|
||||||
|
<summary class="text-muted small" style="cursor: pointer;">Logs bootstrap (cliquer pour ouvrir)</summary>
|
||||||
|
<pre id="tailscaleLog" class="mb-0 mt-2" style="max-height: 250px; overflow-y: auto; font-size: 0.8rem; background-color: #f8f9fa; padding: 0.75rem; border-radius: 0.375rem;">Chargement…</pre>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- SYSTEMD SERVICES SECTION -->
|
<!-- SYSTEMD SERVICES SECTION -->
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-lg-8 col-12">
|
<div class="col-lg-8 col-12">
|
||||||
@@ -702,6 +741,9 @@ window.onload = function() {
|
|||||||
// Load firmware version
|
// Load firmware version
|
||||||
loadFirmwareVersion();
|
loadFirmwareVersion();
|
||||||
|
|
||||||
|
// Load Tailscale connection info
|
||||||
|
refreshTailscaleInfo();
|
||||||
|
|
||||||
} //end window.onload
|
} //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() {
|
function showChangelogModal() {
|
||||||
const modal = new bootstrap.Modal(document.getElementById('changelogModal'));
|
const modal = new bootstrap.Modal(document.getElementById('changelogModal'));
|
||||||
modal.show();
|
modal.show();
|
||||||
|
|||||||
@@ -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
|
// Get current CPU power mode
|
||||||
if ($type == "get_cpu_power_mode") {
|
if ($type == "get_cpu_power_mode") {
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user