]> git.ipfire.org Git - ipfire.org.git/commitdiff
voip: Create an extra page for debugging VoIP stuff
authorMichael Tremer <michael.tremer@ipfire.org>
Wed, 28 Jun 2023 14:51:30 +0000 (14:51 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Wed, 28 Jun 2023 14:51:30 +0000 (14:51 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
Makefile.am
src/backend/accounts.py
src/backend/asterisk.py
src/templates/base.html
src/templates/users/show.html
src/templates/voip/index.html [new file with mode: 0644]
src/templates/voip/modules/registrations.html [new file with mode: 0644]
src/web/__init__.py
src/web/voip.py [new file with mode: 0644]

index b1749141918b04197b8129b562b36b6a2d4bdb10..9fab4394add3fc5d427fb137dc4b370c81219632 100644 (file)
@@ -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 \
index 3c248f2b952b86b541487002971b7295bfa52e07..ade04f7dd350b6930930e84070153cd1cb524c4a 100644 (file)
@@ -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():
index 8a478bfb3ce21e1081be6e6ac1df13903b629e56..f5f91e06534c3f60b060954ed7b70821862996bb 100644 (file)
@@ -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
index 5e38cdcf7a9c8024a17a52f4b4b8ccd5995fbf0d..381d9703aabd5c82ce6fb228d4742da502c18a20 100644 (file)
                                                        {% end %}
                                                </div>
 
-                                               <div class="column is-one-fifth">
+                                               <div class="column">
                                                        <ul>
                                                                <li>
                                                                        <a href="/about">
                                                        </ul>
                                                </div>
 
-                                               <div class="column is-one-fifth">
+                                               <div class="column">
                                                        <ul>
                                                                <li>
                                                                        <a href="/download">
                                                        </ul>
                                                </div>
 
+                                               {% if current_user and current_user.is_staff() %}
+                                                       <div class="column">
+                                                               <ul>
+                                                                       <li>
+                                                                               <a href="/voip">
+                                                                                       {{ _("VoIP") }}
+                                                                               </a>
+                                                                       </li>
+                                                               </ul>
+                                                       </div>
+                                               {% end %}
+
                                                <div class="column is-one-fifth">
                                                        <div class="block">
                                                                <a class="button is-primary is-fullwidth is-medium has-text-weight-bold is-uppercase"
index 40a5ae65fe586004ec2543b43ee0ab189f8b7450..1a9496072e9298a85fdac6a1aa97c3cfa47f7b5e 100644 (file)
                                                                        <li>
                                                                                {% if sip_channels %}
                                                                                        <i class="fas fa-phone-volume has-text-warning fa-fw"></i>
-                                                                               {% elif account.sip_registrations %}
-                                                                                       <i class="fas fa-phone-square has-text-success fa-fw"></i>
                                                                                {% elif account.uses_sip_forwarding() %}
                                                                                        <i class="fas fa-phone-square has-text-warning fa-fw"></i>
                                                                                {% else %}
diff --git a/src/templates/voip/index.html b/src/templates/voip/index.html
new file mode 100644 (file)
index 0000000..e607ed4
--- /dev/null
@@ -0,0 +1,39 @@
+{% extends "../base.html" %}
+
+{% block title %}{{ _("VoIP") }}{% end block %}
+
+{% block container %}
+       <section class="hero is-dark">
+               <div class="hero-body">
+                       <div class="container">
+                               <nav class="breadcrumb is-medium" aria-label="breadcrumbs">
+                                       <ul>
+                                               <li>
+                                                       <a href="/">
+                                                               {{ _("Home") }}
+                                                       </a>
+                                               </li>
+                                               <li class="is-active">
+                                                       <a href="#" aria-current="page">{{ _("VoIP") }}</a>
+                                               </li>
+                                       </ul>
+                               </nav>
+
+                               <h1 class="title is-1">{{ _("VoIP") }}</h1>
+                       </div>
+               </div>
+       </section>
+
+       {% if registrations %}
+               <section class="section">
+                       <div class="container">
+                               <h4 class="title is-4">
+                                       {{ _("Registrations") }}
+                                       <span class="tag">{{ len(registrations) }}</span>
+                               </h4>
+
+                               {% module VoIPRegistrations(registrations) %}
+                       </div>
+               </section>
+       {% end %}
+{% end block %}
diff --git a/src/templates/voip/modules/registrations.html b/src/templates/voip/modules/registrations.html
new file mode 100644 (file)
index 0000000..0edfac9
--- /dev/null
@@ -0,0 +1,48 @@
+{% from ipfire.accounts import Account %}
+
+<table class="table is-striped is-fullwidth">
+       <thead>
+               <tr>
+                       <th>{{ _("User") }}</th>
+                       <th>{{ _("Transport") }}</th>
+                       <th>{{ _("Address") }}</th>
+                       <th>{{ _("User Agent") }}</th>
+                       <th>{{ _("Latency") }}</th>
+               </tr>
+       </thead>
+
+       <tbody>
+               {% for r in sorted(registrations) %}
+                       <tr>
+                               {# User #}
+                               <th scope="row">
+                                       {% if isinstance(r.user, Account) %}
+                                               <a href="/users/{{ r.user.uid }}">{{ r.user }}</a>
+                                       {% else %}
+                                               {{ r.user }}
+                                       {% end %}
+                               </th>
+
+                               {# Transport #}
+                               <td class="has-text-centered">
+                                       {{ r.transport }}
+                               </td>
+
+                               {# Address #}
+                               <td>
+                                       {{ r.address }}
+                               </td>
+
+                               {# User Agent #}
+                               <td>
+                                       {{ r.user_agent or _("N/A") }}
+                               </td>
+
+                               {# Latency #}
+                               <td class="has-text-right">
+                                       {{ _("%.2fms") % r.roundtrip }}
+                               </th>
+                       </tr>
+               {% end %}
+       </tbody>
+</table>
index 72e22ab288c18c196f220b6965514f1459930da7..27d05035cf4e963e772dfcda8b06373ba3f938f9 100644 (file)
@@ -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 (file)
index 0000000..be07f64
--- /dev/null
@@ -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)