400 lines
22 KiB
Bash
Executable File
400 lines
22 KiB
Bash
Executable File
#!/bin/bash
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# check_adsb.sh — Vérification complète du stack ADS-B Linux-25
|
|
# Version 3.0 — JCA 2026 — sortie terminal + HTML
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
HTML_OUT="${1:-/var/www/html/status.html}" # chemin HTML (modifiable en arg)
|
|
|
|
# ── Couleurs terminal ──────────────────────────────────────────────────────────
|
|
GREEN='\033[0;32m'; RED='\033[0;31m'; YELLOW='\033[1;33m'
|
|
CYAN='\033[0;36m'; BLUE='\033[1;34m'; BOLD='\033[1m'; NC='\033[0m'
|
|
|
|
ERRORS=0; WARNS=0
|
|
NOW=$(date '+%Y-%m-%dT%H:%M:%S')
|
|
NOW_DISP=$(date '+%a %d/%m/%Y %H:%M:%S')
|
|
IP=$(ip -4 addr show wlp58s0 2>/dev/null | grep -oP '(?<=inet )\d+\.\d+\.\d+\.\d+' | head -1)
|
|
[ -z "$IP" ] && IP=$(hostname -I | awk '{print $1}')
|
|
|
|
# ── Buffer HTML ────────────────────────────────────────────────────────────────
|
|
HTML=""
|
|
HTML_ROWS="" # lignes de détail
|
|
|
|
# ── Fonctions terminal ─────────────────────────────────────────────────────────
|
|
ok() { echo -e " ${GREEN}✓${NC} $1"; }
|
|
fail() { echo -e " ${RED}✗${NC} $1"; ERRORS=$((ERRORS+1)); }
|
|
warn() { echo -e " ${YELLOW}⚠${NC} $1"; WARNS=$((WARNS+1)); }
|
|
info() { echo -e " ${CYAN}→${NC} $1"; }
|
|
hdr() { echo -e "\n${BLUE}${BOLD}═══ $1 ═══${NC}"; }
|
|
|
|
# ── Fonctions HTML ─────────────────────────────────────────────────────────────
|
|
h_section() { HTML_ROWS+="<tr class='section-row'><td colspan='3'><b>$1</b></td></tr>"; }
|
|
h_ok() { HTML_ROWS+="<tr><td class='ic ok'>✓</td><td>$1</td><td class='tag ok'>OK</td></tr>"; }
|
|
h_fail() { HTML_ROWS+="<tr><td class='ic err'>✗</td><td>$1</td><td class='tag err'>ERREUR</td></tr>"; }
|
|
h_warn() { HTML_ROWS+="<tr><td class='ic warn'>⚠</td><td>$1</td><td class='tag warn'>ATTENTION</td></tr>"; }
|
|
h_info() { HTML_ROWS+="<tr><td class='ic inf'>→</td><td colspan='2' class='info-cell'>$1</td></tr>"; }
|
|
|
|
# ── Wrapper : terminal + HTML simultanément ───────────────────────────────────
|
|
OK() { ok "$1"; h_ok "$1"; }
|
|
FAIL() { fail "$1"; h_fail "$1"; }
|
|
WARN() { warn "$1"; h_warn "$1"; }
|
|
INFO() { info "$1"; h_info "$1"; }
|
|
HDR() { hdr "$1"; h_section "▸ $1"; }
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
# VÉRIFICATIONS
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
echo -e "${BOLD}╔══════════════════════════════════════════════════════╗${NC}"
|
|
echo -e "${BOLD}║ Stack ADS-B Linux-25 — Vérification complète ║${NC}"
|
|
echo -e "${BOLD}║ $NOW_DISP ║${NC}"
|
|
echo -e "${BOLD}╚══════════════════════════════════════════════════════╝${NC}"
|
|
INFO "IP : ${IP:-inconnue} — $NOW_DISP"
|
|
|
|
# ── 1. CONTENEURS ─────────────────────────────────────────────────────────────
|
|
HDR "1. Conteneurs Docker"
|
|
for ctn in adsb-readsb adsb-tar1090 adsb-adsb2pg adsb-webapp wordpress-postgres; do
|
|
STATUS=$(docker inspect --format '{{.State.Status}}' "$ctn" 2>/dev/null)
|
|
HEALTH=$(docker inspect --format '{{.State.Health.Status}}' "$ctn" 2>/dev/null)
|
|
UPTIME=$(docker inspect --format '{{.State.StartedAt}}' "$ctn" 2>/dev/null | \
|
|
xargs -I{} date -d {} '+%d/%m %H:%M' 2>/dev/null)
|
|
LABEL="$ctn"
|
|
[ -n "$HEALTH" ] && [ "$HEALTH" != "<nil>" ] && LABEL="$ctn [$HEALTH]"
|
|
if [ "$STATUS" = "running" ]; then
|
|
[ "$HEALTH" = "unhealthy" ] && WARN "$LABEL — démarré $UPTIME" \
|
|
|| OK "$LABEL — démarré $UPTIME"
|
|
else
|
|
FAIL "$ctn — STATUS=${STATUS:-absent}"
|
|
fi
|
|
done
|
|
|
|
# ── 2. DONGLE RTL-SDR ─────────────────────────────────────────────────────────
|
|
HDR "2. Dongle RTL-SDR"
|
|
if docker logs adsb-readsb 2>&1 | grep -q "SN 00000010"; then
|
|
GAIN=$(docker logs adsb-readsb 2>&1 | grep "tuner gain set to" | tail -1 | grep -oP '[\d.]+(?= dB)')
|
|
OK "AirNav FlightStick SN:00000010 détecté — gain ${GAIN:-?} dB"
|
|
else
|
|
FAIL "Dongle SN:00000010 non détecté dans les logs readsb"
|
|
fi
|
|
ls /dev/adsb_dongle 2>/dev/null && OK "Symlink /dev/adsb_dongle présent" \
|
|
|| WARN "Symlink /dev/adsb_dongle absent"
|
|
|
|
# ── 3. DONNÉES ADS-B ──────────────────────────────────────────────────────────
|
|
HDR "3. Données ADS-B"
|
|
AC_JSON=$(curl -s --max-time 5 "http://127.0.0.1:8090/data/aircraft.json" 2>/dev/null)
|
|
if [ -n "$AC_JSON" ]; then
|
|
MSGS=$(echo "$AC_JSON" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('messages',0))" 2>/dev/null)
|
|
AVIONS=$(echo "$AC_JSON" | python3 -c "import json,sys; d=json.load(sys.stdin); print(len(d.get('aircraft',[])))" 2>/dev/null)
|
|
WITH_POS=$(echo "$AC_JSON" | python3 -c "
|
|
import json,sys
|
|
d=json.load(sys.stdin)
|
|
print(sum(1 for a in d.get('aircraft',[]) if a.get('lat') and a.get('lon')))" 2>/dev/null)
|
|
[ "${MSGS:-0}" -gt 0 ] 2>/dev/null \
|
|
&& OK "tar1090 actif — $(printf '%s' "$MSGS" | sed ':a;s/\B[0-9]\{3\}\>/,&/;ta') messages décodés" \
|
|
|| WARN "tar1090 répond — 0 message (démarrage récent ?)"
|
|
OK "$AVIONS avion(s) en vue dont $WITH_POS avec position GPS"
|
|
else
|
|
FAIL "tar1090 ne répond pas sur :8090/data/aircraft.json"
|
|
fi
|
|
|
|
VOL_PATH=$(docker inspect adsb_readsb-run 2>/dev/null | \
|
|
python3 -c "import json,sys; d=json.load(sys.stdin); print(d[0]['Mountpoint'])" 2>/dev/null)
|
|
if [ -n "$VOL_PATH" ] && [ -f "$VOL_PATH/aircraft.pb" ]; then
|
|
AGE=$(( $(date +%s) - $(stat -c %Y "$VOL_PATH/aircraft.pb") ))
|
|
[ "$AGE" -lt 10 ] && OK "Volume readsb-run : aircraft.pb frais (${AGE}s)" \
|
|
|| WARN "Volume readsb-run : aircraft.pb âgé de ${AGE}s"
|
|
else
|
|
WARN "Volume readsb-run : aircraft.pb introuvable (mode host normal)"
|
|
fi
|
|
|
|
# ── 4. PORTS RÉSEAU ───────────────────────────────────────────────────────────
|
|
HDR "4. Ports réseau"
|
|
declare -A PORT_LABELS=(
|
|
["8080"]="webapp PHP" ["8090"]="tar1090 nginx"
|
|
["30005"]="Beast TCP" ["5432"]="PostgreSQL"
|
|
["30002"]="RAW TCP" ["30003"]="SBS TCP"
|
|
)
|
|
for port in 8080 8090 30005 5432 30002 30003; do
|
|
ss -tlnp 2>/dev/null | grep -q ":${port}[[:space:]]" \
|
|
&& OK "Port $port en écoute — ${PORT_LABELS[$port]}" \
|
|
|| FAIL "Port $port absent — ${PORT_LABELS[$port]}"
|
|
done
|
|
|
|
READSB_IP=$(docker inspect adsb-readsb 2>/dev/null | python3 -c "
|
|
import json,sys
|
|
d=json.load(sys.stdin)[0]
|
|
for n,v in d['NetworkSettings']['Networks'].items():
|
|
if 'adsb' in n: print(v['IPAddress'])" 2>/dev/null)
|
|
[ -n "$READSB_IP" ] && [ "$READSB_IP" != "None" ] \
|
|
&& OK "readsb attaché à adsb-net : $READSB_IP" \
|
|
|| FAIL "readsb non attaché à adsb-net (Beast TCP impossible)"
|
|
|
|
FAIL_CNT=$(docker logs adsb-tar1090 2>&1 | grep "Beast TCP input.*failed" | \
|
|
tail -1 | grep -oP '\d+(?= times)' || echo 0)
|
|
[ "${FAIL_CNT:-0}" -gt 50 ] 2>/dev/null \
|
|
&& FAIL "Beast TCP tar1090→readsb : $FAIL_CNT échecs" \
|
|
|| OK "Connexion Beast tar1090→readsb OK"
|
|
|
|
# ── 5. POSTGRESQL ─────────────────────────────────────────────────────────────
|
|
HDR "5. PostgreSQL — schéma adsb"
|
|
PG="docker exec wordpress-postgres psql -U pgz_admin -d wpz_postgres -t -c"
|
|
|
|
PG_VER=$($PG "SELECT version();" 2>/dev/null | head -1 | xargs)
|
|
[ -n "$PG_VER" ] && OK "PostgreSQL accessible — ${PG_VER:0:55}" || FAIL "PostgreSQL inaccessible"
|
|
|
|
PART_POS=$($PG "SELECT COUNT(*) FROM pg_class c
|
|
JOIN pg_namespace n ON n.oid=c.relnamespace
|
|
WHERE n.nspname='adsb' AND c.relname LIKE 'positions_2%' AND c.relkind='r';" 2>/dev/null | xargs)
|
|
OK "${PART_POS:-0} sous-partitions adsb.positions"
|
|
|
|
PART_HIS=$($PG "SELECT COUNT(*) FROM pg_class c
|
|
JOIN pg_namespace n ON n.oid=c.relnamespace
|
|
WHERE n.nspname='adsb' AND c.relname LIKE 'aircraft_history_%' AND c.relkind='r';" 2>/dev/null | xargs)
|
|
OK "${PART_HIS:-0} sous-partitions adsb.aircraft_history"
|
|
|
|
POS_5MIN=$($PG "SELECT COUNT(*) FROM adsb.positions
|
|
WHERE ts >= NOW() - INTERVAL '5 minutes';" 2>/dev/null | xargs)
|
|
[ "${POS_5MIN:-0}" -gt 0 ] 2>/dev/null \
|
|
&& OK "$POS_5MIN positions insérées dans les 5 dernières minutes" \
|
|
|| WARN "Aucune position dans les 5 dernières minutes"
|
|
|
|
PCT_DESC=$($PG "SELECT ROUND(COUNT(aircraft_desc)*100.0/NULLIF(COUNT(*),0),1)
|
|
FROM adsb.positions WHERE ts >= NOW() - INTERVAL '30 minutes'
|
|
AND lat IS NOT NULL;" 2>/dev/null | xargs)
|
|
python3 -c "exit(0 if float('${PCT_DESC:-0}') >= 80 else 1)" 2>/dev/null \
|
|
&& OK "aircraft_desc rempli à ${PCT_DESC}% sur 30 min" \
|
|
|| WARN "aircraft_desc rempli à ${PCT_DESC:-0}% (< 80%)"
|
|
|
|
AV_TODAY=$($PG "SELECT COUNT(DISTINCT icao) FROM adsb.aircraft_history
|
|
WHERE session_start >= CURRENT_DATE;" 2>/dev/null | xargs)
|
|
SESS_TODAY=$($PG "SELECT COUNT(*) FROM adsb.aircraft_history
|
|
WHERE session_start >= CURRENT_DATE;" 2>/dev/null | xargs)
|
|
INFO "Aujourd'hui : ${AV_TODAY:-0} avions distincts, ${SESS_TODAY:-0} sessions"
|
|
|
|
DB_SIZE=$($PG "SELECT pg_size_pretty(SUM(pg_total_relation_size(c.oid)))
|
|
FROM pg_class c JOIN pg_namespace n ON n.oid=c.relnamespace
|
|
WHERE n.nspname='adsb';" 2>/dev/null | xargs)
|
|
INFO "Taille totale schéma adsb : ${DB_SIZE:-?}"
|
|
|
|
# ── 6. ADSB2PG ────────────────────────────────────────────────────────────────
|
|
HDR "6. adsb2pg — collecteur"
|
|
LAST_INS=$(docker logs adsb-adsb2pg 2>&1 | grep "positions insérées" | tail -1)
|
|
if [ -n "$LAST_INS" ]; then
|
|
OK "adsb2pg actif"
|
|
INFO "$LAST_INS"
|
|
LOG_TIME=$(echo "$LAST_INS" | grep -oP '\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}')
|
|
AGE_LOG=$(( $(date +%s) - $(date -d "${LOG_TIME:-0}" +%s 2>/dev/null || echo $(date +%s)) ))
|
|
[ "$AGE_LOG" -lt 30 ] \
|
|
&& OK "Dernière insertion il y a ${AGE_LOG}s" \
|
|
|| WARN "Dernière insertion il y a ${AGE_LOG}s (> 30s — cycle bloqué ?)"
|
|
else
|
|
WARN "Aucun log d'insertion trouvé"
|
|
fi
|
|
|
|
# ── 7. WEBAPP ─────────────────────────────────────────────────────────────────
|
|
HDR "7. Webapp — interface utilisateur"
|
|
for action in live kpi trajectories; do
|
|
RESP=$(curl -s --max-time 5 \
|
|
"http://127.0.0.1:8080/api.php?action=${action}&period=24&granularity=60" 2>/dev/null)
|
|
if echo "$RESP" | python3 -c "import json,sys; json.load(sys.stdin)" 2>/dev/null; then
|
|
ERR=$(echo "$RESP" | python3 -c "
|
|
import json,sys
|
|
d=json.load(sys.stdin)
|
|
print(d.get('error',''))" 2>/dev/null)
|
|
[ -n "$ERR" ] && WARN "api.php?action=${action} : $ERR" \
|
|
|| OK "api.php?action=${action} : JSON valide"
|
|
else
|
|
FAIL "api.php?action=${action} : réponse non-JSON"
|
|
fi
|
|
done
|
|
|
|
TAB_CNT=$(docker exec adsb-webapp grep -c 'href="#tab' /var/www/html/index.php 2>/dev/null)
|
|
OK "${TAB_CNT:-?} onglets dans index.php"
|
|
[ "${TAB_CNT:-0}" -lt 5 ] 2>/dev/null && WARN "Moins de 5 onglets — index.php peut être obsolète"
|
|
|
|
JS_SZ=$(docker exec adsb-webapp wc -c /var/www/html/js/app.js 2>/dev/null | awk '{print $1}')
|
|
INFO "app.js : ${JS_SZ:-?} octets"
|
|
|
|
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "http://${IP}:8080/" 2>/dev/null)
|
|
[ "$HTTP_CODE" = "200" ] \
|
|
&& OK "Accès LAN http://${IP}:8080 : HTTP $HTTP_CODE" \
|
|
|| WARN "Accès LAN http://${IP}:8080 : HTTP ${HTTP_CODE:-?}"
|
|
|
|
# ── 8. AUTOGAIN ───────────────────────────────────────────────────────────────
|
|
HDR "8. Autogain"
|
|
CUR_GAIN=$(docker logs adsb-readsb 2>&1 | grep "tuner gain set to" | tail -1 | \
|
|
grep -oP '[\d.]+(?= dB)')
|
|
AG_STATE=$(docker logs adsb-readsb 2>&1 | grep "autogain" | \
|
|
grep -oP "(?<=state ')[\w]+" | tail -1)
|
|
[ -n "$CUR_GAIN" ] \
|
|
&& OK "Gain : ${CUR_GAIN} dB — état autogain : ${AG_STATE:-inconnu}" \
|
|
|| WARN "Gain courant inconnu"
|
|
|
|
INSUF=$(docker logs adsb-readsb 2>&1 | grep -c "Insufficient messages")
|
|
[ "$INSUF" -gt 3 ] \
|
|
&& WARN "$INSUF cycles avec messages insuffisants (antenne/portée ?)" \
|
|
|| INFO "$INSUF cycle(s) avec messages insuffisants (autogain en cours)"
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
# RÉSUMÉ TERMINAL
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
echo ""
|
|
echo -e "${BOLD}╔══════════════════════════════════════════════════════╗${NC}"
|
|
echo -e "${BOLD}║ RÉSUMÉ ║${NC}"
|
|
echo -e "${BOLD}╠══════════════════════════════════════════════════════╣${NC}"
|
|
if [ "$ERRORS" -eq 0 ] && [ "$WARNS" -eq 0 ]; then
|
|
GLOBAL_STATUS="OK"; GLOBAL_COLOR="$GREEN"
|
|
echo -e "${BOLD}║ ${GREEN}✓ Stack opérationnel — aucune anomalie${NC}${BOLD} ║${NC}"
|
|
elif [ "$ERRORS" -eq 0 ]; then
|
|
GLOBAL_STATUS="WARN"; GLOBAL_COLOR="$YELLOW"
|
|
echo -e "${BOLD}║ ${YELLOW}⚠ Stack opérationnel — $WARNS avertissement(s)${NC}${BOLD} ║${NC}"
|
|
else
|
|
GLOBAL_STATUS="ERROR"; GLOBAL_COLOR="$RED"
|
|
echo -e "${BOLD}║ ${RED}✗ $ERRORS erreur(s), $WARNS avertissement(s)${NC}${BOLD} ║${NC}"
|
|
fi
|
|
echo -e "${BOLD}║ Dashboard : http://${IP}:8080${NC}${BOLD} ║${NC}"
|
|
echo -e "${BOLD}║ tar1090 : http://${IP}:8090${NC}${BOLD} ║${NC}"
|
|
echo -e "${BOLD}╚══════════════════════════════════════════════════════╝${NC}"
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
# GÉNÉRATION HTML
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
case "$GLOBAL_STATUS" in
|
|
OK) BADGE_CLS="badge-ok"; BADGE_TXT="✓ Opérationnel" ;;
|
|
WARN) BADGE_CLS="badge-warn"; BADGE_TXT="⚠ ${WARNS} avertissement(s)" ;;
|
|
ERROR) BADGE_CLS="badge-err"; BADGE_TXT="✗ ${ERRORS} erreur(s)" ;;
|
|
esac
|
|
|
|
cat > "$HTML_OUT" << HTMLEOF
|
|
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
<meta http-equiv="refresh" content="60">
|
|
<title>ADS-B Linux-25 — Status</title>
|
|
<style>
|
|
:root {
|
|
--bg:#0d1117; --surface:#161b22; --border:#30363d;
|
|
--text:#c9d1d9; --muted:#8b949e; --ok:#3fb950;
|
|
--warn:#d29922; --err:#f85149; --info:#58a6ff;
|
|
--blue:#1f6feb; --teal:#39d353;
|
|
}
|
|
*{box-sizing:border-box;margin:0;padding:0}
|
|
body{background:var(--bg);color:var(--text);font-family:'Segoe UI',Arial,sans-serif;
|
|
font-size:14px;padding:20px}
|
|
h1{color:var(--info);font-size:1.4em;margin-bottom:4px}
|
|
.subtitle{color:var(--muted);font-size:.85em;margin-bottom:20px}
|
|
.header{display:flex;justify-content:space-between;align-items:flex-start;
|
|
flex-wrap:wrap;gap:12px;margin-bottom:24px;
|
|
padding:16px;background:var(--surface);border-radius:8px;
|
|
border:1px solid var(--border)}
|
|
.header-left h1{margin:0}
|
|
.kpi-row{display:flex;gap:12px;flex-wrap:wrap;margin-bottom:24px}
|
|
.kpi{background:var(--surface);border:1px solid var(--border);border-radius:8px;
|
|
padding:12px 18px;min-width:130px;text-align:center}
|
|
.kpi .val{font-size:1.8em;font-weight:700;color:var(--info)}
|
|
.kpi .lbl{font-size:.75em;color:var(--muted);margin-top:2px}
|
|
.kpi.ok-kpi .val{color:var(--ok)}
|
|
.kpi.warn-kpi .val{color:var(--warn)}
|
|
.kpi.err-kpi .val{color:var(--err)}
|
|
.badge{display:inline-block;padding:6px 16px;border-radius:20px;
|
|
font-weight:700;font-size:.95em}
|
|
.badge-ok {background:rgba(63,185,80,.15);color:var(--ok);border:1px solid var(--ok)}
|
|
.badge-warn{background:rgba(210,153,34,.15);color:var(--warn);border:1px solid var(--warn)}
|
|
.badge-err {background:rgba(248,81,73,.15);color:var(--err);border:1px solid var(--err)}
|
|
.links{display:flex;gap:10px;flex-wrap:wrap;margin-bottom:20px}
|
|
.links a{background:var(--blue);color:#fff;padding:6px 14px;border-radius:6px;
|
|
text-decoration:none;font-size:.85em}
|
|
.links a:hover{opacity:.85}
|
|
table{width:100%;border-collapse:collapse;background:var(--surface);
|
|
border:1px solid var(--border);border-radius:8px;overflow:hidden}
|
|
th{background:#1c2128;color:var(--muted);font-weight:600;text-align:left;
|
|
padding:8px 12px;font-size:.8em;text-transform:uppercase;letter-spacing:.5px}
|
|
td{padding:7px 12px;border-top:1px solid var(--border);vertical-align:middle}
|
|
tr:hover td{background:rgba(255,255,255,.03)}
|
|
.section-row td{background:#1c2128;color:var(--info);font-size:.82em;
|
|
text-transform:uppercase;letter-spacing:.8px;padding:6px 12px}
|
|
.ic{width:28px;text-align:center;font-size:1.1em}
|
|
.ic.ok {color:var(--ok)}
|
|
.ic.err {color:var(--err)}
|
|
.ic.warn{color:var(--warn)}
|
|
.ic.inf {color:var(--info)}
|
|
.tag{width:90px;text-align:center}
|
|
.tag{font-size:.75em;font-weight:700;padding:2px 8px;border-radius:12px}
|
|
.tag.ok {background:rgba(63,185,80,.15);color:var(--ok)}
|
|
.tag.err {background:rgba(248,81,73,.15);color:var(--err)}
|
|
.tag.warn{background:rgba(210,153,34,.15);color:var(--warn)}
|
|
.info-cell{color:var(--muted);font-size:.88em;font-style:italic}
|
|
.footer{margin-top:16px;color:var(--muted);font-size:.78em;text-align:right}
|
|
@media(max-width:600px){.kpi{min-width:100px}.header{flex-direction:column}}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="header">
|
|
<div class="header-left">
|
|
<h1>✈ ADS-B Linux-25 — Status</h1>
|
|
<div class="subtitle">Généré le $NOW_DISP — actualisation auto toutes les 60s</div>
|
|
<div style="margin-top:8px"><span class="badge ${BADGE_CLS}">${BADGE_TXT}</span></div>
|
|
</div>
|
|
<div>
|
|
<div style="font-size:.85em;color:var(--muted)">IP : <b style="color:var(--text)">${IP}</b></div>
|
|
<div style="font-size:.85em;color:var(--muted);margin-top:4px">
|
|
Erreurs : <b style="color:var(--err)">${ERRORS}</b>
|
|
Avert. : <b style="color:var(--warn)">${WARNS}</b>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="kpi-row">
|
|
<div class="kpi $([ "${AVIONS:-0}" -gt 0 ] 2>/dev/null && echo ok-kpi || echo warn-kpi)">
|
|
<div class="val">${AVIONS:-0}</div><div class="lbl">Avions en vue</div></div>
|
|
<div class="kpi $([ "${WITH_POS:-0}" -gt 0 ] 2>/dev/null && echo ok-kpi || echo warn-kpi)">
|
|
<div class="val">${WITH_POS:-0}</div><div class="lbl">Avec GPS</div></div>
|
|
<div class="kpi">
|
|
<div class="val" style="color:var(--teal)">${AV_TODAY:-0}</div>
|
|
<div class="lbl">Avions aujourd'hui</div></div>
|
|
<div class="kpi">
|
|
<div class="val">${SESS_TODAY:-0}</div><div class="lbl">Sessions aujourd'hui</div></div>
|
|
<div class="kpi">
|
|
<div class="val" style="font-size:1.2em">${DB_SIZE:-?}</div>
|
|
<div class="lbl">Base adsb</div></div>
|
|
<div class="kpi">
|
|
<div class="val" style="font-size:1.2em">${CUR_GAIN:-?} dB</div>
|
|
<div class="lbl">Gain SDR</div></div>
|
|
</div>
|
|
|
|
<div class="links">
|
|
<a href="http://${IP}:8080" target="_blank">🖥 Dashboard</a>
|
|
<a href="http://${IP}:8090" target="_blank">🗺 tar1090</a>
|
|
<a href="http://${IP}:8080/api.php?action=kpi&period=24&granularity=60" target="_blank">📊 API KPI</a>
|
|
<a href="http://${IP}:8080/api.php?action=live&period=24&granularity=60" target="_blank">📡 API Live</a>
|
|
</div>
|
|
|
|
<table>
|
|
<thead><tr><th>État</th><th>Détail</th><th>Statut</th></tr></thead>
|
|
<tbody>
|
|
${HTML_ROWS}
|
|
</tbody>
|
|
</table>
|
|
|
|
<div class="footer">
|
|
ADS-B Linux-25 — check_adsb.sh v3.0 — JCA 2026 |
|
|
Prochain refresh dans <span id="ctr">60</span>s
|
|
</div>
|
|
<script>
|
|
let t=60;
|
|
setInterval(()=>{ t--; document.getElementById('ctr').textContent=t;
|
|
if(t<=0) location.reload(); },1000);
|
|
</script>
|
|
</body>
|
|
</html>
|
|
HTMLEOF
|
|
|
|
# Copier dans le conteneur webapp
|
|
docker cp "$HTML_OUT" adsb-webapp:/var/www/html/status.html 2>/dev/null && \
|
|
echo -e "\n ${GREEN}→${NC} Rapport HTML : http://${IP}:8080/status.html" || \
|
|
echo -e "\n ${YELLOW}⚠${NC} Copie Docker échouée — fichier local : $HTML_OUT"
|
|
|
|
exit $ERRORS
|
|
|