TT#29350 call flow advanced diagram builder

- static css and javascript libs added

Change-Id: I4d46b9736fc5c04c9d7d55d484b647741fc2eafe
changes/01/18201/54
Roman Romanchenko 7 years ago
parent c81ca69b70
commit 3c6a7ff9f0

@ -4737,6 +4737,22 @@ sub get_pcap :Chained('callflow_base') :PathPart('pcap') :Args(0) {
$c->response->body($pcap);
}
sub get_uas_json :Chained('callflow_base') :PathPart('uas_json') :Args(0) {
my ($self, $c) = @_;
my %int_uas = (
$c->config->{callflow}->{lb_int}, 'lb',
$c->config->{callflow}->{lb_ext}, 'lb',
$c->config->{callflow}->{proxy}, 'proxy',
$c->config->{callflow}->{sbc}, 'sbc',
$c->config->{callflow}->{app}, 'app',
$c->config->{callflow}->{pbx}, 'pbx',
);
$c->response->content_type('application/json');
$c->response->body(encode_json(\%int_uas));
}
sub get_json :Chained('callflow_base') :PathPart('json') :Args(0) {
my ($self, $c) = @_;
my $cid = $c->stash->{callid};

@ -0,0 +1,96 @@
.class-rect {
fill: white;
stroke-width: 1px;
stroke: black;
}
.class-rect:hover {
cursor: pointer;
}
.class-label {
font-size: 10px;
font-family: Courier
}
.class-label:hover {
cursor: pointer;
font-weight: bold;
}
.class-message {
font-size: x-small;
font-family: Courier
}
.class-message:hover {
font-weight: bold;
cursor: pointer;
}
.class-title {
font-size: big;
font-family: Courier
}
.message-number {
font-size: small;
}
.class-vertical-line {
stroke: #888;
}
#editorDiv {
display: none;
padding: 10px;
position:absolute;
background-color: white;
z-index: 1001;
border: solid 1px black;
}
textarea {
height: 100%;
width: 100%;
box-sizing: border-box;
resize: none;
background: #f1f1f1;
}
#controls {
height: 30px;
}
#diagram_header {
height: 30px;
background: #f1f1f1;
}
#diagram_wrapper {
height: 600px;
width: 100%;
}
#diagram {
height: 70%;
overflow-y: auto;
overflow-x: hidden;
}
#message-frame {
height: 30%;
width: 100%;
font-family: Courier;
}
/* overrides*/
#content {
margin-top: 0;
margin-bottom: 0;
}
#masthead {
padding: 0;
}

@ -0,0 +1,72 @@
var dictedit = {
value: function(id){
var res = {};
var ta = document.getElementById(id);
var rows = ta.value.split(ta.dataset["dictedit_delim"]);
for (var i = 0; i < rows.length; i++) {
var x = rows[i].split(ta.dataset["dictedit_sep"]);
if (x[0] != "")
{
res[x[0]] = x[1];
}
}
return res;
},
init: function(id, opts, initKeys, initValues) {
opts = opts || {};
var ta = document.getElementById(id);
var newNode = document.createElement("div");
newNode.id = id + "_dictedit" //+ new Date().getTime();
newNode.className = "dictedit-wrapper " + id + "_dictedit";
ta.parentNode.insertBefore(newNode,ta);
ta.style.display = "none";
var delim = opts.delim || ";";
var sep = opts.sep || " : ";
ta.dataset["dictedit_sep"] = sep;
ta.dataset["dictedit_delim"] = delim;
for(var i = 0; i < initKeys.length; i++){
if (initKeys[i].length > 0){
dictedit.addRow(id, initKeys[i], initValues[i]);
}
}
dictedit.updateRows(id.split("_dictedit")[0]);
},
addRow: function(taID, a, b){
var id = taID + "_dictedit";
this.__handleRowAdd(id, a, b);
},
__handleKeyPress: function(e){
var id = e.target.parentNode.id;
var y = function(){
dictedit.updateRows(id.split("_dictedit")[0]);
}
setTimeout(y, 100);
},
__handleRowAdd: function(id, a, b) {
var taID = id.split("_dictedit")[0];
var dataset = document.getElementById(taID).dataset;
var sep = dataset.dictedit_sep || ": ";
var delim = dataset.dictedit_delim || "; ";
var temp = new Date().getTime().toString();
var outS = "<input onkeydown='dictedit.__handleKeyPress(event)' class='" + temp + " left' value='" + a + "' readonly>"
+ "<input onkeydown='dictedit.__handleKeyPress(event)' class='" + temp + " right' value='" + b + "'>"
+ "<br class='" + temp + "'>";
$("#" + id).append(outS);
},
updateRows(taID)
{
var id = taID + "_dictedit";
var ret = "";
var dataset = document.getElementById(taID).dataset;
var sep = dataset.dictedit_sep || ": ";
var delim = dataset.dictedit_delim || "; ";
var list = document.getElementById(id).getElementsByTagName("input");
for(var i = 0; i < list.length/2; i++){
ret += list[i*2].value + sep + list[i*2+1].value + delim;
}
document.getElementById(taID).value = ret;
return ret;
}
}

