commit 67e15634f3f712ac26b009231b2329f70feba05f Author: oscar.garcia Date: Fri Aug 22 15:41:05 2025 -0500 Primera Version diff --git a/README.md b/README.md new file mode 100644 index 0000000..0f52013 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# PJSIP Dashboard (HTML estático + logs) + +Genera `/var/www/html/dashboard.html` cada minuto con el estado de extensiones +PJSIP y guarda: +- Resumen diario: `/var/log/pjsip_dashboard_summary.csv` +- Problemas del día: `/var/log/pjsip_problems/pjsip_problems-YYYY-MM-DD.csv` + +## Requisitos +- Debian/Ubuntu con systemd. +- Asterisk accesible por root (o sudo) para ejecutar `asterisk -rx`. +- Servidor web sirviendo `/var/www/html` (Apache o Nginx). Si no hay, instalamos Apache. + +## Instalación rápida +```bash +git clone git@git.sapian.cloud:Sapian/realtime_dialbox8.git +cd realtime_dialbox8 +``` + +# scripts/generar_dashboard_extensiones.sh +sudo ./install.sh + +# Validaciones +## Estado del timer +systemctl status pjsip-dashboard.timer + +## Últimas corridas del servicio +journalctl -u pjsip-dashboard.service -n 20 --no-pager + +## HTML actualizado hace menos de 2 minutos +stat -c '%y %n' /var/www/html/dashboard.html + +# Últimas líneas de los logs +tail -n 10 /var/log/pjsip_dashboard_summary.csv +tail -n 10 /var/log/pjsip_problems/pjsip_problems-$(date +%F).csv + + diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..e70e197 --- /dev/null +++ b/install.sh @@ -0,0 +1,74 @@ + +--- + +## 2) `install.sh` + +```bash +#!/usr/bin/env bash +set -euo pipefail + +# ====== Parámetros ====== +SVC_NAME="pjsip-dashboard" +BIN_SRC="scripts/generar_dashboard_extensiones.sh" +BIN_DST="/usr/local/bin/generar_dashboard_extensiones.sh" +WEB_ROOT="/var/www/html" +LOG_DIR_SUM="/var/log" +LOG_DIR_PROB="/var/log/pjsip_problems" + +# ====== Comprobaciones ====== +if [[ ! -f "$BIN_SRC" ]]; then + echo "ERROR: Falta $BIN_SRC. Copia aquí TU script actual (el que ya funciona) y vuelve a correr install.sh" + exit 1 +fi + +if [[ $EUID -ne 0 ]]; then + echo "Ejecuta como root: sudo ./install.sh" + exit 1 +fi + +echo "==> Actualizando índice de paquetes" +apt-get update -y + +echo "==> Instalando dependencias mínimas" +# gawk, sed, grep, coreutils ya suelen venir; apache2 solo si no hay web root +PKGS=( gawk sed grep coreutils findutils ) +if ! command -v a2enmod >/dev/null 2>&1 && [[ ! -d "$WEB_ROOT" ]]; then + PKGS+=( apache2 ) +fi + +# Instalar bc SOLO si tu script lo usa (buscamos 'bc') +if grep -q '\bbc\b' "$BIN_SRC"; then + PKGS+=( bc ) +fi + +DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends "${PKGS[@]}" + +# Crear web root si no existe +mkdir -p "$WEB_ROOT" + +# Copiar logo si existe +if [[ -f "web/logo-sapian.png" ]]; then + cp -f web/logo-sapian.png "$WEB_ROOT/logo-sapian.png" +fi + +# Copiar script generador +install -m 0755 "$BIN_SRC" "$BIN_DST" + +# Crear directorios de logs +mkdir -p "$LOG_DIR_PROB" +chmod 755 "$LOG_DIR_PROB" + +# Instalar unit files de systemd +install -m 0644 systemd/pjsip-dashboard.service /etc/systemd/system/pjsip-dashboard.service +install -m 0644 systemd/pjsip-dashboard.timer /etc/systemd/system/pjsip-dashboard.timer + +# Recargar systemd y habilitar timer +systemctl daemon-reload +systemctl enable --now pjsip-dashboard.timer + +# Forzar una corrida para generar el HTML al tiro +systemctl start pjsip-dashboard.service || true + +echo "==> Listo. Abre: http:///dashboard.html" +echo " Logs resumen: /var/log/pjsip_dashboard_summary.csv" +echo " Logs problemas: /var/log/pjsip_problems/pjsip_problems-YYYY-MM-DD.csv" diff --git a/scripts/generar_dashboard_extensiones.sh b/scripts/generar_dashboard_extensiones.sh new file mode 100755 index 0000000..e9dfadd --- /dev/null +++ b/scripts/generar_dashboard_extensiones.sh @@ -0,0 +1,328 @@ +#!/usr/bin/env bash +# -*- coding: utf-8 -*- +# Tablero PJSIP: Tabla + Columnas, con "Solo problemas" y contadores en columnas. + +set -Eeuo pipefail +LANG=C +PATH=/usr/sbin:/usr/bin:/sbin:/bin + +HTML_FILE="/var/www/html/dashboard.html" + + +# --- Parámetros (umbral) --- +WARN_MS_DEFAULT=300 +WARN_MS="$WARN_MS_DEFAULT" +if [[ -r /etc/default/pjsip-dashboard ]]; then + # Permite definir WARN_MS=300 en /etc/default/pjsip-dashboard (opcional) + # shellcheck disable=SC1091 + . /etc/default/pjsip-dashboard || true +fi +[[ -n "${WARN_MS:-}" ]] || WARN_MS="$WARN_MS_DEFAULT" + +# --- Capturar contactos PJSIP --- +TMP=$(mktemp) +trap 'rm -f "$TMP" "$TMP.ok" "$TMP.slow" "$TMP.bad"' EXIT + +asterisk -rx "pjsip show contacts" >"$TMP.raw" || { + echo "No pude ejecutar 'pjsip show contacts'"; exit 1; } + +# Parseo robusto: Contact: 100/sip:100@IP ... (Avail|Unavail) RTT +awk ' + BEGIN{ OFS="|" } + /^[[:space:]]*Contact:/ { + # EXT, IP, STATUS, RTT (num o nan) + if (match($0, /^[[:space:]]*Contact:[[:space:]]*([0-9]+)\/sip:[^@]+@([^ :;]+).* (Avail|Unavail)[[:space:]]+([0-9.]+|nan)$/, m)) { + print m[1], m[2], m[3], m[4]; + } else if (match($0, /^[[:space:]]*Contact:[[:space:]]*([0-9]+)\/sip:[^ ]+.* (Avail|Unavail)[[:space:]]+([0-9.]+|nan)$/, m)) { + print m[1], "", m[2], m[3]; + } + } +' "$TMP.raw" > "$TMP" + +# Contadores por contacto +total_contacts=$(wc -l < "$TMP" | tr -d ' ') +bad_contacts=$(awk -F'|' '$3=="Unavail"{c++} END{print c+0}' "$TMP") +avail_contacts=$(awk -F'|' '$3=="Avail"{c++} END{print c+0}' "$TMP") +slow_contacts=$(awk -F'|' -v W="$WARN_MS" '$3=="Avail" && $4 ~ /^[0-9.]+$/ && ($4+0)>=W {c++} END{print c+0}' "$TMP") + +# Grupos ordenados +awk -F'|' -v W="$WARN_MS" '$3=="Unavail"{print $0}' "$TMP" | sort -t'|' -k1,1n > "$TMP.bad" +awk -F'|' -v W="$WARN_MS" '$3=="Avail" && $4 ~ /^[0-9.]+$/ && ($4+0)>=W {print $0}' "$TMP" | sort -t'|' -k4,4nr -k1,1n > "$TMP.slow" +awk -F'|' -v W="$WARN_MS" '($3=="Avail") && ( $4 !~ /^[0-9.]+$/ || ($4+0) "$TMP.ok" + +# Endpoints totales (solo numéricos; excluye troncales con letras) +endpoints_total=$( + asterisk -rx "pjsip show endpoints" \ + | sed -E 's/\x1B\[[0-9;]*[A-Za-z]//g' \ + | awk 'match($0,/^ *Endpoint:[[:space:]]*([0-9]+)\//,m){seen[m[1]]=1} END{print length(seen)+0}' +) + +# Fecha +now="$(date '+%Y-%m-%d %H:%M:%S %z')" + +# Utilidades de render +html_escape() { + sed -e 's/&/\&/g' -e 's//\>/g' +} + +# Construir filas HTML (TABLA) +build_rows() { + # $1 = file; $2 = clase (ok|slow|bad) + local file="$1" cls="$2" + while IFS='|' read -r ext ip status rtt; do + [[ -n "${ext:-}" ]] || continue + local ip_show rtt_show rowcls + ip_show="${ip:-—}" + if [[ "$cls" == "bad" ]]; then + rtt_show="Sin respuesta" + else + if [[ "$rtt" == "nan" || -z "$rtt" ]]; then rtt_show="—"; else rtt_show="$rtt"; fi + fi + rowcls="$cls" + printf '%s%s%s%s\n' \ + "$rowcls" "$(printf '%s' "$ext" | html_escape)" \ + "$([[ "$cls" == "bad" ]] && echo "UNAVAILABLE" || echo "AVAILABLE")" \ + "$(printf '%s' "$ip_show" | html_escape)" \ + "$(printf '%s' "$rtt_show" | html_escape)" + done < "$file" +} + +# Construir items (COLUMNAS) +build_cards() { + # $1 = file; $2 = clase (ok|slow|bad) + local file="$1" cls="$2" + while IFS='|' read -r ext ip status rtt; do + [[ -n "${ext:-}" ]] || continue + local ip_show rtt_show + ip_show="${ip:-—}" + if [[ "$cls" == "bad" ]]; then + rtt_show="Sin respuesta" + else + if [[ "$rtt" == "nan" || -z "$rtt" ]]; then rtt_show="—"; else rtt_show="$rtt"; fi + fi + cat < +
$ext
+
+
$ip_show
+
$rtt_show
+
+ +CARD + done < "$file" +} + +# --- HTML --- +cat > "$HTML_FILE" < + + + + Estado de Extensiones Tuyomotor + + + + + + En uso: $total_contacts / $endpoints_total + +
+ +

