From 4235ba55c650156e17f694674fd964623ee22cc5 Mon Sep 17 00:00:00 2001 From: Michael Tremer Date: Wed, 28 Jun 2023 14:51:30 +0000 Subject: [PATCH] voip: Create an extra page for debugging VoIP stuff Signed-off-by: Michael Tremer --- Makefile.am | 11 +++ src/backend/accounts.py | 13 ++- src/backend/asterisk.py | 88 +++++++++++++++++++ src/templates/base.html | 16 +++- src/templates/users/show.html | 2 - src/templates/voip/index.html | 39 ++++++++ src/templates/voip/modules/registrations.html | 48 ++++++++++ src/web/__init__.py | 7 ++ src/web/voip.py | 22 +++++ 9 files changed, 235 insertions(+), 11 deletions(-) create mode 100644 src/templates/voip/index.html create mode 100644 src/templates/voip/modules/registrations.html create mode 100644 src/web/voip.py diff --git a/Makefile.am b/Makefile.am index b1749141..9fab4394 100644 --- a/Makefile.am +++ b/Makefile.am @@ -98,6 +98,7 @@ web_PYTHON = \ src/web/people.py \ src/web/ui_modules.py \ src/web/users.py \ + src/web/voip.py \ src/web/wiki.py webdir = $(backenddir)/web @@ -328,6 +329,16 @@ templates_users_modules_DATA = \ templates_users_modulesdir = $(templates_usersdir)/modules +templates_voip_DATA = \ + src/templates/voip/index.html + +templates_voipdir = $(templatesdir)/voip + +templates_voip_modules_DATA = \ + src/templates/voip/modules/registrations.html + +templates_voip_modulesdir = $(templates_voipdir)/modules + templates_wiki_DATA = \ src/templates/wiki/404.html \ src/templates/wiki/confirm-delete.html \ diff --git a/src/backend/accounts.py b/src/backend/accounts.py index 3c248f2b..ade04f7d 100644 --- a/src/backend/accounts.py +++ b/src/backend/accounts.py @@ -1136,16 +1136,15 @@ class Account(LDAPObject): sip_routing_address = property(get_sip_routing_address, set_sip_routing_address) - @lazy_property - def sip_registrations(self): - sip_registrations = [] + # SIP Registrations - for reg in self.backend.talk.freeswitch.get_sip_registrations(self.sip_url): - reg.account = self + async def get_sip_registrations(self): + if not self.has_sip(): + return [] - sip_registrations.append(reg) + return await self.backend.asterisk.get_registrations(self.sip_id) - return sip_registrations + # SIP Channels async def get_sip_channels(self): if not self.has_sip(): diff --git a/src/backend/asterisk.py b/src/backend/asterisk.py index 8a478bfb..f5f91e06 100644 --- a/src/backend/asterisk.py +++ b/src/backend/asterisk.py @@ -4,7 +4,9 @@ import asyncio import datetime import logging import panoramisk +import urllib.parse +from . import accounts from . import misc from .decorators import * @@ -75,6 +77,25 @@ class Asterisk(misc.Object): return channels + async def get_registrations(self, filter=None): + registrations = [] + + for data in await self.manager.send_action({"Action" : "PJSIPShowContacts"}): + # Skip header and trailer + if data.eventlist: + continue + + # Parse registration + registration = Registration(self.backend, data) + + # Apply filter + if filter and not registration.matches(filter): + continue + + registrations.append(registration) + + return registrations + class Channel(misc.Object): def init(self, data): @@ -112,3 +133,70 @@ class Channel(misc.Object): def is_ringing(self): return self.data.ChannelStateDesc == "Ringing" + + +class Registration(misc.Object): + def init(self, data): + self.data = data + + def __lt__(self, other): + if isinstance(other, self.__class__): + if isinstance(self.user, accounts.Account): + if isinstance(other.user, accounts.Account): + return self.user < other.user + else: + return self.user.name < other.user + else: + if isinstance(other.user, accounts.Account): + return self.user < other.user.name + else: + return self.user < other.user + + return NotImplemented + + def __str__(self): + return self.user_agent + + def matches(self, filter): + return self.data.Endpoint == filter + + @lazy_property + def uri(self): + return urllib.parse.urlparse(self.data.Uri) + + @lazy_property + def uri_params(self): + params = {} + + for param in self.uri.params.split(";"): + key, _, value = param.partition("=") + + params[key] = value + + return params + + @property + def transport(self): + return self.uri_params.get("transport") + + @lazy_property + def user(self): + return self.backend.accounts.get_by_sip_id(self.data.Endpoint) or self.data.Endpoint + + @property + def address(self): + # Remove the user + user, _, address = self.uri.path.partition("@") + + # Remove the port + address, _, port = address.rpartition(":") + + return address + + @property + def user_agent(self): + return self.data.UserAgent.replace("_", " ") + + @property + def roundtrip(self): + return int(self.data.RoundtripUsec) / 1000 diff --git a/src/templates/base.html b/src/templates/base.html index 5e38cdcf..381d9703 100644 --- a/src/templates/base.html +++ b/src/templates/base.html @@ -217,7 +217,7 @@ {% end %} -
+ -
+ + {% if current_user and current_user.is_staff() %} + + {% end %} +
{% if sip_channels %} - {% elif account.sip_registrations %} - {% elif account.uses_sip_forwarding() %} {% else %} diff --git a/src/templates/voip/index.html b/src/templates/voip/index.html new file mode 100644 index 00000000..e607ed4a --- /dev/null +++ b/src/templates/voip/index.html @@ -0,0 +1,39 @@ +{% extends "../base.html" %} + +{% block title %}{{ _("VoIP") }}{% end block %} + +{% block container %} +
+ +
+ + {% if registrations %} +
+
+