@ -0,0 +1,385 @@
var colors=["green", "red", "sienna", "orange", "black", "purple", "chocolate", "olivedrab", "darkred", "darkslategrey", "midnightblue", "maroon", "teal", "goldenrod", "gray", "darkolivegreen", "darkcyan", "brown", "peru", "mediumorchild", "navy", "saddlebrown", "coral"];
function line_color(d)
{
return colors[d];
}
var aliases = { };
function classToAlias(c)
{
var alias = aliases[c];
if (alias == undefined)
{
alias = c;
}
return alias;
}
function receiver(d, considerports)
{
var rc = d.dst_ip;
if (considerports)
{
rc += ":" + d.dst_port;
}
return classToAlias(rc);
}
function sender(d, considerports)
{
var rc = d.src_ip;
if (considerports)
{
rc += ":" + d.src_port;
}
return classToAlias(rc);
}
function prepareData(data, considerports)
{
console.log("prepare data");
data.frames.forEach(function(m, i) {
m.hidden = false;
m.drawable = true;
});
data.frames.forEach(function(m, i) {
var i = (sender(m, considerports) == receiver(m, considerports))
m.internal = i;
m.drawable = !i;
});
return data;
}
function hostPortToClass(c)
{
return "class-" + classToAlias(c).replace(/\./g, "_").replace(":", "_");
}
function toggleSVG(node)
{
console.log(node);
var x = document.getElementsByClassName(node);
console.log(x.length);
var i; for (i = 0; i < x.length; i++)
{
x[i].style.display = (x[i].style.display == 'none') ? 'block' : 'none';
}
}
function createSvg(container)
{
var width = parseInt(d3.select(container).style("width"), 10);
return d3.select(container).append("svg").attr("width", width );
}
function redraw(data, considerports)
{
var svg = d3.selectAll("svg");
svg.remove();
draw_sequence_diagram(data, considerports);
}
function hidePackets(rawdata, pattern, considerports)
{
console.log("hide packets", pattern);
rawdata.frames.forEach(function(item, i)
{
if (sender(item, considerports) == pattern || receiver(item, considerports) == pattern)
{
item.hidden = !item.hidden;
}
});
redraw(rawdata, considerports);
}
function getNodes(data, considerports)
{
var senders = d3.set(data.map(function(d){ return sender(d, considerports); })).values();
var receivers = d3.set(data.map(function(d){ return receiver(d, considerports); })).values();
return _.union(senders, receivers);
}
function considerPortsClick(rawdata, considerports)
{
console.log("consider ports click");
prepareData(rawdata, !considerports);
redraw(rawdata, !considerports);
}
function draw_sequence_diagram(rawdata, considerports)
{
var chartName = rawdata.call;
var data = rawdata.frames;
function considerPorts()
{
considerPortsClick(rawdata, considerports);
}
var message_frame = d3.select("#message-frame");
function showMessageBody(m)
{
message_frame.html("<textarea>" + "packet# " + m.id + "\n" + m.payload + "</textarea>");
}
function listClasses(m, considerports)
{
return hostPortToClass(sender(m, considerports)) + " " +
hostPortToClass(receiver(m, considerports)) + " " +
"class-message";
}
function classClick(node)
{
console.log("class click", node);
// FIXME reimplement with d3
// toggleSVG(hostPortToClass(node));
hidePackets(rawdata, node, considerports);
}
function initAliases(nodes, preAliases)
{
if (_.isEmpty(aliases))
{
var nodes1 = nodes.slice();
if (typeof preAliases !== 'undefined')
{
for (var key in preAliases)
{
var index = nodes1.indexOf(key);
if (index != -1)
{
nodes1[index] = preAliases[key];
}
}
}
dictedit.init("nodeEditor", null, nodes, nodes1);
aliases = dictedit.value("nodeEditor");
applyNodeEdit();
}
}
var button1 = d3.select("#considerPortsButton").on("click", considerPorts);
var expand = d3.select("#expandEditor").on("click", expandEditor);
var apply = d3.select("#applyNodeEdit").on("click", applyNodeEdit);
function applyNodeEdit() {
aliases = dictedit.value("nodeEditor");
redraw(rawdata, considerports);
};
function expandEditor() {
$("#editorDiv").slideToggle(200);
};
var header = createSvg("#diagram_header");
var svg = createSvg("#diagram");
var margin = { top: 20, right: 20, bottom: 20, left: 20 },
width = +svg.attr('width') - margin.left - margin.right,
height = +svg.attr('height') - margin.top - margin.bottom,
g = header.append('g').attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// Graph title
g.append('text')
.attr('x', (width / 2))
.attr('y', 0 - (margin.top / 4))
.attr('class', 'class-title')
.text(chartName);
var nodes = getNodes(data, considerports);
initAliases(nodes, rawdata.aliases);
var XPAD = 200; // horizontal padding for vertical lines/messages/labels
var YPAD = 3;
var VERT_SPACE = parseInt(width/nodes.length);
var MESSAGE_SPACE = 25;
svg.attr("height", (data.length+2)*MESSAGE_SPACE);
var MESSAGE_LABEL_X_OFFSET = -50;
var MESSAGE_LABEL_Y_OFFSET = 10;
var MESSAGE_ARROW_Y_OFFSET = 15;
var CLASS_WIDTH = 120;
var CLASS_LABEL_X_OFFSET = -30;
var CLASS_LABEL_Y_OFFSET = 25;
// Draw vertical lines
nodes.forEach(function(c, i) {
var line = svg.append("line")
.attr("class", "class-vertical-line")
.attr("x1", XPAD + i * VERT_SPACE)
.attr("y1", 0) // YPAD + MESSAGE_SPACE)
.attr("x2", XPAD + i * VERT_SPACE)
.attr("y2", YPAD + data.length * (MESSAGE_SPACE + 5))
});
// Draw class labels
nodes.forEach(function(c, i) {
var x = XPAD + i * VERT_SPACE;
var g1 = header.append("g")
.attr("transform", "translate(" + x + "," + YPAD + ")")
.attr("class", "class-rect")
.append("rect")
.attr("x", -CLASS_WIDTH/2)
.attr("y", "0")
.attr("width", CLASS_WIDTH)
.attr("height", "24px")
.on("click", function() { classClick(c) })
var g2 = header.append("g")
.attr("transform", "translate(" + x + "," + YPAD + ")")
.append("text")
.attr("class", "class-label")
.attr("text-anchor", "middle")
.text(function (d) { return classToAlias(c); })
.attr("dy", "16px")
.on("click", function() { classClick(c) })
});
var i = -1;
var message_number = 0;
data.forEach(function(m, c) {
// FIXME var lcolor = line_color(m.session_id);
var lcolor = line_color(0);
var tcolor = "black";
if (!m.drawable)
{
return;
}
i++;
if (m.hidden)
{
lcolor = "lightgrey";
tcolor = "lightgrey";
}
// draw packet number
var xPos = 0;
var yPos = MESSAGE_LABEL_Y_OFFSET + i * MESSAGE_SPACE;
var classes = listClasses(m, considerports);
var g1 = svg.append("g")
.attr("transform", "translate(" + xPos + "," + yPos + ")")
.attr("text-anchor", "left")
.attr("class", classes)
.attr("class", "message-number")
.append("text")
.style("fill", tcolor)
.text(message_number++);
// draw timestamp
var xPos = XPAD/2;
var yPos = MESSAGE_LABEL_Y_OFFSET + i * MESSAGE_SPACE;
g1 = svg.append("g")
.attr("transform", "translate(" + xPos + "," + yPos + ")")
.attr("text-anchor", "middle")
.attr("class", classes)
.append("text")
.style("fill", tcolor)
.style("font-size", "10px")
.text(m.timestamp);
// draw message labels
xPos = XPAD + MESSAGE_LABEL_X_OFFSET + (((nodes.indexOf(receiver(m, considerports)) - nodes.indexOf(sender(m, considerports))) * VERT_SPACE) / 2) + (nodes.indexOf(sender(m, considerports)) * VERT_SPACE);
yPos = MESSAGE_LABEL_Y_OFFSET + i * MESSAGE_SPACE;
// draw message labels
g1 = svg.append("g")
.attr("transform", "translate(" + xPos + "," + yPos + ")")
.append("text")
.attr("dx", "5px")
.attr("dy", "-2px")
.attr("text-anchor", "begin")
.style("fill", tcolor)
.attr("class", classes)
.on("click", function() { showMessageBody(m) })
.text(m.method + " " + m.request_uri);
// draw line
var y = MESSAGE_ARROW_Y_OFFSET + (i) * MESSAGE_SPACE;
var line = svg.append("line")
.style("stroke", lcolor)
.attr("x1", XPAD + nodes.indexOf(sender(m, considerports)) * VERT_SPACE)
.attr("y1", y)
.attr("x2", XPAD + nodes.indexOf(receiver(m, considerports)) * VERT_SPACE)
.attr("y2", y)
.attr("marker-end", "url(#end)")
.on("click", function() { showMessageBody(m) })
if (m.hidden)
{
line.attr("marker-end", "url(#hidden)");
}
else
{
line.attr("marker-end", "url(#end)");
}
});
// Arrow style
svg.append("svg:defs").selectAll("marker")
.data(["end"])
.enter().append("svg:marker")
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 10)
.attr("refY", 0)
.attr("markerWidth", 10)
.attr("markerHeight", 10)
.attr("orient", "auto")
.attr("fill", "blue")
.append("svg:path")
.style("stroke", "blue")
.attr("d", "M0,-3L10,0L0,3");
// Arrow style
svg.append("svg:defs").selectAll("marker")
.data(["hidden"])
.enter().append("svg:marker")
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 10)
.attr("refY", 0)
.attr("markerWidth", 10)
.attr("markerHeight", 10)
.attr("orient", "auto")
.attr("fill", "lightgrey")
.append("svg:path")
.style("stroke", "lightgrey")
.attr("d", "M0,-3L10,0L0,3");
showMessageBody(data[0]);
}