Estado de Extensiones Tuyomotor

+
+ +
Última actualización: $now • Umbral de alerta de respuesta: ${WARN_MS} ms (menor es mejor)
+ +
+ Normal + Respuesta lenta + No registrado +
+ +
+
    +
  • Extensiones registradas (por contacto): $avail_contacts
  • +
  • Extensiones no registradas (por contacto): $bad_contacts
  • +
  • Con respuesta lenta (> ${WARN_MS} ms): $slow_contacts
  • +
  • Total de contactos: $total_contacts
  • +
+
+ +
+ + + +
+ + + + + +
+
+
+

NO REGISTRADAS (UNAVAILABLE) ($bad_contacts)

+
+$(build_cards "$TMP.bad" "bad") +
+
+ +
+

DISPONIBLES CON RESPUESTA LENTA (> ${WARN_MS} ms) ($slow_contacts)

+
+$(build_cards "$TMP.slow" "slow") +
+
+ +
+

DISPONIBLES ($avail_contacts)

+
+$(build_cards "$TMP.ok" "ok") +
+
+
+
+ + + + +HTML + +echo "✅ Dashboard actualizado (tabla+columnas + Solo problemas): $HTML_FILE" + + + +# === LOG RESUMEN (simple) — conserva 7 días === +HTML="/var/www/html/dashboard.html" +LOG="/var/log/pjsip_dashboard_summary.csv" +mkdir -p /var/log + +ts="$(date -Is)" + +# Función: encuentra la línea por patrón, quita etiquetas HTML y devuelve el primer número +get_num(){ + local pat="$1" + awk -v p="$pat" 'index($0,p){print; exit}' "$HTML" \ + | sed -E 's/<[^>]+>//g' \ + | grep -oE '[0-9]+' | head -1 +} + +reg="$( get_num 'Extensiones registradas (por contacto)')" +unreg="$( get_num 'Extensiones no registradas (por contacto)')" +slow="$( get_num 'Con respuesta lenta (> 300 ms)')" +total="$( get_num 'Total de contactos')" + +[ -s "$LOG" ] || echo "timestamp,registradas,no_registradas,lentas,total" > "$LOG" +echo "$ts,$reg,$unreg,$slow,$total" >> "$LOG" + +# Mantener ~7 días si corre cada minuto (10 080 filas) +tail -n 10080 "$LOG" > "$LOG.tmp" && mv "$LOG.tmp" "$LOG" +# === FIN LOG RESUMEN === +/usr/local/bin/pjsip_log_problems.sh diff --git a/systemd/pjsip-dashboard.service b/systemd/pjsip-dashboard.service new file mode 100644 index 0000000..47407d5 --- /dev/null +++ b/systemd/pjsip-dashboard.service @@ -0,0 +1,14 @@ +[Unit] +Description=Generar dashboard PJSIP (HTML estático) +Wants=pjsip-dashboard.timer + +[Service] +Type=oneshot +ExecStart=/usr/local/bin/generar_dashboard_extensiones.sh +# Opcional: evita saturar si hay múltiples ejecuciones solapadas +Nice=10 +IOSchedulingClass=best-effort +IOSchedulingPriority=7 + +[Install] +WantedBy=multi-user.target diff --git a/systemd/pjsip-dashboard.timer b/systemd/pjsip-dashboard.timer new file mode 100644 index 0000000..b9248a8 --- /dev/null +++ b/systemd/pjsip-dashboard.timer @@ -0,0 +1,12 @@ +[Unit] +Description=Programación del dashboard PJSIP (cada 60s) + +[Timer] +OnBootSec=30s +OnUnitActiveSec=60s +AccuracySec=5s +Persistent=true +Unit=pjsip-dashboard.service + +[Install] +WantedBy=timers.target diff --git a/uninstall.sh b/uninstall.sh new file mode 100644 index 0000000..f6629b2 --- /dev/null +++ b/uninstall.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -euo pipefail + +SVC_NAME="pjsip-dashboard" + +if [[ $EUID -ne 0 ]]; then + echo "Ejecuta como root: sudo ./uninstall.sh" + exit 1 +fi + +systemctl disable --now ${SVC_NAME}.timer || true +systemctl stop ${SVC_NAME}.service || true + +rm -f /etc/systemd/system/${SVC_NAME}.service +rm -f /etc/systemd/system/${SVC_NAME}.timer +systemctl daemon-reload + +# NO borramos HTML ni logs por si quieres conservarlos +# rm -f /var/www/html/dashboard.html +# rm -f /usr/local/bin/generar_dashboard_extensiones.sh + +echo "Desinstalado ${SVC_NAME}. Puedes borrar manualmente HTML y logs si lo deseas." diff --git a/upgrade.sh b/upgrade.sh new file mode 100644 index 0000000..26024ca --- /dev/null +++ b/upgrade.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail + +BIN_SRC="scripts/generar_dashboard_extensiones.sh" +BIN_DST="/usr/local/bin/generar_dashboard_extensiones.sh" + +if [[ $EUID -ne 0 ]]; then + echo "Ejecuta como root: sudo ./upgrade.sh" + exit 1 +fi + +if [[ ! -f "$BIN_SRC" ]]; then + echo "ERROR: Falta $BIN_SRC" + exit 1 +fi + +install -m 0755 "$BIN_SRC" "$BIN_DST" +systemctl daemon-reload +# Forzar una corrida para ver cambios de inmediato +systemctl start pjsip-dashboard.service || true + +echo "Actualizado. Revisa http:///dashboard.html" + diff --git a/web/logo-sapian.png b/web/logo-sapian.png new file mode 100755 index 0000000..ce94505 Binary files /dev/null and b/web/logo-sapian.png differ