commit
67e15634f3
@ -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
|
||||||
|
|
||||||
|
|
||||||
@ -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://<IP>/dashboard.html"
|
||||||
|
echo " Logs resumen: /var/log/pjsip_dashboard_summary.csv"
|
||||||
|
echo " Logs problemas: /var/log/pjsip_problems/pjsip_problems-YYYY-MM-DD.csv"
|
||||||
@ -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)<W ) {print $0}' "$TMP" | sort -t'|' -k1,1n > "$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' -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 '<tr class="%s"><td class="c-ext">%s</td><td class="c-sta">%s</td><td class="c-ip">%s</td><td class="c-rtt">%s</td></tr>\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 <<CARD
|
||||||
|
<div class="card $cls">
|
||||||
|
<div class="ext">$ext</div>
|
||||||
|
<div class="meta">
|
||||||
|
<div class="ip">$ip_show</div>
|
||||||
|
<div class="rtt">$rtt_show</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
CARD
|
||||||
|
done < "$file"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- HTML ---
|
||||||
|
cat > "$HTML_FILE" <<HTML
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>Estado de Extensiones Tuyomotor</title>
|
||||||
|
<meta http-equiv="refresh" content="60" />
|
||||||
|
<style>
|
||||||
|
:root{
|
||||||
|
--brand:#00564f;
|
||||||
|
--head:#00423d;
|
||||||
|
--ok:#6edbd2;
|
||||||
|
--slow:#d9dc3f;
|
||||||
|
--bad:#c84141;
|
||||||
|
--ok2:#45c7bd;
|
||||||
|
--bg:#f6f7f7;
|
||||||
|
--ink:#19332f;
|
||||||
|
--muted:#6a7b77;
|
||||||
|
--table:#004a44;
|
||||||
|
}
|
||||||
|
*{box-sizing:border-box}
|
||||||
|
body{margin:0;font-family:system-ui, -apple-system, Segoe UI, Roboto, Arial; background:var(--bg); color:var(--ink)}
|
||||||
|
header{display:flex;align-items:center;gap:16px;padding:20px 24px 8px}
|
||||||
|
header h1{margin:0;font-size:28px;color:var(--brand);font-weight:800}
|
||||||
|
.logo{height:36px}
|
||||||
|
.pill{position:fixed;top:16px;right:16px;background:var(--head);color:#fff;border-radius:999px;padding:8px 14px;font-weight:700;font-size:14px}
|
||||||
|
.sub{padding:0 24px 8px;color:var(--muted);font-size:12px}
|
||||||
|
.legend{padding:0 24px 6px;display:flex;gap:18px;align-items:center;flex-wrap:wrap}
|
||||||
|
.dot{display:inline-block;width:12px;height:12px;border-radius:3px;margin-right:6px;vertical-align:middle}
|
||||||
|
.dot.ok{background:var(--ok2)} .dot.slow{background:var(--slow)} .dot.bad{background:var(--bad)}
|
||||||
|
.bullets{padding:0 24px 12px}
|
||||||
|
.bullets ul{margin:6px 0 0 18px}
|
||||||
|
.btns{padding:0 24px 8px;display:flex;gap:8px}
|
||||||
|
.btns button{padding:6px 10px;border:1px solid #cfd6d5;background:#fff;border-radius:6px;cursor:pointer}
|
||||||
|
.btns button.active{background:var(--head);color:#fff;border-color:var(--head)}
|
||||||
|
.hidden{display:none !important}
|
||||||
|
|
||||||
|
/* Tabla */
|
||||||
|
table{width:100%;border-collapse:collapse;margin:10px 0 28px}
|
||||||
|
th,td{padding:10px;border:1px solid #d5e1df}
|
||||||
|
th{background:var(--table);color:#fff;text-align:left;font-weight:700}
|
||||||
|
tr.section th{background:var(--head)}
|
||||||
|
tr.ok{background:var(--ok)}
|
||||||
|
tr.slow{background:var(--slow)}
|
||||||
|
tr.bad{background:#e66a6a;color:#fff}
|
||||||
|
td.c-sta{font-weight:700}
|
||||||
|
td.c-rtt{font-weight:800}
|
||||||
|
|
||||||
|
/* Columnas */
|
||||||
|
.cols{display:grid;grid-template-columns:repeat(3,1fr);gap:18px;padding:0 24px 28px}
|
||||||
|
@media (max-width:1100px){ .cols{grid-template-columns:1fr} }
|
||||||
|
.col{background:#fff;border:1px solid #dbe7e5;border-radius:10px;overflow:hidden}
|
||||||
|
.col h3{margin:0;padding:10px 14px;background:var(--head);color:#fff;display:flex;justify-content:space-between;align-items:center}
|
||||||
|
.col h3.bad{background:var(--bad)} .col h3.slow{background:#7d8200}
|
||||||
|
.count{opacity:.9;font-weight:700}
|
||||||
|
.box{padding:12px}
|
||||||
|
.card{display:flex;gap:12px;align-items:center;border-radius:8px;padding:10px 12px;margin-bottom:8px;border:1px solid #e1ebe9;background:#fff}
|
||||||
|
.card.ok{background:var(--ok)}
|
||||||
|
.card.slow{background:var(--slow)}
|
||||||
|
.card.bad{background:#f3b3b3}
|
||||||
|
.card .ext{font-weight:900;width:52px;text-align:center}
|
||||||
|
.card .meta{display:flex;gap:16px;justify-content:space-between;width:100%}
|
||||||
|
.card .rtt{font-weight:900}
|
||||||
|
|
||||||
|
.muted{color:var(--muted)}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<span class="pill">En uso: $total_contacts / $endpoints_total</span>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<img class="logo" src="/var/www/html/logo-sapian.png" alt="Sapian" onerror="this.style.display='none'"/>
|
||||||
|
<h1>Estado de Extensiones Tuyomotor</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="sub">Última actualización: <strong>$now</strong> • Umbral de alerta de respuesta: <strong>${WARN_MS} ms</strong> (menor es mejor)</div>
|
||||||
|
|
||||||
|
<div class="legend">
|
||||||
|
<span><i class="dot ok"></i>Normal</span>
|
||||||
|
<span><i class="dot slow"></i>Respuesta lenta</span>
|
||||||
|
<span><i class="dot bad"></i>No registrado</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bullets">
|
||||||
|
<ul>
|
||||||
|
<li>Extensiones registradas (por contacto): <strong>$avail_contacts</strong></li>
|
||||||
|
<li>Extensiones no registradas (por contacto): <strong>$bad_contacts</strong></li>
|
||||||
|
<li>Con respuesta lenta (> ${WARN_MS} ms): <strong>$slow_contacts</strong></li>
|
||||||
|
<li>Total de contactos: <strong>$total_contacts</strong></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="btns">
|
||||||
|
<button id="btnTable">Tabla</button>
|
||||||
|
<button id="btnCols" class="active">Columnas</button>
|
||||||
|
<button id="btnOnlyIssues">Solo problemas</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Vista: TABLA -->
|
||||||
|
<section id="view-table" class="hidden">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Extensión</th>
|
||||||
|
<th>Estado</th>
|
||||||
|
<th>IP remota</th>
|
||||||
|
<th>Tiempo de respuesta (ms)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody id="sec-bad">
|
||||||
|
<tr class="section"><th colspan="4">NO REGISTRADAS (UNAVAILABLE)</th></tr>
|
||||||
|
$(build_rows "$TMP.bad" "bad")
|
||||||
|
</tbody>
|
||||||
|
|
||||||
|
<tbody id="sec-slow">
|
||||||
|
<tr class="section"><th colspan="4">DISPONIBLES CON RESPUESTA LENTA (> ${WARN_MS} ms)</th></tr>
|
||||||
|
$(build_rows "$TMP.slow" "slow")
|
||||||
|
</tbody>
|
||||||
|
|
||||||
|
<tbody id="sec-ok">
|
||||||
|
<tr class="section"><th colspan="4">DISPONIBLES</th></tr>
|
||||||
|
$(build_rows "$TMP.ok" "ok")
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Vista: COLUMNAS -->
|
||||||
|
<section id="view-columns">
|
||||||
|
<div class="cols">
|
||||||
|
<div class="col" id="col-bad">
|
||||||
|
<h3 class="bad">NO REGISTRADAS (UNAVAILABLE) <span class="count">($bad_contacts)</span></h3>
|
||||||
|
<div class="box">
|
||||||
|
$(build_cards "$TMP.bad" "bad")
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col" id="col-slow">
|
||||||
|
<h3 class="slow">DISPONIBLES CON RESPUESTA LENTA (> ${WARN_MS} ms) <span class="count">($slow_contacts)</span></h3>
|
||||||
|
<div class="box">
|
||||||
|
$(build_cards "$TMP.slow" "slow")
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col" id="col-ok">
|
||||||
|
<h3>DISPONIBLES <span class="count">($avail_contacts)</span></h3>
|
||||||
|
<div class="box">
|
||||||
|
$(build_cards "$TMP.ok" "ok")
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const vTable = document.getElementById('view-table');
|
||||||
|
const vCols = document.getElementById('view-columns');
|
||||||
|
const btnTable = document.getElementById('btnTable');
|
||||||
|
const btnCols = document.getElementById('btnCols');
|
||||||
|
const btnOnly = document.getElementById('btnOnlyIssues');
|
||||||
|
|
||||||
|
btnTable?.addEventListener('click', ()=>{
|
||||||
|
btnTable.classList.add('active'); btnCols.classList.remove('active');
|
||||||
|
vTable.classList.remove('hidden'); vCols.classList.add('hidden');
|
||||||
|
window.scrollTo({top:0,behavior:'smooth'});
|
||||||
|
});
|
||||||
|
btnCols?.addEventListener('click', ()=>{
|
||||||
|
btnCols.classList.add('active'); btnTable.classList.remove('active');
|
||||||
|
vCols.classList.remove('hidden'); vTable.classList.add('hidden');
|
||||||
|
window.scrollTo({top:0,behavior:'smooth'});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Solo problemas -> oculta/mostrar "Disponibles" en ambas vistas
|
||||||
|
btnOnly?.addEventListener('click', ()=>{
|
||||||
|
document.getElementById('sec-ok')?.classList.toggle('hidden'); // tabla
|
||||||
|
document.getElementById('col-ok')?.classList.toggle('hidden'); // columnas
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
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
|
||||||
@ -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
|
||||||
@ -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
|
||||||
@ -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."
|
||||||
@ -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://<IP>/dashboard.html"
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 87 KiB |
Loading…
Reference in new issue