Implement graphing of captured sip messages.

Last part of subscriber-base call-flow handling.
TBD: filtering call-id in subscriber master data.
agranig/1_0_subfix
Andreas Granig 13 years ago
parent 688ed5871e
commit e43fc4b1c5

2
debian/control vendored

@ -36,6 +36,7 @@ Build-Depends: debhelper (>= 8),
libdata-printer-perl,
libxml-mini-perl,
libgd-gd2-perl,
libhtml-parser-perl,
ngcp-schema
Standards-Version: 3.9.4
Homepage: http://sipwise.com/
@ -76,6 +77,7 @@ Depends: libcatalyst-actionrole-acl-perl,
libdata-printer-perl,
libxml-mini-perl,
libgd-gd2-perl,
libhtml-parser-perl,
ngcp-schema,
${misc:Depends},
${perl:Depends}

@ -1,7 +1,7 @@
package NGCP::Panel::Controller::Subscriber;
use Sipwise::Base;
use namespace::sweep;
BEGIN { extends 'Catalyst::Controller'; }
use HTML::Entities;
use NGCP::Panel::Utils;
use NGCP::Panel::Utils::Navigation;
use NGCP::Panel::Utils::Contract;
@ -1380,6 +1380,11 @@ sub master :Chained('base') :PathPart('details') :CaptureArgs(0) {
{ name => "contact", search => 1, title => "Contact" },
{ name => "expires", search => 1, title => "Expires" },
]);
$c->stash->{capture_dt_columns} = NGCP::Panel::Utils::Datatables::set_columns($c, [
{ name => "timestamp", search => 1, title => "Timestamp" },
{ name => "call_id", search => 1, title => "Call-ID" },
{ name => "method", search => 1, title => "Method" },
]);
$c->stash(
template => 'subscriber/master.tt',
@ -1930,6 +1935,25 @@ sub ajax_voicemails :Chained('master') :PathPart('voicemails/ajax') :Args(0) {
$c->detach( $c->view("JSON") );
}
sub ajax_captured_calls :Chained('master') :PathPart('callflow/ajax') :Args(0) {
my ($self, $c) = @_;
my $caller_rs = $c->model('DB')->resultset('messages')->search({
'me.caller_uuid' => $c->stash->{subscriber}->uuid,
});
my $callee_rs = $c->model('DB')->resultset('messages')->search({
'me.callee_uuid' => $c->stash->{subscriber}->uuid,
});
# TODO: group_by or distinct on call_id!
my $rs = $caller_rs->union($callee_rs)->search(undef, {
order_by => { -asc => 'me.timestamp' },
});
NGCP::Panel::Utils::Datatables::process($c, $rs, $c->stash->{capture_dt_columns});
$c->detach( $c->view("JSON") );
}
sub voicemail :Chained('master') :PathPart('voicemail') :CaptureArgs(1) {
my ($self, $c, $vm_id) = @_;
@ -2380,7 +2404,6 @@ sub get_pcap :Chained('callflow_base') :PathPart('pcap') :Args(0) {
$c->response->header ('Content-Disposition' => 'attachment; filename="' . $cid . '.pcap"');
$c->response->content_type('application/octet-stream');
$c->response->body($pcap);
}
sub get_png :Chained('callflow_base') :PathPart('png') :Args(0) {
@ -2399,6 +2422,60 @@ sub get_png :Chained('callflow_base') :PathPart('png') :Args(0) {
$c->response->header ('Content-Disposition' => 'attachment; filename="' . $cid . '.png"');
$c->response->content_type('image/png');
$c->response->body($png);
}
sub get_callmap :Chained('callflow_base') :PathPart('callmap') :Args(0) {
my ($self, $c) = @_;
my $cid = $c->stash->{callid};
my $calls_rs = $c->model('DB')->resultset('messages')->search({
'me.call_id' => { -in => [ $cid, $cid.'_b2b-1' ] },
}, {
order_by => { -asc => 'timestamp' },
});
my $calls = [ $calls_rs->all ];
my $map = NGCP::Panel::Utils::Callflow::generate_callmap($c, $calls);
$c->stash(
canvas => $map,
template => 'subscriber/callmap.tt',
);
}
sub get_packet :Chained('callflow_base') :PathPart('packet') :Args() {
my ($self, $c, $packet_id) = @_;
my $cid = $c->stash->{callid};
my $packet = $c->model('DB')->resultset('messages')->find({
'me.call_id' => { -in => [ $cid, $cid.'_b2b-1' ] },
'me.id' => $packet_id,
}, {
order_by => { -asc => 'timestamp' },
});
return unless($packet);
my $pkg = { $packet->get_inflated_columns };
my $t = DateTime->from_epoch(
epoch => $pkg->{timestamp},
time_zone => DateTime::TimeZone->new(name => 'local'),
);
my $tstamp = $t->ymd('-') . ' ' . $t->hms(':') . '.' . $t->millisecond;
$pkg->{payload} = encode_entities($pkg->{payload});
$pkg->{payload} =~ s/\r//g;
$pkg->{payload} =~ s/([^\n]{120})/$1<br\/>/g;
$pkg->{payload} =~ s/^([^\n]+)\n/<b>$1<\/b>\n/;
$pkg->{payload} = $tstamp .' ('.$pkg->{timestamp}.')<br/>'.
$pkg->{src_ip}.':'.$pkg->{src_port}.' &rarr; '. $pkg->{dst_ip}.':'.$pkg->{dst_port}.'<br/><br/>'.
$pkg->{payload};
$pkg->{payload} =~ s/\n([a-zA-Z0-9\-_]+\:)/\n<b>$1<\/b>/g;
$pkg->{payload} =~ s/\n/<br\/>/g;
$c->response->content_type('text/html');
$c->response->body($pkg->{payload});
}

@ -0,0 +1,222 @@
function modalPopup(align, top, width, padding, disableColor, disableOpacity, backgroundColor, borderColor, borderWeight, borderRadius, fadeOutTime, url, loadingImage){
var containerid = "innerModalPopupDiv";
var popupDiv = document.createElement('div');
var popupMessage = document.createElement('div');
var blockDiv = document.createElement('div');
popupDiv.setAttribute('id', 'outerModalPopupDiv');
popupDiv.setAttribute('class', 'outerModalPopupDiv');
popupMessage.setAttribute('id', 'innerModalPopupDiv');
popupMessage.setAttribute('class', 'innerModalPopupDiv');
blockDiv.setAttribute('id', 'blockModalPopupDiv');
blockDiv.setAttribute('class', 'blockModalPopupDiv');
blockDiv.setAttribute('onClick', 'closePopup(' + fadeOutTime + ')');
document.body.appendChild(popupDiv);
popupDiv.appendChild(popupMessage);
document.body.appendChild(blockDiv);
if (/MSIE (\d+\.\d+);/.test(navigator.userAgent)){ //test for MSIE x.x;
var ieversion=new Number(RegExp.$1) // capture x.x portion and store as a number
if(ieversion>6) {
getScrollHeight(top);
}
} else {
getScrollHeight(top);
}
document.getElementById('outerModalPopupDiv').style.display='block';
document.getElementById('outerModalPopupDiv').style.width = width + 'px';
document.getElementById('outerModalPopupDiv').style.padding = borderWeight + 'px';
document.getElementById('outerModalPopupDiv').style.background = borderColor;
document.getElementById('outerModalPopupDiv').style.borderRadius = borderRadius + 'px';
document.getElementById('outerModalPopupDiv').style.MozBorderRadius = borderRadius + 'px';
document.getElementById('outerModalPopupDiv').style.WebkitBorderRadius = borderRadius + 'px';
document.getElementById('outerModalPopupDiv').style.borderWidth = 0 + 'px';
document.getElementById('outerModalPopupDiv').style.position = 'absolute';
document.getElementById('outerModalPopupDiv').style.zIndex = 100;
document.getElementById('innerModalPopupDiv').style.padding = padding + 'px';
document.getElementById('innerModalPopupDiv').style.background = backgroundColor;
document.getElementById('innerModalPopupDiv').style.borderRadius = (borderRadius - 3) + 'px';
document.getElementById('innerModalPopupDiv').style.MozBorderRadius = (borderRadius - 3) + 'px';
document.getElementById('innerModalPopupDiv').style.WebkitBorderRadius = (borderRadius - 3) + 'px';
document.getElementById('blockModalPopupDiv').style.width = 100 + '%';
document.getElementById('blockModalPopupDiv').style.border = 0 + 'px';
document.getElementById('blockModalPopupDiv').style.padding = 0 + 'px';
document.getElementById('blockModalPopupDiv').style.margin = 0 + 'px';
document.getElementById('blockModalPopupDiv').style.background = disableColor;
document.getElementById('blockModalPopupDiv').style.opacity = (disableOpacity / 100);
document.getElementById('blockModalPopupDiv').style.filter = 'alpha(Opacity=' + disableOpacity + ')';
document.getElementById('blockModalPopupDiv').style.zIndex = 99;
document.getElementById('blockModalPopupDiv').style.position = 'fixed';
document.getElementById('blockModalPopupDiv').style.top = 0 + 'px';
document.getElementById('blockModalPopupDiv').style.left = 0 + 'px';
if(align=="center") {
document.getElementById('outerModalPopupDiv').style.marginLeft = (-1 * (width / 2)) + 'px';
document.getElementById('outerModalPopupDiv').style.left = 50 + '%';
} else if(align=="left") {
document.getElementById('outerModalPopupDiv').style.marginLeft = 0 + 'px';
document.getElementById('outerModalPopupDiv').style.left = 10 + 'px';
} else if(align=="right") {
document.getElementById('outerModalPopupDiv').style.marginRight = 0 + 'px';
document.getElementById('outerModalPopupDiv').style.right = 10 + 'px';
} else {
document.getElementById('outerModalPopupDiv').style.marginLeft = (-1 * (width / 2)) + 'px';
document.getElementById('outerModalPopupDiv').style.left = 50 + '%';
}
blockPage();
var page_request = false;
if (window.XMLHttpRequest) {
page_request = new XMLHttpRequest();
} else if (window.ActiveXObject) {
try {
page_request = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
try {
page_request = new ActiveXObject("Microsoft.XMLHTTP");
} catch (e) { }
}
} else {
return false;
}
page_request.onreadystatechange=function(){
if((url.search(/.jpg/i)==-1) && (url.search(/.jpeg/i)==-1) && (url.search(/.gif/i)==-1) && (url.search(/.png/i)==-1) && (url.search(/.bmp/i)==-1)) {
pageloader(page_request, containerid, loadingImage);
} else {
imageloader(url, containerid, loadingImage);
}
}
page_request.open('GET', url, true);
page_request.send(null);
}
function pageloader(page_request, containerid, loadingImage){
document.getElementById(containerid).innerHTML = '<div align="center"><img src="' + loadingImage + '" border="0" /></div>';
if (page_request.readyState == 4 && (page_request.status==200 || window.location.href.indexOf("http")==-1)) {
document.getElementById(containerid).innerHTML=page_request.responseText;
}
}
function imageloader(url, containerid, loadingImage) {
document.getElementById(containerid).innerHTML = '<div align="center"><img src="' + loadingImage + '" border="0" /></div>';
document.getElementById(containerid).innerHTML='<div align="center"><img src="' + url + '" border="0" /></div>';
}
function blockPage() {
var blockdiv = document.getElementById('blockModalPopupDiv');
var height = screen.height;
blockdiv.style.height = height + 'px';
blockdiv.style.display = 'block';
}
function getScrollHeight(top) {
var h = window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop;
if (/MSIE (\d+\.\d+);/.test(navigator.userAgent)) {
var ieversion=new Number(RegExp.$1);
if(ieversion>6) {
document.getElementById('outerModalPopupDiv').style.top = h + top + 'px';
} else {
document.getElementById('outerModalPopupDiv').style.top = top + 'px';
}
} else {
document.getElementById('outerModalPopupDiv').style.top = h + top + 'px';
}
}
function closePopup(fadeOutTime) {
fade('outerModalPopupDiv', fadeOutTime);
document.getElementById('blockModalPopupDiv').style.display='none';
}
function fade(id, fadeOutTime) {
var el = document.getElementById(id);
if(el == null) {
return;
}
if(el.FadeState == null) {
if(el.style.opacity == null || el.style.opacity == '' || el.style.opacity == '1') {
el.FadeState = 2;
} else {
el.FadeState = -2;
}
}
if(el.FadeState == 1 || el.FadeState == -1) {
el.FadeState = el.FadeState == 1 ? -1 : 1;
el.fadeTimeLeft = fadeOutTime - el.fadeTimeLeft;
} else {
el.FadeState = el.FadeState == 2 ? -1 : 1;
el.fadeTimeLeft = fadeOutTime;
setTimeout("animateFade(" + new Date().getTime() + ",'" + id + "','" + fadeOutTime + "')", 33);
}
}
function animateFade(lastTick, id, fadeOutTime) {
var currentTick = new Date().getTime();
var totalTicks = currentTick - lastTick;
var el = document.getElementById(id);
if(el.fadeTimeLeft <= totalTicks) {
el.style.opacity = el.FadeState == 1 ? '1' : '0';
el.style.filter = 'alpha(opacity = ' + (el.FadeState == 1 ? '100' : '0') + ')';
el.FadeState = el.FadeState == 1 ? 2 : -2;
document.body.removeChild(el);
return;
}
el.fadeTimeLeft -= totalTicks;
var newOpVal = el.fadeTimeLeft / fadeOutTime;
if(el.FadeState == 1) {
newOpVal = 1 - newOpVal;
}
el.style.opacity = newOpVal;
el.style.filter = 'alpha(opacity = ' + (newOpVal*100) + ')';
setTimeout("animateFade(" + currentTick + ",'" + id + "','" + fadeOutTime + "')", 33);
}

@ -0,0 +1,37 @@
[% site_config.title = 'Call Flow for Call-ID ' _ callid -%]
<script type="text/javascript" src="/js/libs/jquery.popup.js"></script>
<script type="text/javascript">
function pkgPopup(pkgid) {
// http://www.jquerypopup.com/documentation.php
modalPopup("center", 100, 640, 10, "#666666", 40, "#FFFFFF", "#000000", 4, 5, 300,
"[% c.uri_for_action('/subscriber/get_packet', c.req.captures) %]/"+pkgid,
"/img/loader.gif");
}
</script>
<div class="row">
<span class="pull-left" style="margin:0 5px 0 5px;">
<a class="btn btn-primary btn-large" href="[% c.uri_for('/back') %]"><i class="icon-arrow-left"></i> Back</a>
<a class="btn btn-primary btn-large" href="[% c.uri_for_action('/subscriber/get_pcap', c.req.captures) %]"><i class="icon-file"></i> Download PCAP</a>
</span>
</div>
[% back_created = 1 -%]
<div class="row">
[% FOREACH m IN messages -%]
<div class="alert alert-[% m.type %]">[% m.text %]</div>
[% END -%]
</div>
<div class="ngcp-separator"></div>
<img src="[% c.uri_for_action('/subscriber/get_png', c.req.captures) %]"
width="[% canvas.width %]" height="[% canvas.height %]" usemap="#diamap" style="min-width:[% canvas.width %]px !important" />
<map name="diamap">
[% FOREACH area IN canvas.areas -%]
<area shape="rect" coords="[% area.coords %]" href="javascript:pkgPopup([% area.id %])" />
[% END -%]
</map>
[% # vim: set tabstop=4 syntax=html expandtab: -%]

@ -161,6 +161,34 @@
</div>
</div>
<div class="accordion-group">
<div class="accordion-heading">
<a class="accordion-toggle" data-toggle="collapse" data-parent="#subscriber_data" href="#collapse_cap">Captured Dialogs</a>
</div>
<div class="accordion-body collapse" id="collapse_cap">
<div class="accordion-inner">
[%
helper.name = 'Captured Dialogs';
#helper.column_sort = 'origtime';
helper.dt_columns = capture_dt_columns;
helper.close_target = close_target;
helper.create_flag = create_flag;
helper.edit_flag = edit_flag;
helper.form_object = form;
helper.ajax_uri = c.uri_for_action('/subscriber/ajax_captured_calls', [c.req.captures.0]);
helper.dt_buttons = [
{ name = 'Call Flow', uri = "callflow/'+encodeURIComponent(full.call_id)+'/callmap", class = 'btn-small btn-primary', icon = 'icon-random' },
];
PROCESS 'helpers/datatables.tt';
%]
</div>
</div>
</div>
</div>

Loading…
Cancel
Save