Panel de monitorizacion en tiempo real y estadisticas CDR para Asterisk PBX
Reemplazar una solucion lenta y externa por una herramienta interna de alto rendimiento integrada con Asterisk.
Visualizacion del estado de aproximadamente 1000 extensiones (libre, en llamada, sonando, en espera, DND, no registrada) con latencia inferior a 100 ms via WebSocket.
Busqueda y estadisticas sobre el historico completo de llamadas, reemplazando CDRStats que consultaba MySQL directamente en el servidor Asterisk con alto impacto en produccion.
Solo conexion AMI y sync incremental cada 5 minutos. El backend corre en infraestructura separada. Asterisk 13.38.3 no se modifica.
Arquitectura async de un solo proceso Python que combina cliente AMI, servidor HTTP/WS y sync de CDR.
Backend Python y frontend estatico bien separados. Un unico punto de entrada.
asterisk-panel/ backend/ main.py — Entry point, orquesta todos los componentes server.py — aiohttp HTTP + WebSocket server, API REST, admin auth ami_client.py — AMI TCP client, auth, event parsing, mapeo de estados cdr_db.py — Schema SQLite, queries de busqueda y estadisticas cdr_sync.py — Sync incremental MySQL -> SQLite cdr_export.py — Export a HTML, XLSX (openpyxl), PDF (fpdf2) config.py — Carga de config desde config.ini user_sync.py — Fetch periodico HTTP del directorio de usuarios user_editor.py — Parse/serialize usuarios.txt (CSV round-trip) queue_editor.py — Parse/serialize queues.conf (round-trip con comentarios) graph_client.py — Microsoft Graph API para avatares de usuario import_cdr.py — Import inicial one-time desde TSV filters.json — Filtros predefinidos (grupos de extensiones) frontend/ index.html — CDR search con filtros multi-usuario realtime.html — Panel BLF en tiempo real con vistas y filtros predefinidos stats.html — Dashboard de estadisticas con filtros predefinidos extensiones.html — Editor de extensiones (seccion admin) colas.html — Editor de colas de Asterisk (seccion admin) filtros.html — Editor de filtros predefinidos (seccion superadmin) callmap.html — Mapa de llamadas activas (modo dev) app.js — WebSocket client, grid extensiones, vistas y filtros cdr.js — CDR search, paginacion, detalle de llamada vinculada stats.js — Donut chart SVG, heatmap, desglose por usuario callmap.js — Visualizacion SVG de llamadas activas usuarios.js — Editor de extensiones con gestion de acceso colas.js — Editor de colas con drag & drop de miembros filtros.js — Editor de filtros predefinidos (CRUD) auth.js — Admin/superadmin auth check (App Proxy header) user-tags.js — Componente tag input multi-usuario compartido theme.js — Toggle de tema con persistencia localStorage style.css — Sistema de temas completo (claro/oscuro) scripts/ serve_users.py — Micro HTTP server en Asterisk (Python 3.5 compatible) deploy.sh — Deploy a produccion en un comando DEPLOY.md — Guia de despliegue completa
Los 6 estados posibles de cada extension, representados con color e icono:
| Funcionalidad | Detalle |
|---|---|
| WebSocket push | Cambios de estado enviados a todos los clientes conectados. Latencia <100 ms. |
| Cards de extension | Header con nombre de usuario, color dinamico por estado, timer de duracion actualizado cada segundo. |
| Busqueda | Filtro en tiempo real por numero de extension o nombre de usuario. |
| Filtros por estado | Badges coloreados toggleables. "No registrada" desmarcado por defecto para reducir ruido. |
| Sync de directorio | Fetch HTTP del directorio de usuarios cada 24 h (configurable). Resuelve conflicto de conexion AMI. |
| Filtro de extensiones | Solo se muestran extensiones de 4-5 digitos. Excluye trunks y rutas de marcado. |
| Capacidad | Detalle |
|---|---|
| Filtros de busqueda | Origen, destino, caller ID, estado (ANSWERED / NO ANSWER / BUSY / FAILED), contexto, rango fecha+hora. |
| Estadisticas agregadas | Total, contestadas, no contestadas, ocupado, fallidas, duracion media. Calculadas sobre el filtro activo. |
| Paginacion | 50 registros por pagina con navegacion. |
| Export | HTML autocontenido con estilos, XLSX (openpyxl con cabeceras, colores y filas alternas), PDF (fpdf2 A4 landscape). |
| Rendimiento | SQLite con indices en calldate, src, dst, disposition. ~19K CDR sincronizados en 1.25 s. |
| Import inicial | 7M+ registros importados desde TSV en aproximadamente 35 minutos. |
Toggle con persistencia en localStorage. Estetica coherente con el resto del ecosistema interno (spo-permissions-hub).
Grid de extensiones adaptable. Funciona en pantallas grandes de supervision y en portatil.
Navbar con logo y favicon. Paleta personalizada: navbar lima #e1f56e, accent magenta #b62682.
| Funcionalidad | Detalle |
|---|---|
| Donut chart SVG | Distribucion visual de llamadas por estado (contestada, no contestada, ocupado, fallida) con SVG puro. |
| Estadisticas por direccion | Metricas separadas para llamadas entrantes y salientes con totales y duraciones. |
| Mapa de calor | Grid CSS con horas 7-22, media de llamadas por dia y hora de la semana. |
| Desglose por usuario | Tabla de estadisticas individuales cuando se busca por multiples usuarios. |
| Filtros guardados | Presets de filtros almacenados en localStorage con chips de acceso rapido. |
| Exportacion | HTML standalone y XLSX con estilos. |
| Funcionalidad | Detalle |
|---|---|
| Vistas por usuario | Seleccion de usuarios para filtrar extensiones en el panel BLF. |
| Persistencia client-side | Vistas guardadas en localStorage, sin dependencia del servidor. |
| Tag input compartido | Componente de input con tags reutilizado en CDR, estadisticas y tiempo real. |
| Chips de seleccion | Barra de chips para cambiar rapidamente entre vistas. |
| Funcionalidad | Detalle |
|---|---|
| Editor web | Edicion directa del fichero usuarios.txt del servidor Asterisk desde el navegador. |
| Tabla con avatares | Cada extension muestra iniciales, numero prominente y nombre. |
| Modales de edicion | Alta, edicion y eliminacion de extensiones con validacion. |
| Preservacion CSV | Round-trip completo del fichero CSV, solo modifica columnas editables. |
| Micro HTTP server | serve_users.py en el servidor Asterisk (Python 3.5 compatible) para GET/POST. |
| Funcionalidad | Detalle |
|---|---|
| Azure AD App Proxy | Autenticacion via cabecera X-MS-CLIENT-PRINCIPAL-NAME inyectada por el proxy. |
Admin (allowed_upns) |
Acceso a Extensiones y Colas. Protegido con 403 para usuarios no autorizados. |
Superadmin (admins_upns) |
Acceso completo incluyendo Filtros predefinidos. Los superadmins tambien son admins. |
| Doble acceso | La app funciona sin App Proxy (sin secciones admin) y con App Proxy (admin/superadmin visible). |
| Configuracion | Seccion [admin] en config.ini con header, allowed_upns y admins_upns. |
| Endpoint /api/auth/me | Devuelve {admin, superadmin, upn} para mostrar/ocultar enlaces de navegacion. |
| Funcionalidad | Detalle |
|---|---|
| Editor web | Edicion directa de /etc/asterisk/queues.conf desde el navegador. CRUD completo de colas. |
| Propiedades editables | strategy, timeout, retry, maxlen, weight, wrapuptime, musiconhold, ringinuse, joinempty, leavewhenempty. |
| Miembros estaticos | Gestion de miembros con device (SIP/XXXX) y prioridad. Reordenacion con drag & drop (HTML5 API). |
| Round-trip | Preserva comentarios, lineas en blanco y formato original del fichero. Solo modifica secciones editadas. |
| Parser dedicado | queue_editor.py: parser linea a linea (no configparser) para soportar member => y comentarios ;. |
| Seccion [general] | Preservada intacta, no editable desde la interfaz web. |
| Reload automatico | Tras guardar, se ejecuta asterisk -rx "queue reload all" via hook en serve_users.py. |
| Funcionalidad | Detalle |
|---|---|
| Editor de filtros | Pagina de administracion (superadmin) para crear/editar/eliminar grupos de extensiones con nombre. |
| Almacenamiento | filters.json en el backend. API REST GET/POST /api/filters. |
| Integracion Real-Time | Chips seleccionables en la barra de vistas. Multi-seleccion con union de extensiones. |
| Integracion Estadisticas | Chips en la barra de filtros predefinidos. Se incluyen en la busqueda y en la exportacion. |
| Filtrado directo | Parametro extensions en la API para filtrar por extensiones directamente, sin resolucion de nombres. |
# Ejemplo de mapeo de estados AMI en ami_client.py DEVICE_STATE_MAP = { "NOT_INUSE": "available", "Idle": "available", # Asterisk 13 devuelve "Idle" en ExtensionStateList "INUSE": "in_call", "RINGING": "ringing", "ONHOLD": "on_hold", "UNAVAILABLE": "unregistered", "DND": "dnd", }
Despliegue automatizado en un solo comando, con nginx reverse proxy y SSL wildcard.
Restart=alwaysjournalctlsystemctl enable asterisk-panelgit pull desde rama masterpip install -r requirements.txtsystemctl restart asterisk-panelconfig.ini en .gitignore# config.ini.example — credenciales nunca en el repositorio [asterisk] host = 10.x.x.x ami_port = 5038 ami_user = panel_user ami_pass = CHANGE_ME [mysql] host = 10.x.x.x db = asteriskcdrdb user = cdr_reader password = CHANGE_ME [server] host = 0.0.0.0 port = 8081 sync_interval_minutes = 5 [admin] header = X-MS-CLIENT-PRINCIPAL-NAME allowed_upns = [email protected]
Cada obstaculo encontrado, diagnosticado y resuelto con una solucion definitiva.
ExtensionStateList devuelve "Idle" en lugar de "NOT_INUSE" en Asterisk 13.
Solucion: se anadio un mapping extra en DEVICE_STATE_MAP para que ambos valores se traduzcan al estado "available".
Los puertos 5038 (AMI) y 3306 (MySQL) estaban bloqueados desde el entorno de desarrollo por el firewall perimetral. Solucion: desplegar el backend en la misma red que Asterisk. Solo se expone el puerto 8081 hacia exterior via nginx.
UserSync y listen_events compartian la misma conexion AMI, causando "readuntil() called while another coroutine is already waiting".
Solucion: el sync de usuarios se realiza via HTTP a un micro-servidor externo en lugar de usar el canal AMI.
El comando !cat usuarios.txt solo funciona en la CLI interactiva de Asterisk, no via AMI.
Solucion: se levanta un micro HTTP server en el host Asterisk que sirve usuarios.txt directamente por HTTP.
CDRStats consultaba directamente la base de datos MySQL de Asterisk, generando carga en produccion y tiempos de respuesta lentos.
Solucion: SQLite local con indices optimizados en calldate, src, dst, disposition y sync incremental cada 5 minutos.
El certificado wildcard *.sys.dominio no cubre subdominios de mas de un nivel como panel.xxx.sys.dominio.
Solucion: cambiar el DNS para que el servicio quede en un unico nivel dentro del dominio cubierto.
El fichero usuarios.txt tiene 11+ columnas pero solo se editan 3.
Solucion: almacenar la fila CSV completa como _raw y solo parchear las columnas editables al guardar.
nginx filtraba la cabecera X-MS-CLIENT-PRINCIPAL-NAME.
Solucion: proxy_set_header explicito en la configuracion de nginx.
El link de Extensiones seguia visible porque no existia una regla generica .hidden.
Solucion: anadir regla .hidden { display: none !important }.
| Capa | Tecnologia | Uso |
|---|---|---|
| Backend runtime | Python 3.11+ / asyncio |
Concurrencia sin threads: AMI client + HTTP server + CDR sync en el mismo proceso. |
| HTTP + WebSocket | aiohttp |
Servidor web async, API REST y WebSocket server para push de eventos. |
| Conexion MySQL | pymysql |
Lectura incremental de CDR desde la base de datos de Asterisk. |
| Base de datos local | SQLite (WAL mode) |
Almacenamiento de CDR con WAL para lecturas concurrentes sin bloqueo. |
| Export Excel | openpyxl |
XLSX con cabeceras coloreadas, filas alternas y autoajuste de columnas. |
| Export PDF | fpdf2 |
PDF A4 landscape con tabla de CDR. |
| Frontend | Vanilla JS + CSS | Sin dependencias de framework. WebSocket nativo del navegador. |
| Proxy / TLS | nginx |
Reverse proxy con SSL termination. Unico puerto publico: 443. |
| Init system | systemd |
Gestion del proceso con auto-restart y logs centralizados. |
| Microsoft Graph API | graph_client.py |
Avatares de usuario en panel de tiempo real. |
| Azure AD App Proxy | Cabeceras SSO | Autenticacion admin via cabeceras SSO inyectadas por el proxy. |