</section>
{% if current_user.is_admin() %}
+ {% module TalkOngoingCalls() %}
+
<section id="lines">
{% module TalkLines(show_account=True) %}
</section>
</div>
{% end %}
- {% if current_user and current_user.is_admin() %}
- {% module TalkOngoingCalls() %}
- {% else %}
- {% module TalkOngoingCalls(current_user) %}
- {% end %}
+ {% module TalkOngoingCalls(current_user) %}
{% module TalkCallLog(current_user) %}
{% end block %}
--- /dev/null
+{% if account %}
+ <i class="fa fa-user"></i>
+ <a href="/phonebook/{{ account.uid }}">{{ account.name }}</a>
+ <span class="text-muted">({{ number }})</span>
+
+{% elif number == "900" %}
+ {{ _("Conference Service") }}
+
+{% elif number in ("901", "902", "903", "904", "905", "906", "907", "908", "909") %}
+ {{ _("Conference Room #%s") % number[2] }}
+
+{% elif number in ("980", "981", "982", "983", "984", "985", "986", "987", "988", "989") %}
+ {{ _("Parked Calls Extension") }} <span class="text-muted">({{ number }})</span>
+
+{% elif number == "990" %}
+ {{ _("Voicemail") }}
+
+{% elif number == "991" %}
+ {{ _("Echo Test") }}
+
+{% elif number == "992" %}
+ {{ _("Music") }}
+
+{% elif name and number %}
+ {{ name }} <span class="text-muted">({{ number }})</span>
+
+{% elif number %}
+ {{ number }}
+
+{% else %}
+ <span class="text-muted">{{ _("Unknown") }}</span>
+{% end %}
-<h3>{{ _("Ongoing Calls") }}</h3>
+{% if channels %}
+ <h3>{{ _("Ongoing Calls") }}</h3>
-<table class="table table-hover table-striped">
- <thead>
- <tr>
- <th>{{ _("Time Started") }}</th>
- <th>{{ _("Caller") }}</th>
- <th></th>
- <th>{{ _("Called") }}</th>
- </tr>
- </thead>
- <tbody>
- {% for call in calls %}
+ <table class="table table-hover table-striped">
+ <thead>
<tr>
- <td>{{ locale.format_date(call.time) }}</td>
- <td>
- {% if call.caller_account %}
- <a href="/phonebook/{{ call.caller_account.uid }}">{{ call.caller_account.name }}</a>
- <span class="text-muted">({{ call.caller }})</span>
- {% else %}
- {{ call.caller }}
- {% end %}
- </td>
- <td>
- <span class="glyphicon glyphicon-arrow-right text-success"></span>
- </td>
- <td>
- {% if call.called_account %}
- <a href="/phonebook/{{ call.called_account.uid }}">{{ call.called_account.name }}</a>
- <span class="text-muted">({{ call.called }})</span>
- {% elif call.called_conference %}
- <a href="/conferences#{{ call.called_conference.no }}">{{ call.called_conference.name }}</a>
- <span class="text-muted">({{ call.called }})</span>
- {% else %}
- {{ call.called }}
- {% end %}
- </td>
+ <th>{{ _("Caller") }}</th>
+ <th></th>
+ <th>{{ _("Called") }}</th>
+ <th>{{ _("Codec") }}</th>
+ <th class="ar">{{ _("Duration") }}</th>
</tr>
- {% end %}
- </tbody>
-</table>
+ </thead>
+ <tbody>
+ {% for c in channels %}
+ <tr>
+ <td>
+ {% module TalkContact(c.caller, name=c.caller_name) %}
+ </td>
+
+ <td>
+ <span class="glyphicon glyphicon-arrow-right text-success"></span>
+ </td>
+
+ <td>
+ {% module TalkContact(c.callee) %}
+ </td>
+
+ <td>
+ {{ c.format }}
+ </td>
+
+ <td class="ar">{{ format_time(c.duration) }}</td>
+ </tr>
+ {% end %}
+ </tbody>
+ </table>
+{% end %}
"FireinfoDeviceTable" : FireinfoDeviceTableModule,
"FireinfoDeviceAndGroupsTable" : FireinfoDeviceAndGroupsTableModule,
"FireinfoGeoTable" : FireinfoGeoTableModule,
+ "TalkContact" : TalkContactModule,
"TalkCallLog" : TalkCallLogModule,
"TalkLines" : TalkLinesModule,
"TalkOngoingCalls" : TalkOngoingCallsModule,
--- /dev/null
+#!/usr/bin/python
+
+import logging
+import socket
+import time
+
+log = logging.getLogger("asterisk")
+
+class AsteriskManager(object):
+ def __init__(self, address, port=5038, username=None, password=None, timeout=10):
+ self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.socket.settimeout(timeout)
+ self.socket.connect((address, port))
+
+ self.conn = self.socket.makefile("rw", 0)
+
+ self._authenticate(username, password)
+
+ def _authenticate(self, username, password):
+ banner = self.conn.readline()
+
+ if not banner.startswith("Asterisk Call Manager"):
+ raise RuntimeError("Did not connect to an Asterisk here")
+
+ self._send_action("Login", {
+ "Username" : username,
+ "Secret" : password,
+ "Events" : "off",
+ })
+
+ def _make_action_id(self):
+ return "%s" % time.time()
+
+ def _send_action(self, action, parameters={}):
+ self._send("Action", action)
+
+ action_id = self._make_action_id()
+ self._send("ActionID", action_id)
+
+ for k, v in parameters.items():
+ self._send(k, v)
+
+ return self._submit()
+
+ def _send(self, key, value):
+ line = "%s: %s" % (key, value)
+ log.debug("S: %s" % line)
+
+ self.conn.write("%s\r\n" % line)
+
+ def _recv(self):
+ line = self.conn.readline()
+ line = line.rstrip()
+
+ log.debug("R: %s" % line)
+
+ return line
+
+ def _recv_response(self):
+ response = {}
+
+ while True:
+ # Read one line
+ line = self._recv()
+
+ # An empty line signals end of response
+ if not line:
+ break
+
+ k, sep, v = line.partition(": ")
+ response[k] = v
+
+ return response
+
+ def _submit(self):
+ # End command
+ self.conn.write("\r\n")
+
+ # Read response
+ res = self._recv_response()
+
+ if res["Response"] == "Error":
+ raise Exception(res["Message"])
+
+ if res.get("EventList") == "start":
+ events = []
+
+ while True:
+ event = self._recv_response()
+
+ # This is the end of the list
+ if event.get("EventList") == "Complete":
+ break
+
+ events.append(event)
+
+ # Return event list
+ return events
+
+ return res
+
+ def ping(self):
+ """
+ Sends a ping to asterisk and expects pong
+ """
+ res = self._send_action("Ping")
+
+ return res["Ping"] == "Pong"
+
+ def call(self, caller, callee, caller_id=None, timeout=30000):
+ res = self._send_action("Originate", {
+ "Channel" : caller,
+ "Exten" : callee,
+ "Context" : "from-cli",
+ "Priority" : 1,
+ "Timeout" : timeout,
+ "CallerID" : caller_id or callee,
+ })
+
+ return res
+
+ def list_channels(self):
+ channels = []
+
+ for c in self._send_action("CoreShowChannels"):
+ channel = Channel(self, c.get("Channel"))
+ channels.append(channel)
+
+ return sorted(channels)
+
+ def _mailbox_status(self, mailbox):
+ return self._send_action("MailboxStatus", { "Mailbox" : "%s@default" % mailbox })
+
+ def messages_waiting(self, mailbox):
+ status = self._mailbox_status(mailbox)
+
+ # Get messages waiting
+ waiting = status.get("Waiting", 0)
+
+ return int(waiting)
+
+ def list_peers(self):
+ peers = []
+
+ for p in self._send_action("SIPPeers"):
+ peer = Peer(self, p.get("ObjectName"))
+ peers.append(peer)
+
+ return sorted(peers)
+
+ def list_registry(self):
+ print self._send_action("SIPShowRegistry")
+
+
+class Channel(object):
+ def __init__(self, manager, channel_id):
+ self.manager = manager
+ self.id = channel_id
+
+ self.status = self.manager._send_action("Status", { "Channel" : self.id })[0]
+
+ def __eq__(self, other):
+ return self.id == other.id
+
+ def __lt__(self, other):
+ # Longest first
+ return not self.duration < other.duration
+
+ def __repr__(self):
+ return "<%s %s>" % (self.__class__.__name__, self.id)
+
+ def hangup(self):
+ res = self.manager._send_action("Hangup", { "Channel" : self.id })
+
+ return res["Response"] == "Success"
+
+ @property
+ def type(self):
+ return self.status.get("Type")
+
+ @property
+ def application(self):
+ return self.status.get("Application")
+
+ @property
+ def duration(self):
+ seconds = self.status.get("Seconds", None)
+
+ if seconds is None:
+ return
+
+ return int(seconds)
+
+ @property
+ def format(self):
+ return "%s/%s" % (
+ self.status.get("Readformat"),
+ self.status.get("Writeformat"),
+ )
+
+ @staticmethod
+ def _format_number(number):
+ # Replace 00 by +
+ if number and number.startswith("00"):
+ return "+%s" % number[2:]
+
+ return number
+
+ @property
+ def caller(self):
+ num = self.status.get("CallerIDNum")
+
+ return self._format_number(num)
+
+ @property
+ def caller_name(self):
+ name = self.status.get("CallerIDName")
+
+ if name in ("", "<unknown>"):
+ return None
+
+ return name
+
+ @property
+ def callee(self):
+ num = self.status.get("DNID")
+
+ return self._format_number(num)
+
+
+class Peer(object):
+ def __init__(self, manager, peer_id):
+ self.manager = manager
+ self.id = peer_id
+
+ self.data = self.manager._send_action("SIPShowPeer", { "Peer" : self.id })
+
+ def __repr__(self):
+ return "<%s %s>" % (self.__class__.__name__, self.id)
+
+ def __eq__(self, other):
+ return self.id == other.id
+
+ def __lt__(self, other):
+ return self.id < other.id
import re
-import database
+from . import asterisk
+from . import database
from misc import Object
def db(self):
return self._db
+ def connect_to_asterisk(self):
+ hostname = self.settings.get("asterisk_hostname")
+ username = self.settings.get("asterisk_username")
+ password = self.settings.get("asterisk_password")
+
+ return asterisk.AsteriskManager(
+ hostname, username=username, password=password
+ )
+
def get_phonebook(self, account=None):
accounts = []
for a in self.accounts.list():
return self._process_cdr(res)
+ def get_channels(self, account=None):
+ a = self.connect_to_asterisk()
+
+ channels = []
+ for c in a.list_channels():
+ if account and not account.sip_id in (c.caller, c.callee):
+ continue
+
+ channels.append(c)
+
+ return sorted(channels)
+
def get_ongoing_calls(self, account=None, sip_id=None):
if account and sip_id is None:
sip_id = account.sip_id
return "%.0f%s" % (s, units[i])
-def format_time(s):
+def format_time(s, shorter=True):
#_ = handler.locale.translate
_ = lambda x: x
call_log = self.talk.get_call_log(self.current_user, limit=6)
favourite_contacts = self.talk.get_favourite_contacts(self.current_user)
- if self.current_user.is_admin():
- ongoing_calls = self.talk.get_ongoing_calls()
- else:
- ongoing_calls = self.talk.get_ongoing_calls(self.current_user)
-
self.render("talk/index.html", call_log=call_log,
- favourite_contacts=favourite_contacts, ongoing_calls=ongoing_calls)
+ favourite_contacts=favourite_contacts)
class TalkPhonebookHandler(BaseHandler):
colour=colour, value=value)
+class TalkContactModule(UIModule):
+ def render(self, number, name=None):
+ account = self.accounts.get_by_sip_id(number)
+
+ return self.render_string("talk/modules/contact.html",
+ account=account, number=number, name=name)
+
+
class TalkCallLogModule(UIModule):
def render(self, account=None, viewer=None):
if (account is None or not self.current_user == account) \
class TalkOngoingCallsModule(UIModule):
- def render(self, account=None):
+ def render(self, account=None, debug=False):
if (account is None or not self.current_user == account) \
and not self.current_user.is_admin():
raise RuntimeException("Insufficient permissions")
- calls = self.talk.get_ongoing_calls(account)
-
- if calls:
- return self.render_string("talk/modules/ongoing-calls.html",
- calls=calls)
+ channels = self.talk.get_channels()
- return ""
+ return self.render_string("talk/modules/ongoing-calls.html",
+ account=account, channels=channels, debug=debug)
class TrackerPeerListModule(UIModule):