<a href="/conferences">{{ _("Conferences") }}</a>
</li>
- {% if current_user and current_user.is_admin() %}
- <li>
- <a href="/trunks">{{ _("Trunks") }}</a>
- </li>
- {% end %}
+ <li>
+ <a href="http://wiki.ipfire.org/en/community/talk.ipfire.org/start">
+ {{ _("Documentation") }}
+ </a>
+ </li>
<li>
<a href="/diagnosis">{{ _("Diagnosis") }}</a>
{% block body %}
<div class="page-header">
- <h3>{{ _("Diagnosis") }}</h3>
+ <h2>{{ _("Diagnosis") }}</h2>
</div>
<section id="test-call">
- <h4>{{ _("Test Call") }}</h4>
+ <h3>{{ _("Test Call") }}</h3>
<div class="well">
<span class="glyphicon glyphicon-earphone text-success"></span>
- {{ _("A music playing service") }}
</div>
</section>
+
+ {% if current_user.is_admin() %}
+ <section id="lines">
+ {% module TalkLines(show_account=True) %}
+ </section>
+ {% end %}
{% end block %}
</div>
{% end %}
- {% if ongoing_calls %}
- <h4>{{ _("Ongoing Calls") }}</h4>
- {% module TalkOngoingCalls(ongoing_calls) %}
- {% end %}
+ {% module TalkOngoingCalls(current_user) %}
- {% if call_log %}
- <h4>{{ _("Call Log") }}</h4>
- {% module TalkCallLog(call_log) %}
- {% end %}
+ {% module TalkCallLog(current_user) %}
{% end block %}
+<h3>{{ _("Call Log") }}</h3>
+
<table class="table table-condensed table-hover table-striped">
<thead>
<tr>
</thead>
<tbody>
{% for entry in calls %}
- {% if entry.status == "ringing" and entry.reason == "hangup" %}
- <tr class="info">
- {% elif entry.status == "ringing" and entry.reason == "Request Terminated" %}
- <tr class="danger">
- {% else %}
+ {% if entry.sip_code == "200" %}
<tr>
+ {% elif entry.caller == viewer.sip_id %}
+ <tr class="warning">
+ {% else %}
+ <tr class="info">
{% end %}
<td>{{ locale.format_date(entry.time, full_format=True, relative=False) }}</td>
<td>
- {% if entry.direction == "incoming" %}
+ {% if entry.called == viewer.sip_id %}
<span class="glyphicon glyphicon-arrow-left text-success" title="{{ _("incoming") }}"></span>
{% if entry.caller_account %}
<a href="/phonebook/{{ entry.caller_account.uid }}">{{ entry.caller_account.name }}</a>
{% else %}
{{ entry.caller }}
{% end %}
- {% elif entry.direction == "outgoing" %}
+ {% elif entry.caller == viewer.sip_id %}
<span class="glyphicon glyphicon-arrow-right text-info" title="{{ _("outgoing") }}"></span>
{% if entry.called_account %}
<a href="/phonebook/{{ entry.called_account.uid }}">{{ entry.called_account.name }}</a>
{% end %}
</td>
<td>
- {% if entry.status == "ringing" %}
- {% if entry.reason == "hangup" %}
- {{ _("no answer") }}
- {% elif entry.reason == "Request Terminated" %}
- {{ _("missed call") }}
- {% end %}
+ {% if entry.sip_code == "200" %}
+ {{ entry.duration }}
{% else %}
- {{ entry.duration.strftime("%H:%M:%S") }}
+ {% if entry.reason == "Busy Here" %}
+ {{ _("Busy") }}
+ {% elif entry.reason in ("Cancelled", "Request Cancelled", "Request Timeout") %}
+ {% if entry.caller == viewer.sip_id %}
+ {{ _("no answer") }}
+ {% else %}
+ {{ _("missed call") }}
+ {% end %}
+ {% else %}
+ {{ entry.sip_code }} {{ entry.reason }}
+ {% end %}
{% end %}
</td>
</tr>
--- /dev/null
+<h3>{{ _("Lines") }}</h3>
+
+<table class="table table-condensed table-hover table-striped">
+ <thead>
+ <tr>
+ {% if show_account %}
+ <th>{{ _("Account") }}</th>
+ {% end %}
+ <th>{{ _("Expires") }}</th>
+ <th>{{ _("Location") }}</th>
+ <th>{{ _("User Agent") }}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for line in lines %}
+ <tr>
+ {% if show_account %}
+ <td>
+ {% if line.account %}
+ <a href="/phonebook/{{ line.account.uid }}">{{ line.account.name }}</a>
+ <span class="text-muted">({{ line.account.sip_id }})</span>
+ </a>
+ {% else %}
+ {{ line.username }}@{{ line.domain }}
+ {% end %}
+ </td>
+ {% end %}
+
+ <td>{{ locale.format_date(line.expires) }}</td>
+ <td>
+ {% if line.tls_enabled %}
+ <span class="glyphicon glyphicon-lock" title="{{ _("TLS") }}"></span>
+ {% end %}
+ {{ line.location }}
+ </td>
+ <td>{{ line.user_agent }}</td>
+ </tr>
+ {% end %}
+ </tbody>
+</table>
+<h3>{{ _("Ongoing Calls") }}</h3>
+
<table class="table table-hover table-striped">
<thead>
<tr>
</div>
{% end %}
- {% if lines %}
- <h3>{{ _("Lines") }}</h3>
-
- <table class="table table-condensed table-hover table-striped">
- <thead>
- <tr>
- <th>{{ _("Expires") }}</th>
- <th>{{ _("Location") }}</th>
- <th>{{ _("User Agent") }}</th>
- </tr>
- </thead>
- <tbody>
- {% for line in lines %}
- <tr>
- <td>{{ locale.format_date(line.expires) }}</td>
- <td>
- {% if line.tls_enabled %}
- <span class="glyphicon glyphicon-lock" title="{{ _("TLS") }}"></span>
- {% end %}
- {{ line.location }}
- </td>
- <td>{{ line.user_agent }}</td>
- </tr>
- {% end %}
- </tbody>
- </table>
- {% end %}
-
- {% if ongoing_calls %}
- <h3>{{ _("Ongoing Calls") }}</h3>
- {% module TalkOngoingCalls(ongoing_calls) %}
- {% end %}
-
- {% if call_log %}
- <h3>{{ _("Call Log") }}</h3>
- {% module TalkCallLog(call_log) %}
+ {% if current_user == account or current_user.is_admin() %}
+ {% module TalkLines(account) %}
+ {% module TalkOngoingCalls(account) %}
+ {% module TalkCallLog(account) %}
{% end %}
{% end block %}
+++ /dev/null
-{% extends "../base.html" %}
-
-{% block title %}{{ _("Trunks") }}{% end block %}
-
-{% block body %}
- <h3>{{ _("Trunks") }}</h3>
- <table class="table table-hover table-striped">
- <thead>
- <tr>
- <th></th>
- <th>{{ _("Name") }}</th>
- <th>{{ _("Registrar") }}</th>
- </tr>
- </thead>
- <tbody>
- {% for trunk in trunks %}
- <tr>
- <td>
- {% if trunk.status == "online" %}
- <span class="glyphicon glyphicon-ok text-success" title="{{ _("online") }}"></span>
- {% elif trunk.status == "offline" or trunk.status is None %}
- <span class="glyphicon glyphicon-remove text-danger" title="{{ _("offline") }}"></span>
- {% else %}
- {{ trunk.status }}
- {% end %}
- </td>
- <td>{{ trunk.description or trunk.account }}</td>
- <td>{{ trunk.username }}@{{ trunk.registrar }}</td>
- </tr>
- {% end %}
- </tbody>
- </table>
-{% end block %}
"FireinfoDeviceAndGroupsTable" : FireinfoDeviceAndGroupsTableModule,
"FireinfoGeoTable" : FireinfoGeoTableModule,
"TalkCallLog" : TalkCallLogModule,
+ "TalkLines" : TalkLinesModule,
"TalkOngoingCalls" : TalkOngoingCallsModule,
"TrackerPeerList" : TrackerPeerListModule,
"Wish" : WishModule,
(r"/phonebook/(\w+)", TalkPhonebookAccountHandler),
(r"/phonebook", TalkPhonebookHandler),
(r"/profile", TalkProfileHandler),
- (r"/trunks", TalkTrunksHandler),
] + authentication_handlers + static_handlers)
# accounts.ipfire.org
#!/usr/bin/python
+import re
+
import database
from misc import Object
def init(self):
db_args = self.backend.db._db_args
db_args.update({
- "database" : "yate",
+ "database" : "kamailio",
})
self._db = database.Connection(**db_args)
return accounts
def user_is_online(self, sip_id):
- res = self.db.get("SELECT 1 FROM lines WHERE username = %s \
+ res = self.db.get("SELECT 1 FROM location WHERE username = %s \
AND expires >= NOW() LIMIT 1", sip_id)
if res:
return False
- def get_trunks(self):
- res = self.db.query("SELECT * FROM accounts WHERE enabled = TRUE \
- ORDER BY description, account")
-
- return res
+ def get_lines(self, account=None):
+ accounts_cache = {}
- def get_lines(self, account):
- if not account.is_talk_enabled():
+ if account and not account.is_talk_enabled():
return []
- res = self.db.query("SELECT location, expires, user_agent FROM lines \
- WHERE username = %s ORDER BY expires DESC", account.sip_id)
+ if account:
+ res = self.db.query("SELECT contact AS location, expires, user_agent, socket \
+ FROM location WHERE domain = %s AND username = %s ORDER BY expires DESC",
+ "ipfire.org", account.sip_id)
+ else:
+ res = self.db.query("SELECT username, domain, contact AS location, \
+ expires, user_agent, socket FROM location ORDER BY username, expires DESC")
result = []
for row in res:
- # Check if TLS is used
- row.tls_enabled = row.location.startswith("sip/sips:") or \
- "transport=TLS" in row.location
+ if account:
+ row.account = account
+ elif row.username:
+ try:
+ row.account = accounts_cache[row.username]
+ except KeyError:
+ row.account = accounts_cache[row.username] = \
+ self.accounts.get_by_sip_id(row.username)
+ else:
+ row.account = None
- # Strip a leading sip/
- if row.location.startswith("sip/"):
- row.location = row.location[4:]
+ # Check if TLS is used
+ row.tls_enabled = row.socket.startswith("tls:")
# Cut off everything after ;
row.location, sep, rest = row.location.partition(";")
return result
- def _process_cdr(self, entries):
+ def _process_sip_uri(self, sip_uri):
+ sip_uri, delimiter, rest = sip_uri.partition(";")
+
+ m = re.match(r"^sip:([0-9]{4})@ipfire\.org", sip_uri)
+ if m:
+ return m.group(1)
+
+ # Remove trailing default port
+ if sip_uri.endswith(":5060"):
+ sip_uri = sip_uri.replace(":5060", "")
+
+ return sip_uri
+
+ def _process_cdr(self, entries, replace_sip_uris=False):
accounts_cache = {}
result = []
for row in entries:
+ if replace_sip_uris:
+ row["caller"] = self._process_sip_uri(row.caller)
+
try:
- row.caller_account = accounts_cache[row.caller]
+ row["caller_account"] = accounts_cache[row.caller]
except KeyError:
- row.caller_account = accounts_cache[row.caller] = \
+ row["caller_account"] = accounts_cache[row.caller] = \
self.accounts.get_by_sip_id(row.caller)
+ if replace_sip_uris:
+ row["called"] = self._process_sip_uri(row.called)
+
try:
- row.called_account = accounts_cache[row.called]
+ row["called_account"] = accounts_cache[row.called]
except KeyError:
- row.called_account = accounts_cache[row.called] = \
+ row["called_account"] = accounts_cache[row.called] = \
self.accounts.get_by_sip_id(row.called)
if not row.called_account:
- row.called_conference = self.get_conference(row.called)
+ row["called_conference"] = self.get_conference(row.called)
else:
- row.called_conference = None
+ row["called_conference"] = None
+
+ try:
+ row["time"] = datetime.datetime.fromutctimestamp(row.time)
+ except:
+ pass
result.append(row)
return result
def get_call_log(self, account, limit=25):
- res = self.db.query("SELECT * FROM cdr \
- WHERE ((direction = 'incoming' AND called = %s) OR (direction = 'outgoing' AND caller = %s)) \
- AND (status = 'answered' OR \
- (status = 'ringing' AND (reason = 'hangup' OR reason = 'Request Terminated'))) \
- AND NOT (status = 'outgoing' AND reason = 'pickup') \
- AND ended = TRUE ORDER BY time DESC LIMIT %s", account.sip_id, account.sip_id, limit)
+ if account:
+ res = self.db.query("SELECT * FROM cdr WHERE (caller = %s OR called = %s) \
+ ORDER BY time DESC LIMIT %s", account.sip_id, account.sip_id, limit)
+ else:
+ res = self.db.query("SELECT * FROM cdr ORDER BY time DESC LIMIT %s", limit)
return self._process_cdr(res)
if account and sip_id is None:
sip_id = account.sip_id
+ # If the given account does not have a SIP ID,
+ # we not need to search for any active sessions
+ if sip_id is None:
+ return []
+
if sip_id:
- res = self.db.query("SELECT * FROM cdr \
- WHERE direction = 'incoming' AND (caller = %s OR called = %s) \
- AND status = 'answered' AND NOT ended \
- ORDER BY time", sip_id, sip_id)
+ sip_id = "sip:%s@%%" % sip_id
+
+ res = self.db.query("SELECT from_uri AS caller, \
+ to_uri AS called, start_time AS time FROM dialog \
+ WHERE from_uri LIKE %s OR to_uri LIKE %s ORDER BY time",
+ sip_id, sip_id)
else:
- res = self.db.query("SELECT * FROM cdr \
- WHERE direction = 'incoming' AND \
- status = 'answered' AND NOT ended \
- ORDER BY time")
+ res = self.db.query("SELECT from_uri AS caller, to_uri AS called, \
+ start_time AS time FROM dialog ORDER BY time")
- return self._process_cdr(res)
+ return self._process_cdr(res, replace_sip_uris=True)
def initiate_call(self, caller, called, when=None):
self.db.execute("INSERT INTO dialout(caller, called, not_before) \
# Favourites
def get_favourite_contacts(self, account, limit=6):
- ignored_numbers = ["9999", account.sip_id, ""]
-
- res = self.db.query("SELECT called, %s AS caller, COUNT(*) AS count FROM cdr \
- WHERE direction = 'outgoing' AND caller = %s AND status = 'answered' \
- AND NOT called = ANY(%s) GROUP BY called ORDER BY count DESC LIMIT %s",
- account.sip_id, account.sip_id, ignored_numbers, limit)
+ res = self.db.query("SELECT src_user AS caller, dst_ouser AS called, \
+ COUNT(*) AS count FROM acc WHERE method = %s AND src_user = %s AND \
+ dst_ouser BETWEEN %s AND %s AND time >= NOW() - INTERVAL '1 year' \
+ GROUP BY caller, called ORDER BY count DESC LIMIT %s",
+ "INVITE", account.sip_id, "1000", "1999", limit)
return self._process_cdr(res)
participants = [p.caller for p in self.participants]
for invitee in self.backend.talk.get_phonebook():
+ if not invitee.sip_id:
+ continue
+
if invitee.sip_id in participants:
continue
if not account:
raise tornado.web.HTTPError(404, "Account not found: %s" % uid)
- # Get the call log, currently registered lines and ongoing calls
- if self.current_user == account or self.current_user.is_admin():
- call_log = self.talk.get_call_log(account)
- lines = self.talk.get_lines(account)
- ongoing_calls = self.talk.get_ongoing_calls(account)
- else:
- call_log = None
- lines = None
- ongoing_calls = None
-
- self.render("talk/phonebook-contact.html", account=account,
- ongoing_calls=ongoing_calls, call_log=call_log, lines=lines)
-
-
-class TalkTrunksHandler(BaseHandler):
- @tornado.web.authenticated
- def get(self):
- if not self.current_user.is_admin():
- raise tornado.web.HTTPError(403)
-
- trunks = self.talk.get_trunks()
-
- self.render("talk/trunks.html", trunks=trunks)
+ self.render("talk/phonebook-contact.html", account=account)
class TalkDiagnosisHandler(BaseHandler):
def planet(self):
return self.handler.planet
+ @property
+ def talk(self):
+ return self.handler.talk
+
@property
def wishlist(self):
return self.handler.wishlist
class TalkCallLogModule(UIModule):
- def render(self, calls):
- return self.render_string("talk/modules/call-log.html", calls=calls)
+ def render(self, account=None, viewer=None):
+ if (account is None or not self.current_user == account) \
+ and not self.current_user.is_admin():
+ raise RuntimeException("Insufficient permissions")
+
+ if viewer is None:
+ viewer = self.current_user
+
+ calls = self.talk.get_call_log(account)
+
+ return self.render_string("talk/modules/call-log.html",
+ calls=calls, viewer=viewer)
+
+
+class TalkLinesModule(UIModule):
+ def render(self, account=None, show_account=False):
+ if (account is None or not self.current_user == account) \
+ and not self.current_user.is_admin():
+ raise RuntimeException("Insufficient permissions")
+
+ lines = self.talk.get_lines(account)
+
+ return self.render_string("talk/modules/lines.html",
+ show_account=show_account, lines=lines)
class TalkOngoingCallsModule(UIModule):
- def render(self, calls):
- return self.render_string("talk/modules/ongoing-calls.html", calls=calls)
+ def render(self, account=None):
+ 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)
+
+ return ""
class TrackerPeerListModule(UIModule):