+ {{ _("Registrations") }} + {{ len(registrations) }} +

+ + {% module VoIPRegistrations(registrations) %} +
+
+ {% end %} +{% end block %} diff --git a/src/templates/voip/modules/registrations.html b/src/templates/voip/modules/registrations.html new file mode 100644 index 00000000..0edfac9e --- /dev/null +++ b/src/templates/voip/modules/registrations.html @@ -0,0 +1,48 @@ +{% from ipfire.accounts import Account %} + + + + + + + + + + + + + + {% for r in sorted(registrations) %} + + {# User #} + + + {# Transport #} + + + {# Address #} + + + {# User Agent #} + + + {# Latency #} + + {% end %} + +
{{ _("User") }}{{ _("Transport") }}{{ _("Address") }}{{ _("User Agent") }}{{ _("Latency") }}
+ {% if isinstance(r.user, Account) %} + {{ r.user }} + {% else %} + {{ r.user }} + {% end %} + + {{ r.transport }} + + {{ r.address }} + + {{ r.user_agent or _("N/A") }} + + {{ _("%.2fms") % r.roundtrip }} + +
diff --git a/src/web/__init__.py b/src/web/__init__.py index 72e22ab2..27d05035 100644 --- a/src/web/__init__.py +++ b/src/web/__init__.py @@ -27,6 +27,7 @@ from . import nopaste from . import people from . import ui_modules from . import users +from . import voip from . import wiki class Application(tornado.web.Application): @@ -92,6 +93,9 @@ class Application(tornado.web.Application): # Users "UsersList" : users.ListModule, + # VoIP + "VoIPRegistrations" : voip.RegistrationsModule, + # Wiki "WikiDiff" : wiki.WikiDiffModule, "WikiList" : wiki.WikiListModule, @@ -172,6 +176,9 @@ class Application(tornado.web.Application): (r"/users/([a-z_][a-z0-9_-]{0,31})/edit", users.EditHandler), (r"/users/([a-z_][a-z0-9_-]{0,31})/passwd", users.PasswdHandler), + # VoIP + (r"/voip", voip.IndexHandler), + # Static Pages (r"/about", StaticHandler, { "template" : "about.html" }), (r"/legal", StaticHandler, { "template" : "legal.html" }), diff --git a/src/web/voip.py b/src/web/voip.py new file mode 100644 index 00000000..be07f646 --- /dev/null +++ b/src/web/voip.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 + +import asyncio +import tornado.web + +from . import base +from . import ui_modules + +class IndexHandler(base.BaseHandler): + @tornado.web.authenticated + async def get(self): + registrations, = await asyncio.gather( + self.backend.asterisk.get_registrations(), + ) + + self.render("voip/index.html", registrations=registrations) + + +class RegistrationsModule(ui_modules.UIModule): + def render(self, registrations): + return self.render_string("voip/modules/registrations.html", + registrations=registrations) -- 2.47.3