@ -1,37 +1,61 @@
[% site_config.title = c.loc('Call Flow for Call-ID [_1]', callid) -%]
<link rel="stylesheet" href="/css/callflow.css">
<link rel="stylesheet" href="/css/ui-lightness/jquery-ui-1.10.3.custom.min.css">
<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>
<script src="https://d3js.org/d3.v4.js"></script>
<script src="https://d3js.org/d3-dsv.v1.min.js"></script>
<script src="/js/libs/aliaseditor.js"></script>
<script src="/js/libs/diagram.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
<script src="/js/libs/jquery-1.7.2.min.js"></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> [% c.loc('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> [% c.loc('Download PCAP') %]</a>
</span>
<div id="controls">
<a class="btn btn-primary btn-small" href="[% c.uri_for('/back') %]"><i class="icon-arrow-left"></i> [% c.loc('Back') %]</a>
<a class="btn btn-primary btn-small" href="[% c.uri_for_action('/subscriber/get_pcap', c.req.captures) %]"><i class="icon-file"></i> [% c.loc('Download PCAP') %]</a>
<input id="considerPortsButton" type="button" class="btn-primary btn-small" value="Consider Ports"/>
<input id="expandEditor" type="button" class="btn-primary btn-small" value="Node Editor"/>
<div id="editorDiv">
node|alias
<textarea id="nodeEditor"></textarea>
<input id="applyNodeEdit" type="button" value="Apply"/>
</div>
</div>
[% back_created = 1 -%]
<div class="row">
[% FOREACH m IN messages -%]
<div class="alert alert-[% m.type %]">[% m.text %]</div>
[% END -%]
<div id="diagram_wrapper">
<div id="diagram_header">
</div>
<div id="diagram">
</div>
<div id="message-frame">
</div>
</div>
<div class="ngcp-separator"></div>
<script>
$(document).mouseup(function(e) {
var container = $("#editorDiv");
// if the target of the click isn't the container nor a descendant of the container
if (!container.is(e.target) && container.has(e.target).length === 0)
{
container.hide();
}
});
$( document ).ready(function() {
$.getJSON("[% c.uri_for_action('/subscriber/get_uas_json', c.req.captures) %]", function(aliases) {
<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>
$.getJSON("[% c.uri_for_action('/subscriber/get_json', c.req.captures) %]", function(frames) {
var considerports = true;
var data = {};
data.frames = frames;
data.aliases = aliases;
data = prepareData(data, considerports);
redraw(data, considerports);
});
});
});
</script>
[% back_created = 1 -%]
[% # vim: set tabstop=4 syntax=html expandtab: -%]

Loading…
Cancel
Save