Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • opentrafficshaper/opentrafficshaper
  • eezysol/opentrafficshaper
  • sudesh/opentrafficshaper
  • Yuriy/opentrafficshaper
  • nkukard/opentrafficshaper
  • fcardoso/opentrafficshaper
6 results
Show changes
Showing
with 2958 additions and 256 deletions
(function($){function init(plot){var selection={first:{x:-1,y:-1},second:{x:-1,y:-1},show:false,active:false};var savedhandlers={};var mouseUpHandler=null;function onMouseMove(e){if(selection.active){updateSelection(e);plot.getPlaceholder().trigger("plotselecting",[getSelection()])}}function onMouseDown(e){if(e.which!=1)return;document.body.focus();if(document.onselectstart!==undefined&&savedhandlers.onselectstart==null){savedhandlers.onselectstart=document.onselectstart;document.onselectstart=function(){return false}}if(document.ondrag!==undefined&&savedhandlers.ondrag==null){savedhandlers.ondrag=document.ondrag;document.ondrag=function(){return false}}setSelectionPos(selection.first,e);selection.active=true;mouseUpHandler=function(e){onMouseUp(e)};$(document).one("mouseup",mouseUpHandler)}function onMouseUp(e){mouseUpHandler=null;if(document.onselectstart!==undefined)document.onselectstart=savedhandlers.onselectstart;if(document.ondrag!==undefined)document.ondrag=savedhandlers.ondrag;selection.active=false;updateSelection(e);if(selectionIsSane())triggerSelectedEvent();else{plot.getPlaceholder().trigger("plotunselected",[]);plot.getPlaceholder().trigger("plotselecting",[null])}return false}function getSelection(){if(!selectionIsSane())return null;if(!selection.show)return null;var r={},c1=selection.first,c2=selection.second;$.each(plot.getAxes(),function(name,axis){if(axis.used){var p1=axis.c2p(c1[axis.direction]),p2=axis.c2p(c2[axis.direction]);r[name]={from:Math.min(p1,p2),to:Math.max(p1,p2)}}});return r}function triggerSelectedEvent(){var r=getSelection();plot.getPlaceholder().trigger("plotselected",[r]);if(r.xaxis&&r.yaxis)plot.getPlaceholder().trigger("selected",[{x1:r.xaxis.from,y1:r.yaxis.from,x2:r.xaxis.to,y2:r.yaxis.to}])}function clamp(min,value,max){return value<min?min:value>max?max:value}function setSelectionPos(pos,e){var o=plot.getOptions();var offset=plot.getPlaceholder().offset();var plotOffset=plot.getPlotOffset();pos.x=clamp(0,e.pageX-offset.left-plotOffset.left,plot.width());pos.y=clamp(0,e.pageY-offset.top-plotOffset.top,plot.height());if(o.selection.mode=="y")pos.x=pos==selection.first?0:plot.width();if(o.selection.mode=="x")pos.y=pos==selection.first?0:plot.height()}function updateSelection(pos){if(pos.pageX==null)return;setSelectionPos(selection.second,pos);if(selectionIsSane()){selection.show=true;plot.triggerRedrawOverlay()}else clearSelection(true)}function clearSelection(preventEvent){if(selection.show){selection.show=false;plot.triggerRedrawOverlay();if(!preventEvent)plot.getPlaceholder().trigger("plotunselected",[])}}function extractRange(ranges,coord){var axis,from,to,key,axes=plot.getAxes();for(var k in axes){axis=axes[k];if(axis.direction==coord){key=coord+axis.n+"axis";if(!ranges[key]&&axis.n==1)key=coord+"axis";if(ranges[key]){from=ranges[key].from;to=ranges[key].to;break}}}if(!ranges[key]){axis=coord=="x"?plot.getXAxes()[0]:plot.getYAxes()[0];from=ranges[coord+"1"];to=ranges[coord+"2"]}if(from!=null&&to!=null&&from>to){var tmp=from;from=to;to=tmp}return{from:from,to:to,axis:axis}}function setSelection(ranges,preventEvent){var axis,range,o=plot.getOptions();if(o.selection.mode=="y"){selection.first.x=0;selection.second.x=plot.width()}else{range=extractRange(ranges,"x");selection.first.x=range.axis.p2c(range.from);selection.second.x=range.axis.p2c(range.to)}if(o.selection.mode=="x"){selection.first.y=0;selection.second.y=plot.height()}else{range=extractRange(ranges,"y");selection.first.y=range.axis.p2c(range.from);selection.second.y=range.axis.p2c(range.to)}selection.show=true;plot.triggerRedrawOverlay();if(!preventEvent&&selectionIsSane())triggerSelectedEvent()}function selectionIsSane(){var minSize=plot.getOptions().selection.minSize;return Math.abs(selection.second.x-selection.first.x)>=minSize&&Math.abs(selection.second.y-selection.first.y)>=minSize}plot.clearSelection=clearSelection;plot.setSelection=setSelection;plot.getSelection=getSelection;plot.hooks.bindEvents.push(function(plot,eventHolder){var o=plot.getOptions();if(o.selection.mode!=null){eventHolder.mousemove(onMouseMove);eventHolder.mousedown(onMouseDown)}});plot.hooks.drawOverlay.push(function(plot,ctx){if(selection.show&&selectionIsSane()){var plotOffset=plot.getPlotOffset();var o=plot.getOptions();ctx.save();ctx.translate(plotOffset.left,plotOffset.top);var c=$.color.parse(o.selection.color);ctx.strokeStyle=c.scale("a",.8).toString();ctx.lineWidth=1;ctx.lineJoin=o.selection.shape;ctx.fillStyle=c.scale("a",.4).toString();var x=Math.min(selection.first.x,selection.second.x)+.5,y=Math.min(selection.first.y,selection.second.y)+.5,w=Math.abs(selection.second.x-selection.first.x)-1,h=Math.abs(selection.second.y-selection.first.y)-1;ctx.fillRect(x,y,w,h);ctx.strokeRect(x,y,w,h);ctx.restore()}});plot.hooks.shutdown.push(function(plot,eventHolder){eventHolder.unbind("mousemove",onMouseMove);eventHolder.unbind("mousedown",onMouseDown);if(mouseUpHandler)$(document).unbind("mouseup",mouseUpHandler)})}$.plot.plugins.push({init:init,options:{selection:{mode:null,color:"#e8cfac",shape:"round",minSize:5}},name:"selection",version:"1.1"})})(jQuery);
\ No newline at end of file
(function($){var options={series:{stack:null}};function init(plot){function findMatchingSeries(s,allseries){var res=null;for(var i=0;i<allseries.length;++i){if(s==allseries[i])break;if(allseries[i].stack==s.stack)res=allseries[i]}return res}function stackData(plot,s,datapoints){if(s.stack==null||s.stack===false)return;var other=findMatchingSeries(s,plot.getData());if(!other)return;var ps=datapoints.pointsize,points=datapoints.points,otherps=other.datapoints.pointsize,otherpoints=other.datapoints.points,newpoints=[],px,py,intery,qx,qy,bottom,withlines=s.lines.show,horizontal=s.bars.horizontal,withbottom=ps>2&&(horizontal?datapoints.format[2].x:datapoints.format[2].y),withsteps=withlines&&s.lines.steps,fromgap=true,keyOffset=horizontal?1:0,accumulateOffset=horizontal?0:1,i=0,j=0,l,m;while(true){if(i>=points.length)break;l=newpoints.length;if(points[i]==null){for(m=0;m<ps;++m)newpoints.push(points[i+m]);i+=ps}else if(j>=otherpoints.length){if(!withlines){for(m=0;m<ps;++m)newpoints.push(points[i+m])}i+=ps}else if(otherpoints[j]==null){for(m=0;m<ps;++m)newpoints.push(null);fromgap=true;j+=otherps}else{px=points[i+keyOffset];py=points[i+accumulateOffset];qx=otherpoints[j+keyOffset];qy=otherpoints[j+accumulateOffset];bottom=0;if(px==qx){for(m=0;m<ps;++m)newpoints.push(points[i+m]);newpoints[l+accumulateOffset]+=qy;bottom=qy;i+=ps;j+=otherps}else if(px>qx){if(withlines&&i>0&&points[i-ps]!=null){intery=py+(points[i-ps+accumulateOffset]-py)*(qx-px)/(points[i-ps+keyOffset]-px);newpoints.push(qx);newpoints.push(intery+qy);for(m=2;m<ps;++m)newpoints.push(points[i+m]);bottom=qy}j+=otherps}else{if(fromgap&&withlines){i+=ps;continue}for(m=0;m<ps;++m)newpoints.push(points[i+m]);if(withlines&&j>0&&otherpoints[j-otherps]!=null)bottom=qy+(otherpoints[j-otherps+accumulateOffset]-qy)*(px-qx)/(otherpoints[j-otherps+keyOffset]-qx);newpoints[l+accumulateOffset]+=bottom;i+=ps}fromgap=false;if(l!=newpoints.length&&withbottom)newpoints[l+2]+=bottom}if(withsteps&&l!=newpoints.length&&l>0&&newpoints[l]!=null&&newpoints[l]!=newpoints[l-ps]&&newpoints[l+1]!=newpoints[l-ps+1]){for(m=0;m<ps;++m)newpoints[l+ps+m]=newpoints[l+m];newpoints[l+1]=newpoints[l-ps+1]}}datapoints.points=newpoints}plot.hooks.processDatapoints.push(stackData)}$.plot.plugins.push({init:init,options:options,name:"stack",version:"1.2"})})(jQuery);
\ No newline at end of file
(function($){function processRawData(plot,series,datapoints){var handlers={square:function(ctx,x,y,radius,shadow){var size=radius*Math.sqrt(Math.PI)/2;ctx.rect(x-size,y-size,size+size,size+size)},diamond:function(ctx,x,y,radius,shadow){var size=radius*Math.sqrt(Math.PI/2);ctx.moveTo(x-size,y);ctx.lineTo(x,y-size);ctx.lineTo(x+size,y);ctx.lineTo(x,y+size);ctx.lineTo(x-size,y)},triangle:function(ctx,x,y,radius,shadow){var size=radius*Math.sqrt(2*Math.PI/Math.sin(Math.PI/3));var height=size*Math.sin(Math.PI/3);ctx.moveTo(x-size/2,y+height/2);ctx.lineTo(x+size/2,y+height/2);if(!shadow){ctx.lineTo(x,y-height/2);ctx.lineTo(x-size/2,y+height/2)}},cross:function(ctx,x,y,radius,shadow){var size=radius*Math.sqrt(Math.PI)/2;ctx.moveTo(x-size,y-size);ctx.lineTo(x+size,y+size);ctx.moveTo(x-size,y+size);ctx.lineTo(x+size,y-size)}};var s=series.points.symbol;if(handlers[s])series.points.symbol=handlers[s]}function init(plot){plot.hooks.processDatapoints.push(processRawData)}$.plot.plugins.push({init:init,name:"symbols",version:"1.0"})})(jQuery);
\ No newline at end of file
(function($){var options={series:{threshold:null}};function init(plot){function thresholdData(plot,s,datapoints,below,color){var ps=datapoints.pointsize,i,x,y,p,prevp,thresholded=$.extend({},s);thresholded.datapoints={points:[],pointsize:ps,format:datapoints.format};thresholded.label=null;thresholded.color=color;thresholded.threshold=null;thresholded.originSeries=s;thresholded.data=[];var origpoints=datapoints.points,addCrossingPoints=s.lines.show;var threspoints=[];var newpoints=[];var m;for(i=0;i<origpoints.length;i+=ps){x=origpoints[i];y=origpoints[i+1];prevp=p;if(y<below)p=threspoints;else p=newpoints;if(addCrossingPoints&&prevp!=p&&x!=null&&i>0&&origpoints[i-ps]!=null){var interx=x+(below-y)*(x-origpoints[i-ps])/(y-origpoints[i-ps+1]);prevp.push(interx);prevp.push(below);for(m=2;m<ps;++m)prevp.push(origpoints[i+m]);p.push(null);p.push(null);for(m=2;m<ps;++m)p.push(origpoints[i+m]);p.push(interx);p.push(below);for(m=2;m<ps;++m)p.push(origpoints[i+m])}p.push(x);p.push(y);for(m=2;m<ps;++m)p.push(origpoints[i+m])}datapoints.points=newpoints;thresholded.datapoints.points=threspoints;if(thresholded.datapoints.points.length>0){var origIndex=$.inArray(s,plot.getData());plot.getData().splice(origIndex+1,0,thresholded)}}function processThresholds(plot,s,datapoints){if(!s.threshold)return;if(s.threshold instanceof Array){s.threshold.sort(function(a,b){return a.below-b.below});$(s.threshold).each(function(i,th){thresholdData(plot,s,datapoints,th.below,th.color)})}else{thresholdData(plot,s,datapoints,s.threshold.below,s.threshold.color)}}plot.hooks.processDatapoints.push(processThresholds)}$.plot.plugins.push({init:init,options:options,name:"threshold",version:"1.2"})})(jQuery);
\ No newline at end of file
(function($){var options={xaxis:{timezone:null,timeformat:null,twelveHourClock:false,monthNames:null}};function floorInBase(n,base){return base*Math.floor(n/base)}function formatDate(d,fmt,monthNames,dayNames){if(typeof d.strftime=="function"){return d.strftime(fmt)}var leftPad=function(n,pad){n=""+n;pad=""+(pad==null?"0":pad);return n.length==1?pad+n:n};var r=[];var escape=false;var hours=d.getHours();var isAM=hours<12;if(monthNames==null){monthNames=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]}if(dayNames==null){dayNames=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]}var hours12;if(hours>12){hours12=hours-12}else if(hours==0){hours12=12}else{hours12=hours}for(var i=0;i<fmt.length;++i){var c=fmt.charAt(i);if(escape){switch(c){case"a":c=""+dayNames[d.getDay()];break;case"b":c=""+monthNames[d.getMonth()];break;case"d":c=leftPad(d.getDate());break;case"e":c=leftPad(d.getDate()," ");break;case"h":case"H":c=leftPad(hours);break;case"I":c=leftPad(hours12);break;case"l":c=leftPad(hours12," ");break;case"m":c=leftPad(d.getMonth()+1);break;case"M":c=leftPad(d.getMinutes());break;case"q":c=""+(Math.floor(d.getMonth()/3)+1);break;case"S":c=leftPad(d.getSeconds());break;case"y":c=leftPad(d.getFullYear()%100);break;case"Y":c=""+d.getFullYear();break;case"p":c=isAM?""+"am":""+"pm";break;case"P":c=isAM?""+"AM":""+"PM";break;case"w":c=""+d.getDay();break}r.push(c);escape=false}else{if(c=="%"){escape=true}else{r.push(c)}}}return r.join("")}function makeUtcWrapper(d){function addProxyMethod(sourceObj,sourceMethod,targetObj,targetMethod){sourceObj[sourceMethod]=function(){return targetObj[targetMethod].apply(targetObj,arguments)}}var utc={date:d};if(d.strftime!=undefined){addProxyMethod(utc,"strftime",d,"strftime")}addProxyMethod(utc,"getTime",d,"getTime");addProxyMethod(utc,"setTime",d,"setTime");var props=["Date","Day","FullYear","Hours","Milliseconds","Minutes","Month","Seconds"];for(var p=0;p<props.length;p++){addProxyMethod(utc,"get"+props[p],d,"getUTC"+props[p]);addProxyMethod(utc,"set"+props[p],d,"setUTC"+props[p])}return utc}function dateGenerator(ts,opts){if(opts.timezone=="browser"){return new Date(ts)}else if(!opts.timezone||opts.timezone=="utc"){return makeUtcWrapper(new Date(ts))}else if(typeof timezoneJS!="undefined"&&typeof timezoneJS.Date!="undefined"){var d=new timezoneJS.Date;d.setTimezone(opts.timezone);d.setTime(ts);return d}else{return makeUtcWrapper(new Date(ts))}}var timeUnitSize={second:1e3,minute:60*1e3,hour:60*60*1e3,day:24*60*60*1e3,month:30*24*60*60*1e3,quarter:3*30*24*60*60*1e3,year:365.2425*24*60*60*1e3};var baseSpec=[[1,"second"],[2,"second"],[5,"second"],[10,"second"],[30,"second"],[1,"minute"],[2,"minute"],[5,"minute"],[10,"minute"],[30,"minute"],[1,"hour"],[2,"hour"],[4,"hour"],[8,"hour"],[12,"hour"],[1,"day"],[2,"day"],[3,"day"],[.25,"month"],[.5,"month"],[1,"month"],[2,"month"]];var specMonths=baseSpec.concat([[3,"month"],[6,"month"],[1,"year"]]);var specQuarters=baseSpec.concat([[1,"quarter"],[2,"quarter"],[1,"year"]]);function init(plot){plot.hooks.processOptions.push(function(plot,options){$.each(plot.getAxes(),function(axisName,axis){var opts=axis.options;if(opts.mode=="time"){axis.tickGenerator=function(axis){var ticks=[];var d=dateGenerator(axis.min,opts);var minSize=0;var spec=opts.tickSize&&opts.tickSize[1]==="quarter"||opts.minTickSize&&opts.minTickSize[1]==="quarter"?specQuarters:specMonths;if(opts.minTickSize!=null){if(typeof opts.tickSize=="number"){minSize=opts.tickSize}else{minSize=opts.minTickSize[0]*timeUnitSize[opts.minTickSize[1]]}}for(var i=0;i<spec.length-1;++i){if(axis.delta<(spec[i][0]*timeUnitSize[spec[i][1]]+spec[i+1][0]*timeUnitSize[spec[i+1][1]])/2&&spec[i][0]*timeUnitSize[spec[i][1]]>=minSize){break}}var size=spec[i][0];var unit=spec[i][1];if(unit=="year"){if(opts.minTickSize!=null&&opts.minTickSize[1]=="year"){size=Math.floor(opts.minTickSize[0])}else{var magn=Math.pow(10,Math.floor(Math.log(axis.delta/timeUnitSize.year)/Math.LN10));var norm=axis.delta/timeUnitSize.year/magn;if(norm<1.5){size=1}else if(norm<3){size=2}else if(norm<7.5){size=5}else{size=10}size*=magn}if(size<1){size=1}}axis.tickSize=opts.tickSize||[size,unit];var tickSize=axis.tickSize[0];unit=axis.tickSize[1];var step=tickSize*timeUnitSize[unit];if(unit=="second"){d.setSeconds(floorInBase(d.getSeconds(),tickSize))}else if(unit=="minute"){d.setMinutes(floorInBase(d.getMinutes(),tickSize))}else if(unit=="hour"){d.setHours(floorInBase(d.getHours(),tickSize))}else if(unit=="month"){d.setMonth(floorInBase(d.getMonth(),tickSize))}else if(unit=="quarter"){d.setMonth(3*floorInBase(d.getMonth()/3,tickSize))}else if(unit=="year"){d.setFullYear(floorInBase(d.getFullYear(),tickSize))}d.setMilliseconds(0);if(step>=timeUnitSize.minute){d.setSeconds(0)}if(step>=timeUnitSize.hour){d.setMinutes(0)}if(step>=timeUnitSize.day){d.setHours(0)}if(step>=timeUnitSize.day*4){d.setDate(1)}if(step>=timeUnitSize.month*2){d.setMonth(floorInBase(d.getMonth(),3))}if(step>=timeUnitSize.quarter*2){d.setMonth(floorInBase(d.getMonth(),6))}if(step>=timeUnitSize.year){d.setMonth(0)}var carry=0;var v=Number.NaN;var prev;do{prev=v;v=d.getTime();ticks.push(v);if(unit=="month"||unit=="quarter"){if(tickSize<1){d.setDate(1);var start=d.getTime();d.setMonth(d.getMonth()+(unit=="quarter"?3:1));var end=d.getTime();d.setTime(v+carry*timeUnitSize.hour+(end-start)*tickSize);carry=d.getHours();d.setHours(0)}else{d.setMonth(d.getMonth()+tickSize*(unit=="quarter"?3:1))}}else if(unit=="year"){d.setFullYear(d.getFullYear()+tickSize)}else{d.setTime(v+step)}}while(v<axis.max&&v!=prev);return ticks};axis.tickFormatter=function(v,axis){var d=dateGenerator(v,axis.options);if(opts.timeformat!=null){return formatDate(d,opts.timeformat,opts.monthNames,opts.dayNames)}var useQuarters=axis.options.tickSize&&axis.options.tickSize[1]=="quarter"||axis.options.minTickSize&&axis.options.minTickSize[1]=="quarter";var t=axis.tickSize[0]*timeUnitSize[axis.tickSize[1]];var span=axis.max-axis.min;var suffix=opts.twelveHourClock?" %p":"";var hourCode=opts.twelveHourClock?"%I":"%H";var fmt;if(t<timeUnitSize.minute){fmt=hourCode+":%M:%S"+suffix}else if(t<timeUnitSize.day){if(span<2*timeUnitSize.day){fmt=hourCode+":%M"+suffix}else{fmt="%b %d "+hourCode+":%M"+suffix}}else if(t<timeUnitSize.month){fmt="%b %d"}else if(useQuarters&&t<timeUnitSize.quarter||!useQuarters&&t<timeUnitSize.year){if(span<timeUnitSize.year){fmt="%b"}else{fmt="%b %Y"}}else if(useQuarters&&t<timeUnitSize.year){if(span<timeUnitSize.year){fmt="Q%q"}else{fmt="Q%q %Y"}}else{fmt="%Y"}var rt=formatDate(d,fmt,opts.monthNames,opts.dayNames);return rt}}})})}$.plot.plugins.push({init:init,options:options,name:"time",version:"1.0"});$.plot.formatDate=formatDate})(jQuery);
\ No newline at end of file
/*
* Functions to help create flot graphs
* Copyright (c) 2013-2014, AllWorldIT
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
*/
// Function draw a graph
function awit_flot_draw_graph(options) {
// Setup default graph options
var defaults = {
series: {
lines: {
show: true,
lineWidth: 1,
fill: true,
fillColor: {
colors: [
{ opacity: 0.1 },
{ opacity: 0.13 }
]
}
},
points: {
show: false,
lineWidth: 2,
radius: 3
},
shadowSize: 0,
stack: true
},
grid: {
hoverable: true,
clickable: false,
tickColor: "#F9F9F9",
borderWidth: 1
},
legend: {
labelBoxBorderColor: "#AAAAAA"
},
xaxes: [
{
mode: "time",
tickLength: 10
}
],
yaxes: [
{
position: 'left',
min: 0,
tickFormatter: _flot_format_bandwidth
},
{
position: 'right',
min: 0,
tickDecimals: false
}
]
}
// Merge our options ontop of our defaults
var plotOptions = jQuery.extend({},defaults,options);
// Grab the placeholder
var placeholder = jQuery('#'+plotOptions.id);
// Plot the graph, the [] signifies an empty dataset, the data is populated by awitds
var plot = jQuery.plot(placeholder, [ ], plotOptions);
return plot;
}
// Function draw pie graph
function awit_flot_draw_pie(options) {
// Setup default graph options
var defaults = {
series: {
pie: {
show: true,
radius: 1,
label: {
show: true,
radius: 3/4,
formatter: _flot_format_pie_label,
background: {
opacity: 0.5,
color: '#000000'
}
}
}
}
}
// Merge our options ontop of our defaults
var plotOptions = jQuery.extend({},defaults,options);
// Set count to 1 and override timestamp to 1 for all identifiers
if (typeof(plotOptions.awitds) !== 'undefined') {
for (var tag in plotOptions.awitds.xaxis) {
for (var identifier in plotOptions.awitds.xaxis[tag]) {
plotOptions.awitds.xaxis[tag][identifier].maxCount = 1;
plotOptions.awitds.xaxis[tag][identifier].overrideTimestamp = 1;
}
}
}
// Grab the placeholder
var placeholder = jQuery('#'+plotOptions.id);
// Plot the graph, the [] signifies an empty dataset, the data is populated by awitds
var plot = jQuery.plot(placeholder, [ ], plotOptions);
return plot;
}
// Function to format thousands with , and add Kbps
function _flot_format_bandwidth(value, axis) {
return _flot_format_thousands(value,axis) + ' Kbps';
}
// Function to format thousands
function _flot_format_thousands(value, axis) {
// Convert number to string
value = value.toString();
// Match on 3 digits
var R = new RegExp('(-?[0-9]+)([0-9]{3})');
while(R.test(value)) {
// Replace market with ,
value = value.replace(R, '$1,$2');
}
return value;
}
function _flot_format_pie_label(label, series) {
return "<div style='font-size:8pt; text-align:center; padding:2px; color:white;'>" + label + "<br/>" + Math.round(series.percent) + "%</div>";
}
// vim: ts=4
opentrafficshaper/plugins/webserver/pages/static/logo-inverted-short.png

4.85 KiB

opentrafficshaper/plugins/webserver/pages/static/logo-inverted.png

5.59 KiB

opentrafficshaper/plugins/webserver/pages/static/logo.png

5.54 KiB

# OpenTrafficShaper webserver module: statistics page
# Copyright (C) 2007-2023, AllWorldIT
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
package opentrafficshaper::plugins::webserver::pages::statistics;
use strict;
use warnings;
# Exporter stuff
require Exporter;
our (@ISA,@EXPORT,@EXPORT_OK);
@ISA = qw(Exporter);
@EXPORT = qw(
);
@EXPORT_OK = qw(
);
use DateTime;
use HTML::Entities;
use HTTP::Status qw(
:constants
);
use JSON;
use URI;
use URI::Escape qw(
uri_escape
);
use awitpt::util qw(
parseURIQuery
isNumber
);
use opentrafficshaper::logger;
use opentrafficshaper::plugins;
use opentrafficshaper::plugins::configmanager qw(
getPoolByName
getInterfaceGroup
getInterfaceGroups
getInterface
getTrafficClass
getAllTrafficClasses
isTrafficClassIDValid
);
use opentrafficshaper::plugins::statistics::statistics;
# Graphs to display on pools stat page
sub POOL_GRAPHS {
{
3600 => 'Last Hour',
86400 => 'Last 24 Hours',
604800 => 'Last 7 Days',
2419200 => 'Last Month'
}
};
# Graphs by pool
sub byPool
{
my ($kernel,$system,$client_session_id,$request) = @_;
# Header
my $content = "";
# Check request
if ($request->method ne "GET") {
$content .=<<EOF;
<p class="info text-center">Invalid Method</p>
EOF
goto END;
}
# Parse GET data
my $queryParams = $request->uri->query_form_hash;
# We need our PID
if (!defined($queryParams->{'pool'})) {
$content .=<<EOF;
<p class="info text-center">No "pool" in Query String</p>
EOF
goto END;
}
# Check we have an interface group ID and pool name
my ($interfaceGroupID,$poolName) = split(/:/,$queryParams->{'pool'});
if (!defined($interfaceGroupID) || !defined($poolName)) {
$content .=<<EOF;
<p class="info text-center">Format of "pool" option is invalid, use InterfaceGroupID:PoolName</p>
EOF
goto END;
}
# Check if we get some data back when pulling in the pool from the backend
my $pool;
if (!defined($pool = getPoolByName($interfaceGroupID,$poolName))) {
$content .=<<EOF;
<p class="info text-center">Pool Not Found</p>
EOF
goto END;
}
# Header for the page
$content .= <<EOF;
<legend>
<a href="/limits/pool-list"><span class="glyphicon glyphicon-circle-arrow-left"></span></a>
Pool Stats View
</legend>
EOF
my $timespan = 900;
my $now = time();
my $startTimestamp = $now - $timespan;
# Menu setup
my $menu = [
{
'name' => 'Graphs',
'items' => [
{
'name' => 'Live',
'link' => sprintf("by-pool?pool=%s",uri_escape("$interfaceGroupID:$poolName"))
},
{
'name' => 'Historical',
'link' => sprintf("by-pool?pool=%s&static=1",uri_escape("$interfaceGroupID:$poolName"))
}
]
}
];
my $name = (defined($pool->{'FriendlyName'}) && $pool->{'FriendlyName'} ne "") ? $pool->{'FriendlyName'} :
$pool->{'Name'};
my $nameEncoded = encode_entities($name);
# Build content
$content .= <<EOF;
EOF
# Graphs to display
my @graphs = ();
# Check if we doing a static display or not
if (defined($queryParams->{'static'}) && $queryParams->{'static'}) {
# Loop with periods to display on this page
foreach my $period (sort { $a <=> $b } keys %{POOL_GRAPHS()}) {
my $canvasName = "flotCanvas$period";
my $graphName = POOL_GRAPHS()->{$period};
my $now = time();
my $startTimestamp = $now - $period;
$content .= <<EOF;
<h4 style="color: #8F8F8F;">Statistics: $nameEncoded - $graphName</h4>
<div id="$canvasName" class="flotCanvas poolCanvas" style="width: 800px; height: 240px" ></div>
EOF
# Static graphs
push(@graphs,{
'Type' => 'graph',
'Tag' => $canvasName,
'Title' => sprintf("Pool: %s",$pool->{'Name'}),
'Datasources' => [
{
'Type' => 'ajax',
'Subscriptions' => [
{
'Type' => 'pool',
'Data' => sprintf('%s:%s',$pool->{'InterfaceGroupID'},$pool->{'Name'}),
'StartTimestamp' => $startTimestamp,
'EndTimestamp' => $now
}
]
}
],
'XIdentifiers' => [
{ 'Name' => 'tx.cir', 'Label' => "TX Cir", 'Timespan' => $period },
{ 'Name' => 'tx.limit', 'Label' => "TX Limit", 'Timespan' => $period },
{ 'Name' => 'tx.rate', 'Label' => "TX Rate", 'Timespan' => $period },
{ 'Name' => 'rx.cir', 'Label' => "RX Cir", 'Timespan' => $period },
{ 'Name' => 'rx.limit', 'Label' => "RX Limit", 'Timespan' => $period },
{ 'Name' => 'rx.rate', 'Label' => "RX Rate", 'Timespan' => $period }
]
});
}
# Display live graph
} else {
my $canvasName = "flotCanvas";
$content .= <<EOF;
<h4 style="color: #8F8F8F;">Live Statistics: $nameEncoded</h4>
<div id="$canvasName" class="flotCanvas poolCanvas" style="width: 1000px; height: 400px" />
EOF
# Live graph
push(@graphs,{
'Type' => 'graph',
'Tag' => $canvasName,
'Title' => sprintf("Pool: %s",$pool->{'Name'}),
'Datasources' => [
{
'Type' => 'websocket',
'Subscriptions' => [
sprintf('pool=%s:%s',$pool->{'InterfaceGroupID'},$pool->{'Name'})
]
},
{
'Type' => 'ajax',
'Subscriptions' => [
{
'Type' => 'pool',
'Data' => sprintf('%s:%s',$pool->{'InterfaceGroupID'},$pool->{'Name'}),
'StartTimestamp' => $startTimestamp,
'EndTimestamp' => $now
}
]
}
],
'XIdentifiers' => [
{ 'Name' => 'tx.cir', 'Label' => "TX Cir", 'Timespan' => $timespan },
{ 'Name' => 'tx.limit', 'Label' => "TX Limit", 'Timespan' => $timespan },
{ 'Name' => 'tx.rate', 'Label' => "TX Rate", 'Timespan' => $timespan },
{ 'Name' => 'rx.cir', 'Label' => "RX Cir", 'Timespan' => $timespan },
{ 'Name' => 'rx.limit', 'Label' => "RX Limit", 'Timespan' => $timespan },
{ 'Name' => 'rx.rate', 'Label' => "RX Rate", 'Timespan' => $timespan }
]
});
}
# Build graphs
my $javascript = _buildGraphJavascript(\@graphs);
# Files loaded at end of HTML document
my @javascripts = (
'/static/flot/jquery.flot.min.js',
'/static/flot/jquery.flot.time.min.js',
'/static/flot/jquery.flot.pie.min.js',
'/static/flot/jquery.flot.resize.min.js',
'/static/js/flot-functions.js',
'/static/awit-flot-toolkit/js/jquery.flot.awitds.js',
'/static/awit-flot-toolkit/js/resize.js'
);
my @stylesheets = (
'/static/awit-flot-toolkit/css/awit-flot-toolkit.css'
);
END:
return (HTTP_OK,$content,{ 'menu' => $menu, 'javascripts' => \@javascripts, 'javascript' => $javascript });
}
# Graphs by class
sub byClass
{
my ($kernel,$system,$client_session_id,$request) = @_;
# Header
my $content = "";
# Check request
if ($request->method ne "GET") {
$content .=<<EOF;
<p class="info text-center">Invalid Method</p>
EOF
goto END;
}
# Parse GET data
my $queryParams = $request->uri->query_form_hash;
# We need our class definition
if (!defined($queryParams->{'class'})) {
$content .=<<EOF;
<p class="info text-center">No "class" in Query String</p>
EOF
goto END;
}
# Check we have an interface group ID and traffic class id
my ($interfaceGroupID,$trafficClassID) = split(/:/,$queryParams->{'class'});
if (!defined($interfaceGroupID) || !defined($trafficClassID)) {
$content .=<<EOF;
<p class="info text-center">Format of "class" option is invalid, use InterfaceGroupID:ClassID</p>
EOF
goto END;
}
# Check if we get some data back when pulling in the interface group from the backend
my $interfaceGroup;
if (!defined($interfaceGroup = getInterfaceGroup($interfaceGroupID))) {
$content .=<<EOF;
<p class="info text-center">Interface Group Not Found</p>
EOF
goto END;
}
# Check if we get some data back when pulling in the traffic class from the backend
my $trafficClass;
if (!defined($trafficClass = getTrafficClass($trafficClassID))) {
$content .=<<EOF;
<p class="info text-center">Traffic Class Not Found</p>
EOF
goto END;
}
# Header for the page
$content .= <<EOF;
<legend>
<a href="/statistics/igdashboard?interface-group=$interfaceGroupID">
<span class="glyphicon glyphicon-circle-arrow-left"></span>
</a>
Class Stats View
</legend>
EOF
# Menu setup
my $menu = [
{
'name' => 'Graphs',
'items' => [
{
'name' => 'Live',
'link' => sprintf("by-class?class=%s","$interfaceGroupID:$trafficClassID")
},
{
'name' => 'Historical',
'link' => sprintf("by-class?class=%s&static=1","$interfaceGroupID:$trafficClassID")
}
]
}
];
my $nameEncoded = encode_entities($trafficClass->{'Name'});
# Build content
$content .= <<EOF;
EOF
# Graphs to display
my @graphs = ();
# Check if we doing a static display or not
if (defined($queryParams->{'static'}) && $queryParams->{'static'}) {
# Loop with periods to display on this page
foreach my $period (sort { $a <=> $b } keys %{POOL_GRAPHS()}) {
my $canvasName = "flotCanvas$period";
my $graphName = POOL_GRAPHS()->{$period};
my $now = time();
my $startTimestamp = $now - $period;
$content .= <<EOF;
<h4 style="color: #8F8F8F;">Class Statistics: $nameEncoded - $graphName</h4>
<div id="$canvasName" class="flotCanvas poolCanvas" style="width: 800px; height: 240px" ></div>
EOF
# Static graphs
push(@graphs,{
'Type' => 'graph',
'Tag' => $canvasName,
'Title' => sprintf("Class: %s",$trafficClass->{'Name'}),
'Datasources' => [
{
'Type' => 'ajax',
'Subscriptions' => [
{
'Type' => 'class',
'Data' => sprintf('%s:%s',$interfaceGroupID,$trafficClassID),
'StartTimestamp' => $startTimestamp,
'EndTimestamp' => $now
}
]
}
],
'XIdentifiers' => [
{ 'Name' => 'tx.cir', 'Label' => "TX Cir", 'Timespan' => $period },
{ 'Name' => 'tx.limit', 'Label' => "TX Limit", 'Timespan' => $period },
{ 'Name' => 'tx.rate', 'Label' => "TX Rate", 'Timespan' => $period },
{ 'Name' => 'rx.cir', 'Label' => "RX Cir", 'Timespan' => $period },
{ 'Name' => 'rx.limit', 'Label' => "RX Limit", 'Timespan' => $period },
{ 'Name' => 'rx.rate', 'Label' => "RX Rate", 'Timespan' => $period }
]
});
}
# Display live graph
} else {
my $canvasName = "flotCanvas";
$content .= <<EOF;
<h4 style="color: #8F8F8F;">Live Statistics: $nameEncoded</h4>
<div id="$canvasName" class="flotCanvas poolCanvas" style="width: 1000px; height: 400px" />
EOF
# Live graph
push(@graphs,{
'Type' => 'graph',
'Tag' => $canvasName,
'Title' => sprintf("Class: %s",$trafficClass->{'Name'}),
'Datasources' => [
{
'Type' => 'websocket',
'Subscriptions' => [
sprintf('class=%s:%s',$interfaceGroupID,$trafficClassID)
]
}
],
'XIdentifiers' => [
{ 'Name' => 'tx.cir', 'Label' => "TX Cir", 'Timespan' => 900 },
{ 'Name' => 'tx.limit', 'Label' => "TX Limit", 'Timespan' => 900 },
{ 'Name' => 'tx.rate', 'Label' => "TX Rate", 'Timespan' => 900 },
{ 'Name' => 'rx.cir', 'Label' => "RX Cir", 'Timespan' => 900 },
{ 'Name' => 'rx.limit', 'Label' => "RX Limit", 'Timespan' => 900 },
{ 'Name' => 'rx.rate', 'Label' => "RX Rate", 'Timespan' => 900 }
]
});
}
# Build graphs
my $javascript = _buildGraphJavascript(\@graphs);
# Files loaded at end of HTML document
my @javascripts = (
'/static/flot/jquery.flot.min.js',
'/static/flot/jquery.flot.time.min.js',
'/static/flot/jquery.flot.pie.min.js',
'/static/flot/jquery.flot.resize.min.js',
'/static/js/flot-functions.js',
'/static/awit-flot-toolkit/js/jquery.flot.awitds.js',
'/static/awit-flot-toolkit/js/resize.js'
);
my @stylesheets = (
'/static/awit-flot-toolkit/css/awit-flot-toolkit.css'
);
END:
return (HTTP_OK,$content,{ 'menu' => $menu, 'javascripts' => \@javascripts, 'javascript' => $javascript });
}
# Dashboard display
sub dashboard
{
my ($kernel,$system,$client_session_id,$request) = @_;
# Header
my $content = <<EOF;
<legend>
Dashboard View
</legend>
EOF
# Build list of graphs for the left hand side
my @interfaceGroups = sort(getInterfaceGroups());
my @trafficClasses = sort(getAllTrafficClasses());
my $timespan = 900;
my $now = time();
my $startTimestamp = $now - $timespan;
my @graphs;
my $graphCounter = 0;
foreach my $interfaceGroupID (@interfaceGroups) {
my $interfaceGroup = getInterfaceGroup($interfaceGroupID);
push(@graphs,{
'.InterfaceGroup' => $interfaceGroup->{'ID'},
'Tag' => sprintf('tag%s',$graphCounter++),
'Type' => 'graph',
'Title' => sprintf("%s: Main",$interfaceGroup->{'Name'}),
'Datasources' => [
{
'Type' => 'websocket',
'Subscriptions' => [
sprintf('interface-group=%s',$interfaceGroupID),
]
},
{
'Type' => 'ajax',
'Subscriptions' => [
{
'Type' => 'interface-group',
'Data' => $interfaceGroupID,
'StartTimestamp' => $startTimestamp,
'EndTimestamp' => $now
}
]
}
],
'XIdentifiers' => [
{ 'Name' => 'tx.rate', 'Label' => "TX Rate", 'Timespan' => $timespan },
{ 'Name' => 'rx.rate', 'Label' => "RX Rate", 'Timespan' => $timespan }
]
});
}
foreach my $graph (@graphs) {
my $interfaceGroupEscaped = uri_escape($graph->{'.InterfaceGroup'});
$content .= <<EOF;
<div class="row">
<h4 class="text-center" style="color:#8f8f8f;">$graph->{'Title'}</h4>
<a href="/statistics/igdashboard?interface-group=$interfaceGroupEscaped">
<div id="$graph->{'Tag'}" class="flotCanvas poolCanvas"
style="width: 1000px; height: 250px; margin-left: auto; margin-right: auto;">
</div>
</a>
</div>
EOF
}
# Build graphs
my $javascript = _buildGraphJavascript(\@graphs);
# Files loaded at end of HTML document
my @javascripts = (
'/static/flot/jquery.flot.min.js',
'/static/flot/jquery.flot.time.min.js',
'/static/flot/jquery.flot.pie.min.js',
'/static/flot/jquery.flot.resize.min.js',
'/static/js/flot-functions.js',
'/static/awit-flot-toolkit/js/jquery.flot.awitds.js',
'/static/awit-flot-toolkit/js/resize.js'
);
my @stylesheets = (
'/static/awit-flot-toolkit/css/awit-flot-toolkit.css'
);
return (HTTP_OK,$content,{
'javascripts' => \@javascripts,
'javascript' => $javascript,
'stylesheets' => \@stylesheets
});
}
# Dashboard display for interface groups
sub igdashboard
{
my ($kernel,$system,$client_session_id,$request) = @_;
# Header
my $content = <<EOF;
<legend>
<a href="/statistics/dashboard"><span class="glyphicon glyphicon-circle-arrow-left"></span></a>
Interface Dashboard View
</legend>
EOF
# Get query params
my $queryParams = $request->uri->query_form_hash;
# We need our PID
if (!defined($queryParams->{'interface-group'})) {
$content .=<<EOF;
<p class="info text-center">No "interface-group" in Query String</p>
EOF
return (HTTP_TEMPORARY_REDIRECT,"/statistics");
}
my $interfaceGroup = getInterfaceGroup($queryParams->{'interface-group'});
if (!defined($interfaceGroup)) {
$content .=<<EOF;
<p class="info text-center">Invalid "interface-group" in Query String</p>
EOF
return (HTTP_TEMPORARY_REDIRECT,"/statistics");
}
# Left and right graphs are added to the main graph list
my @graphs = ();
# Left and right graphs
my @leftGraphs = ();
my @rightGraphs = ();
# Build list of graphs for the left hand side
my @trafficClasses = sort(getAllTrafficClasses());
my $timespan = 900;
my $now = time();
my $startTimestamp = $now - $timespan;
foreach my $trafficClassID (@trafficClasses) {
my $trafficClass = getTrafficClass($trafficClassID);
push(@leftGraphs,{
'Type' => 'graph',
'Title' => sprintf("%s: %s",$interfaceGroup->{'Name'},$trafficClass->{'Name'}),
'.URIData' => sprintf('%s:%s',$interfaceGroup->{'ID'},$trafficClassID),
'Datasources' => [
{
'Type' => 'websocket',
'Subscriptions' => [
sprintf('class=%s:%s',$interfaceGroup->{'ID'},$trafficClassID),
sprintf('counter=configmanager.classpoolmembers.%s',$trafficClassID)
]
},
{
'Type' => 'ajax',
'Subscriptions' => [
{
'Type' => 'class',
'Data' => sprintf('%s:%s',$interfaceGroup->{'ID'},$trafficClassID),
'StartTimestamp' => $startTimestamp,
'EndTimestamp' => $now
}
]
},
{
'Type' => 'ajax',
'Subscriptions' => [
{
'Type' => 'counter',
'Data' => sprintf('configmanager.classpoolmembers.%s',$trafficClassID),
'StartTimestamp' => $startTimestamp,
'EndTimestamp' => $now
}
]
}
],
'XIdentifiers' => [
{ 'Name' => 'tx.cir', 'Label' => "TX Cir", 'Timespan' => $timespan },
{ 'Name' => 'tx.limit', 'Label' => "TX Limit", 'Timespan' => $timespan },
{ 'Name' => 'tx.rate', 'Label' => "TX Rate", 'Timespan' => $timespan },
{ 'Name' => 'rx.cir', 'Label' => "RX Cir", 'Timespan' => $timespan },
{ 'Name' => 'rx.limit', 'Label' => "RX Limit", 'Timespan' => $timespan },
{ 'Name' => 'rx.rate', 'Label' => "RX Rate", 'Timespan' => $timespan }
],
'YIdentifiers' => [
{
'Name' => sprintf('configmanager.classpoolmembers.%s',$trafficClassID),
'Label' => "Pool Count",
'Timespan' => $timespan
},
]
});
}
# Pool distribution
my @dataSubscriptions = ();
my @xidentifiers = ();
foreach my $trafficClassID (@trafficClasses) {
my $trafficClass = getTrafficClass($trafficClassID);
push(@dataSubscriptions,sprintf('counter=configmanager.classpools.%s',$trafficClassID));
push(@xidentifiers,{
'Name' => sprintf('configmanager.classpools.%s',$trafficClassID),
'Label' => $trafficClass->{'Name'}
});
}
push(@rightGraphs,{
'Type' => 'pie',
'Title' => "Pool Distribution",
'Datasources' => [
{
'Type' => 'websocket',
'Subscriptions' => \@dataSubscriptions
}
],
'XIdentifiers' => \@xidentifiers
});
# Loop while we have graphs to output
my $graphCounter = 0;
while (@leftGraphs || @rightGraphs) {
# Layout Begin
$content .= <<EOF;
<div class="row">
EOF
# LHS - Begin
$content .= <<EOF;
<div class="col-md-8">
EOF
# Loop with 2 sets of normal graphs per row
for (my $row = 0; $row < 2; $row++) {
# LHS - Begin Row
$content .= <<EOF;
<div class="row">
<div class="col-xs-6">
EOF
# Graph 1
if (defined(my $graph = shift(@leftGraphs))) {
my $uriData = $graph->{'.URIData'};
# Assign this graph a tag
$graph->{'Tag'} = "tag".$graphCounter++;
$content .= <<EOF;
<h4 style="color:#8f8f8f;">$graph->{'Title'}</h4>
<a href="/statistics/by-class?class=$uriData">
<div id="$graph->{'Tag'}" class="flotCanvas dashboardCanvas"
style="width: 600px; height: 250px"></div>
</a>
EOF
push(@graphs,$graph);
}
# LHS - Spacer
$content .= <<EOF;
</div>
<div class="col-xs-6">
EOF
# Graph 2
if (defined(my $graph = shift(@leftGraphs))) {
my $uriData = $graph->{'.URIData'};
# Assign this graph a tag
$graph->{'Tag'} = "tag".$graphCounter++;
$content .= <<EOF;
<h4 style="color:#8f8f8f;">$graph->{'Title'}</h4>
<a href="/statistics/by-class?class=$uriData">
<div id="$graph->{'Tag'}" class="flotCanvas dashboardCanvas"
style="width: 600px; height: 250px"></div>
</a>
EOF
push(@graphs,$graph);
}
# LHS - End Row
$content .= <<EOF;
</div>
</div>
EOF
}
# LHS - End
$content .= <<EOF;
</div>
EOF
# RHS - Begin Row
$content .= <<EOF;
<div class="col-md-4">
EOF
# Graph
if (defined(my $graph = shift(@rightGraphs))) {
# Assign this graph a tag
$graph->{'Tag'} = "tag".$graphCounter++;
$content .= <<EOF;
<h4 style="color:#8f8f8f;">$graph->{'Title'}</h4>
<div id="$graph->{'Tag'}" class="flotCanvas dashboardCanvas" style="width: 600px; height: 340px"></div>
EOF
push(@graphs,$graph);
}
# RHS - End Row
$content .= <<EOF;
</div>
EOF
# Layout End
$content .= <<EOF;
</div>
EOF
}
# Build graphs
my $javascript = _buildGraphJavascript(\@graphs);
# Files loaded at end of HTML document
my @javascripts = (
'/static/flot/jquery.flot.min.js',
'/static/flot/jquery.flot.time.min.js',
'/static/flot/jquery.flot.pie.min.js',
'/static/flot/jquery.flot.resize.min.js',
'/static/js/flot-functions.js',
'/static/awit-flot-toolkit/js/jquery.flot.awitds.js',
'/static/awit-flot-toolkit/js/resize.js'
);
my @stylesheets = (
'/static/awit-flot-toolkit/css/awit-flot-toolkit.css'
);
END:
return (HTTP_OK,$content,{
'javascripts' => \@javascripts,
'javascript' => $javascript,
'stylesheets' => \@stylesheets
});
}
# Return data in json format
#
# Supported URLs:
#
# pool=<tag>:<interface-group-id>:<pool-name>
#
# class=<tag>:<interface-group-id>:<class-id>,...
#
# interface-group=<tag>:<interface-group>,...
#
# counter=<tag>:<counter>,...
#
# max=<max number of entries to return, default 100>
#
# key=<data key to return>
sub jsondata
{
my ($kernel,$system,$client_session_id,$request) = @_;
# Parse GET data
my $queryParams = $request->uri->query_form_hash;
my $rawData = { };
# Check for some things that apply to all types of data
my $startTimestamp;
my $endTimestamp;
if (defined($queryParams->{'start'})) {
$startTimestamp = isNumber($queryParams->{'start'});
}
if (defined($queryParams->{'end'})) {
$endTimestamp = isNumber($queryParams->{'end'});
}
# Process pools
if (defined($queryParams->{'pool'})) {
# Simple de-dupication
my %poolSpecs;
foreach my $poolSpec ($request->uri->query_param('pool')) {
$poolSpecs{$poolSpec} = 1;
}
# Then loop through the unique keys
foreach my $poolSpec (keys %poolSpecs) {
# Check we have a tag, interface group ID and pool name
my ($tag,$rawInterfaceGroupID,$rawPoolName) = split(/:/,$poolSpec);
if (!defined($tag)) {
$system->{'logger'}->log(LOG_INFO,"[WEBSERVER/PAGES/STATISTICS] Invalid format for pool specification '%s'",
$poolSpec
);
return (HTTP_OK,{'status' => 'fail', 'message' => "Invalid format for pool specification '$poolSpec'"},
{ 'type' => 'json' });
}
if (!defined($rawInterfaceGroupID)) {
$system->{'logger'}->log(LOG_INFO,"[WEBSERVER/PAGES/STATISTICS] Tag '%s' has an invalid interface group ID '%s'",
$tag,
$poolSpec
);
return (HTTP_OK,{'status' => 'fail', 'message' => "Tag '$tag' has invalid interface group ID '$rawPoolName'"},
{ 'type' => 'json' });
}
# Check if we can grab the interface group
my $interfaceGroup = getInterfaceGroup($rawInterfaceGroupID);
if (!defined($interfaceGroup)) {
$system->{'logger'}->log(LOG_INFO,"[WEBSERVER/PAGES/STATISTICS] Tag '%s' has an invalid interface group ID '%s'",
$tag,
$rawInterfaceGroupID
);
return (HTTP_OK,{'status' => 'fail', 'message' => "Tag '$tag' has an invalid interface group ID ".
"'$rawInterfaceGroupID'"},
{ 'type' => 'json' });
}
if (!defined($rawPoolName)) {
$system->{'logger'}->log(LOG_INFO,"[WEBSERVER/PAGES/STATISTICS] Tag '%s' has an invalid pool name '%s'",
$tag,
$poolSpec
);
return (HTTP_OK,{'status' => 'fail', 'message' => "Tag '$tag' has an invalid pool name '$rawPoolName'"},
{ 'type' => 'json' });
}
# Grab pool
my $pool = getPoolByName($rawInterfaceGroupID,$rawPoolName);
if (!defined($pool)) {
$system->{'logger'}->log(LOG_INFO,"[WEBSERVER/PAGES/STATISTICS] Tag '%s' has an invalid pool name '%s'",
$tag,
$rawPoolName
);
return (HTTP_OK,{'status' => 'fail', 'message' => "Tag '$tag' has an invalid pool name '$rawPoolName'"},
{ 'type' => 'json' });
}
# Grab SID
my $sid = opentrafficshaper::plugins::statistics::getSIDFromPID($pool->{'ID'});
if (!defined($sid)) {
$system->{'logger'}->log(LOG_INFO,"[WEBSERVER/PAGES/STATISTICS] Tag '%s' stats data cannot be found for pool ".
"'%s'",
$tag,
$rawPoolName
);
return (HTTP_OK,{'status' => 'fail', 'message' => "Tag '$tag' stats data cannot be found for pool '$rawPoolName'"},
{ 'type' => 'json' });
}
# Pull in stats data
my $statsData = opentrafficshaper::plugins::statistics::getStatsBySID($sid,undef,$startTimestamp,$endTimestamp);
# Loop with timestamps
foreach my $timestamp (sort keys %{$statsData}) {
# Grab the stat
my $tstat = $statsData->{$timestamp};
# Loop with its keys
foreach my $item (keys %{$tstat}) {
# Add the keys to the data to return
push(@{$rawData->{$tag}->{$item}->{'data'}},[
$timestamp,
$tstat->{$item}
]);
}
}
}
}
# Process classes
if (defined($queryParams->{'class'})) {
# Simple de-dupication
my %trafficClassIDSpecs;
foreach my $rawClassID ($request->uri->query_param('class')) {
$trafficClassIDSpecs{$rawClassID} = 1;
}
# Then loop through the unique keys
foreach my $trafficClassIDSpec (keys %trafficClassIDSpecs) {
# Check we have a tag, interface group ID and class
my ($tag,$rawInterfaceGroupID,$rawClassID) = split(/:/,$trafficClassIDSpec);
if (!defined($tag)) {
$system->{'logger'}->log(LOG_INFO,"[WEBSERVER/PAGES/STATISTICS] Invalid format for class ID specification '%s'",
$trafficClassIDSpec
);
return (HTTP_OK,{
'status' => 'fail',
'message' => "Invalid format for class ID specification '$trafficClassIDSpec'"
},
{ 'type' => 'json' }
);
}
if (!defined($rawInterfaceGroupID)) {
$system->{'logger'}->log(LOG_INFO,"[WEBSERVER/PAGES/STATISTICS] Tag '%s' has an invalid interface group ID '%s'",
$tag,
$trafficClassIDSpec
);
return (HTTP_OK,{
'status' => 'fail',
'message' => "Tag '$tag' has an invalid interface group ID '$trafficClassIDSpec'"
},
{ 'type' => 'json' }
);
}
if (!defined($rawClassID)) {
$system->{'logger'}->log(LOG_INFO,"[WEBSERVER/PAGES/STATISTICS] Tag '%s' has an invalid class ID '%s'",
$tag,
$trafficClassIDSpec
);
return (HTTP_OK,{'status' => 'fail', 'message' => "Tag '$tag' has an invalid class ID '$trafficClassIDSpec'"},
{ 'type' => 'json' });
}
# Get more sane values...
my $interfaceGroup = getInterfaceGroup($rawInterfaceGroupID);
if (!defined($interfaceGroup)) {
$system->{'logger'}->log(LOG_INFO,"[WEBSERVER/PAGES/STATISTICS] Tag '%s' has a non-existent interface group ID ".
"'%s'",
$tag,
$rawInterfaceGroupID
);
return (HTTP_OK,{'status' => 'fail', 'message' => "Tag '$tag' has a non-existent interface group ID ".
"'$rawInterfaceGroupID'"},
{ 'type' => 'json' });
}
my $trafficClassID = isTrafficClassIDValid($rawClassID);
if (!defined($trafficClassID)) {
$system->{'logger'}->log(LOG_INFO,"[WEBSERVER/PAGES/STATISTICS] Tag '%s' has a non-existent class ID '%s'",
$tag,
$rawClassID
);
return (HTTP_OK,{'status' => 'fail', 'message' => "Tag '$tag' has a non-existent class ID '$rawClassID'"},
{ 'type' => 'json' });
}
# Grab data for each direction associated with a class ID on an inteface group
foreach my $direction ('Tx','Rx') {
# Grab stats ID
my $sid = opentrafficshaper::plugins::statistics::getSIDFromCID($interfaceGroup->{"${direction}Interface"},
$trafficClassID);
if (!defined($sid)) {
$system->{'logger'}->log(LOG_INFO,"[WEBSERVER/PAGES/STATISTICS] Tag '%s' stats data cannot be found for ".
"class ID '%s'",
$tag,
$trafficClassID
);
return (HTTP_OK,{'status' => 'fail', 'message' => "Tag '$tag' stats data cannot be found for class ID "
."'$trafficClassID'"},
{ 'type' => 'json' });
}
# Pull in stats data, override direction used
my $statsData = opentrafficshaper::plugins::statistics::getStatsBySID(
$sid,
{ 'Direction' => lc($direction) },
$startTimestamp,
$endTimestamp
);
# Loop with timestamps
foreach my $timestamp (sort keys %{$statsData}) {
# Grab the stat
my $tstat = $statsData->{$timestamp};
# Loop with its keys
foreach my $item (keys %{$tstat}) {
# Add the keys to the data to return
push(@{$rawData->{$tag}->{$item}->{'data'}},[
$timestamp,
$tstat->{$item}
]);
}
}
}
}
}
# Process interface groups
if (defined($queryParams->{'interface-group'})) {
# Simple de-dupication
my %interfaceGroupIDSpecs;
foreach my $rawInterfaceGroupID ($request->uri->query_param('interface-group')) {
$interfaceGroupIDSpecs{$rawInterfaceGroupID} = 1;
}
# Then loop through the unique keys
foreach my $interfaceGroupIDSpec (keys %interfaceGroupIDSpecs) {
# Check we have a tag, interface group ID and class
my ($tag,$rawInterfaceGroupID) = split(/:/,$interfaceGroupIDSpec);
if (!defined($tag)) {
$system->{'logger'}->log(LOG_INFO,"[WEBSERVER/PAGES/STATISTICS] Invalid format for interface group ".
"specification '%s'",
$interfaceGroupIDSpec
);
return (HTTP_OK,{'status' => 'fail', 'message' => "Invalid format for interface group specification ".
"'$interfaceGroupIDSpec'"},
{ 'type' => 'json' });
}
if (!defined($rawInterfaceGroupID)) {
$system->{'logger'}->log(LOG_INFO,"[WEBSERVER/PAGES/STATISTICS] Tag '%s' has an invalid interface group ID '%s'",
$tag,
$interfaceGroupIDSpec
);
return (HTTP_OK,{'status' => 'fail', 'message' => "Tag '$tag' has an invalid interface group ID ".
"'$interfaceGroupIDSpec'"},
{ 'type' => 'json' });
}
my $interfaceGroupID = getInterfaceGroup($rawInterfaceGroupID);
if (!defined($interfaceGroupID)) {
$system->{'logger'}->log(LOG_INFO,"[WEBSERVER/PAGES/STATISTICS] Tag '%s' has a non-existent interface group ID ".
"'%s'",
$tag,
$rawInterfaceGroupID
);
return (HTTP_OK,{'status' => 'fail', 'message' => "Tag '$tag' has a non-existent interface group ID ".
"'$rawInterfaceGroupID'"},
{ 'type' => 'json' });
}
# Loop with both directions
foreach my $direction ('Tx','Rx') {
# Grab stats ID
my $sid = opentrafficshaper::plugins::statistics::getSIDFromCID($interfaceGroupID->{"${direction}Interface"},0);
if (!defined($sid)) {
$system->{'logger'}->log(LOG_INFO,"[WEBSERVER/PAGES/STATISTICS] Tag '%s' stats data cannot be found for ".
"interface group ID '%s'",
$tag,
$rawInterfaceGroupID
);
return (HTTP_OK,{'status' => 'fail', 'message' => "Tag '$tag' stats data cannot be found for interface group ".
"ID '$rawInterfaceGroupID'"},
{ 'type' => 'json' });
}
# Pull in stats data, override direction used
my $statsData = opentrafficshaper::plugins::statistics::getStatsBySID(
$sid,
{ 'Direction' => lc($direction) },
$startTimestamp,
$endTimestamp
);
# Loop with timestamps
foreach my $timestamp (sort keys %{$statsData}) {
# Grab the stat
my $tstat = $statsData->{$timestamp};
# Loop with its keys
foreach my $item (keys %{$tstat}) {
# Add the keys to the data to return
push(@{$rawData->{$tag}->{$item}->{'data'}},[
$timestamp,
$tstat->{$item}
]);
}
}
}
}
}
# If we need to return a counter, lets see what there is we can return...
if (defined($queryParams->{'counter'})) {
# Lets get unique counters as keys
my %counterSpecs;
foreach my $rawCounterSpec (@{$queryParams->{'counter'}->{'values'}}) {
$counterSpecs{$rawCounterSpec} = 1;
}
# Then loop through the unique keys
foreach my $counterSpec (keys %counterSpecs) {
# Check we have a tag and counter
my ($tag,$rawCounter) = split(/:/,$counterSpec);
if (!defined($tag)) {
$system->{'logger'}->log(LOG_INFO,"[WEBSERVER/PAGES/STATISTICS] Invalid format for counter specification '%s'",
$counterSpec
);
return (HTTP_OK,{'status' => 'fail', 'message' => "Invalid format for counter specification '$counterSpec'"},
{ 'type' => 'json' });
}
if (!defined($rawCounter)) {
$system->{'logger'}->log(LOG_INFO,"[WEBSERVER/PAGES/STATISTICS] Tag '%s' has an invalid interface group ID '%s'",
$tag,
$counterSpec
);
return (HTTP_OK,{'status' => 'fail', 'message' => "Tag '$tag' has an invalid interface group ID ".
"'$counterSpec'"},
{ 'type' => 'json' });
}
# Grab the SID
my $sid = opentrafficshaper::plugins::statistics::getSIDFromCounter($rawCounter);
if (!defined($sid)) {
$system->{'logger'}->log(LOG_INFO,"[WEBSERVER/PAGES/STATISTICS] Tag '%s' has a non-existent counter '%s'",
$tag,
$counterSpec
);
return (HTTP_OK,{'status' => 'fail', 'message' => "Tag '$tag' has a non-existent counter ".
"'$counterSpec'"},
{ 'type' => 'json' });
}
# Pull in stats data
my $statsData = opentrafficshaper::plugins::statistics::getStatsBasicBySID($sid,{ 'Name' => $rawCounter });
# Loop with timestamps
foreach my $timestamp (sort keys %{$statsData}) {
# Grab the stat
my $tstat = $statsData->{$timestamp};
# Loop with its keys
foreach my $item (keys %{$tstat}) {
# Add the keys to the data to return
push(@{$rawData->{$tag}->{$item}->{'data'}},[
$timestamp,
$tstat->{$item}
]);
}
}
}
}
return (HTTP_OK,{'status' => 'success', 'data' => $rawData},{ 'type' => 'json' });
}
# Function to build the javascript we need to display graphs in a canvas
sub _buildGraphJavascript
{
my $graphs = shift;
my $javascript = "";
foreach my $graph (@{$graphs}) {
my $encodedCanvasName = encode_entities($graph->{'Tag'});
# Items we going to need...
my @datasources = ();
my $axesIdentifiers = { 'X' => [ ], 'Y' => [ ] };
my @axesStrList;
# Loop with and build the JS for our datasources
foreach my $datasource (@{$graph->{'Datasources'}}) {
# Websocket based data
if ($datasource->{'Type'} eq "websocket") {
# Create Subscriptions
my @subscriptions;
foreach my $subscription (@{$datasource->{'Subscriptions'}}) {
my $encodedSubscription = encode_entities($subscription);
push(@subscriptions,"{ 'function': 'subscribe', args: ['$encodedCanvasName','$encodedSubscription'] }");
}
# Create subscription string
my $subscriptionStr = join(',',@subscriptions);
# Add datasource
push(@datasources,<<EOF);
{
type: 'websocket',
uri: 'ws://'+window.location.host+'/statistics/graphdata',
shared: true,
// Websocket specific
onconnect: [
$subscriptionStr
]
}
EOF
# JSON based data
} elsif ($datasource->{'Type'} eq "ajax") {
# Create Subscriptions
my @subscriptions;
foreach my $subscription (@{$datasource->{'Subscriptions'}}) {
my $encodedType = encode_entities($subscription->{'Type'});
my $encodedData = encode_entities($subscription->{'Data'});
# Data we nee dto pull
push(@subscriptions,sprintf(
"%s=%s:%s",
$encodedType,
$encodedCanvasName,
$encodedData
));
# Check if we have a start period
if (defined($subscription->{'StartTimestamp'})) {
push(@subscriptions,sprintf("start=%s",$subscription->{'StartTimestamp'}));
}
# Check if we have an end period
if (defined($subscription->{'EndTimestamp'})) {
push(@subscriptions,sprintf("end=%s",$subscription->{'EndTimestamp'}));
}
}
# Create subscription string
my $subscriptionStr = join('&',@subscriptions);
# Add datasource
push(@datasources,<<EOF);
{
type: 'ajax',
url: '///'+window.location.host+'/statistics/jsondata?$subscriptionStr'
}
EOF
}
}
# Loop with axes and build our axes structure
foreach my $axis (keys %{$axesIdentifiers}) {
foreach my $identifier (@{$graph->{"${axis}Identifiers"}}) {
# Our first identifier option is the label
my @options = (
sprintf("label: '%s'", encode_entities($identifier->{'Label'}))
);
# Set limiting factors if there are any
if (defined($identifier->{'Timespan'})) {
push(@options,sprintf("maxTimespan: %s",$identifier->{'Timespan'}));
}
if (defined($identifier->{'Count'})) {
push(@options,sprintf("maxCount: %s",$identifier->{'Count'}));
}
# Join everything up
my $optionsStr = join(' ,',@options);
# Add to axes
push(@{$axesIdentifiers->{$axis}},sprintf("'%s': { %s }",encode_entities($identifier->{'Name'}),$optionsStr));
}
push(@axesStrList,
sprintf("%saxis: { '%s': { %s } }",lc($axis),$encodedCanvasName,join(',',@{$axesIdentifiers->{$axis}}))
);
}
# Build final JS
my $datasourceStr = join(',',@datasources);
my $axesStr = join(',',@axesStrList);
$javascript .=<<EOF;
awit_flot_draw_$graph->{'Type'}({
id: '$encodedCanvasName',
awitds: {
sources: [
$datasourceStr
],
$axesStr
}
});
EOF
}
return $javascript;
}
1;
# vim: ts=4
# OpenTrafficShaper webserver module: statistics websockets
# Copyright (C) 2007-2023, AllWorldIT
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
package opentrafficshaper::plugins::webserver::snapins::websockets::statistics;
use strict;
use warnings;
# Exporter stuff
require Exporter;
our (@ISA,@EXPORT,@EXPORT_OK);
@ISA = qw(Exporter);
@EXPORT = qw(
);
@EXPORT_OK = qw(
);
use DateTime;
use HTTP::Status qw( :constants );
use JSON;
use POE;
use URI;
use awitpt::util qw(
parseKeyPairString
);
use opentrafficshaper::logger;
use opentrafficshaper::plugins::configmanager qw(
getPoolByName
getInterfaceGroup
isTrafficClassIDValid
);
use constant {
VERSION => '1.0.0'
};
# Plugin info
our $pluginInfo = {
Name => "Webserver/WebSockets/Statistics",
Version => VERSION,
Requires => ["webserver","statistics"],
Init => \&plugin_init,
};
# Logger
my $logger;
# Stats subscriptsions
my $subscribers = {}; # Connections subscribed, indexed by client_session_id then ssid
my $subscriberMap = {}; # Index of connections by ssid
# Initialize plugin
sub plugin_init
{
my $system = shift;
# Setup our environment
$logger = $system->{'logger'};
$logger->log(LOG_NOTICE,"[WEBSERVER] OpenTrafficShaper Snapin [WebSockets/Statistics] Module v%s - Copyright (c) 2013-2014".
", AllWorldIT",
VERSION
);
# Protocol conversion
opentrafficshaper::plugins::webserver::snapin_register('HTTP=>WebSocket','statistics','graphdata', {
'requires' => [ 'statistics' ],
'on_request' => \&graphdata_http2websocket,
}
);
# Live graphdata feed
opentrafficshaper::plugins::webserver::snapin_register('WebSocket','statistics','graphdata', {
'requires' => [ 'statistics' ],
'on_request' => \&graphdata_websocket_onrequest,
'on_disconnect' => \&graphdata_websocket_disconnect
}
);
# This is our session handling sending data to connections
POE::Session->create(
inline_states => {
_start => \&_session_start,
_stop => \&_session_stop,
'websocket.send' => \&_session_websocket_send,
}
);
return 1;
}
# Session initialization
sub _session_start
{
my ($kernel,$heap) = @_[KERNEL,HEAP];
# Set our alias
$kernel->alias_set("plugin.webserver.websockets.statistics");
$logger->log(LOG_DEBUG,"[WEBSERVER] Snapin/WebSockets/Statistics - Initialized");
}
# Session stop
sub _session_stop
{
my ($kernel,$heap) = @_[KERNEL,HEAP];
# Remove our alias
$kernel->alias_remove("plugin.webserver.websockets.statistics");
$subscribers = {};
$subscriberMap = {};
$logger->log(LOG_DEBUG,"[WEBSERVER] Snapin/WebSockets/Statistics - Shutdown");
$logger = undef;
}
# Send data to client
sub _session_websocket_send
{
my ($kernel,$heap,$statsData) = @_[KERNEL, HEAP, ARG0, ARG1];
# Loop with stats data
foreach my $ssid (keys %{$statsData}) {
my $ssidStat = $statsData->{$ssid};
# Check if we know about this SSID
if (!defined($subscriberMap->{$ssid})) {
$logger->log(LOG_ERR,"[WEBSERVER] Snapin/WebSockets/Statistics - Subscription inconsidency with SSID '$ssid'");
next;
}
# First stage, pull in the data items we want
my $rawData;
# Loop with timestamps
foreach my $timestamp (sort keys %{$ssidStat}) {
# Grab the stat
my $tstat = $ssidStat->{$timestamp};
# Loop with its keys
foreach my $item (keys %{$tstat}) {
# Add the keys to the data to return
push(@{$rawData->{$item}->{'data'}},[
$timestamp,
$tstat->{$item}
]);
}
}
my $socket = $subscriberMap->{$ssid}->{'Socket'};
my $tag = $subscriberMap->{$ssid}->{'Tag'};
$socket->put(_json_data({ $tag => $rawData }));
}
}
# HTTP to WebSocket
sub graphdata_websocket_disconnect
{
my ($kernel,$globals,$client_session_id) = @_;
$logger->log(LOG_INFO,"[WEBSERVER] Snapin/WebSockets/Statistics - Client '$client_session_id' disconnected");
# Loop with our clients' subscriber ID's
foreach my $ssid (keys %{$subscribers->{$client_session_id}}) {
# And unsubscribe them
opentrafficshaper::plugins::statistics::unsubscribe($ssid);
# Remove the ssid map
delete($subscriberMap->{$ssid});
}
# Remove the client
delete($subscribers->{$client_session_id});
}
# HTTP to WebSocket
sub graphdata_http2websocket
{
my ($kernel,$globals,$client_session_id,$request,$socket) = @_;
# Grab the query string
# my %queryForm = $request->uri->query_form();
# Check we have a user ID
# if (!defined($queryForm{'uid'})) {
# return (HTTP_BAD_REQUEST,"Request not valid","Request does not contain a 'uid' parameter");
# }
# my $uid = $queryForm{'uid'};
$logger->log(LOG_INFO,"[WEBSERVER] Snapin/WebSockets/Statistics - Accepting upgrade of HTTP to WebSocket");
# Subscribe to the stats for this user
# if (!defined($uSubscriptions->{$uid}) || !(keys %{$uSubscriptions->{$uid}})) {
# $logger->log(LOG_INFO,"[WEBSERVER] Snapin/WebSockets/Statistics - Subscribing to '$uid'");
# $kernel->post('statistics' => 'subscribe' => 'plugin.webserver.websockets.statistics' => 'websocket.send' => $uid);
# }
# Setup tracking of our client & user subscriptions
# $cSubscriptions->{$client_session_id}->{$uid} = $socket;
# $uSubscriptions->{$uid}->{$client_session_id} = $socket;
# And return...
return;
}
# Websocket data handler
sub graphdata_websocket_onrequest
{
my ($kernel,$globals,$client_session_id,$request,$socket) = @_;
my $logger = $globals->{'logger'};
# Parse the command we got...
if ($request->{'function'} eq "subscribe") {
# Grab the first parameter as our tag
my $tag = shift(@{$request->{'args'}});
if (!defined($tag) || ref($tag) ne "") {
return (
opentrafficshaper::plugins::webserver::WS_ERROR,
"The first parameter of 'subscribe' must be a text based tag"
);
}
# Pull off our datasets
my $datasets = { };
foreach my $arg (@{$request->{'args'}}) {
my ($item,$params) = split(/=/,$arg);
push(@{$datasets->{$item}},$params);
}
# Parse dataset data
my @sidList;
if (defined($datasets->{'pool'})) {
# Loop with pool specifications
foreach my $poolSpec (@{$datasets->{'pool'}}) {
# Split interface group id and pool name
my ($rawInterfaceGroupID,$rawPoolName) = split(/:/,$poolSpec);
if (!defined($rawInterfaceGroupID)) {
return (
opentrafficshaper::plugins::webserver::WS_FAIL,
"Datasource for pool has invalid format '$poolSpec'"
);
}
if (!defined($rawPoolName)) {
return (
opentrafficshaper::plugins::webserver::WS_FAIL,
"Datasource for pool has invalid format '$poolSpec'"
);
}
# Check if we can grab the interface group
my $interfaceGroup = getInterfaceGroup($rawInterfaceGroupID);
if (!defined($interfaceGroup)) {
return (
opentrafficshaper::plugins::webserver::WS_FAIL,
"Datasource has invalid interface group '$rawInterfaceGroupID'"
);
}
# Check if the pool name exists
my $pool = getPoolByName($rawInterfaceGroupID,$rawPoolName);
if (!defined($pool)) {
return (
opentrafficshaper::plugins::webserver::WS_FAIL,
"Datasource has invalid pool '$rawPoolName'"
);
}
# Check if we have a stats ID
my $sid = opentrafficshaper::plugins::statistics::getSIDFromPID($pool->{'ID'});
if (!defined($sid)) {
return (
opentrafficshaper::plugins::webserver::WS_FAIL,
"Datasource stats for pool not found '$rawPoolName'"
);
}
# Add SID to SID list that we need to subscribe to
push(@sidList,{'SID' => $sid});
}
}
if (defined($datasets->{'class'})) {
# Loop with class specifications
foreach my $classIDSpec (@{$datasets->{'class'}}) {
# Check we have a tag, interface group ID and class ID
my ($rawInterfaceGroupID,$rawClassID) = split(/:/,$classIDSpec);
if (!defined($rawInterfaceGroupID)) {
return (
opentrafficshaper::plugins::webserver::WS_FAIL,
"Datasource for class has invalid format '$classIDSpec'"
);
}
if (!defined($rawClassID)) {
return (
opentrafficshaper::plugins::webserver::WS_FAIL,
"Datasource for class has invalid format '$classIDSpec'"
);
}
# Get more sane values...
my $interfaceGroup = getInterfaceGroup($rawInterfaceGroupID);
if (!defined($interfaceGroup)) {
return (
opentrafficshaper::plugins::webserver::WS_FAIL,
"Datasource has invalid interface group '$rawInterfaceGroupID'"
);
}
my $classID = isTrafficClassIDValid($rawClassID);
if (!defined($classID)) {
return (
opentrafficshaper::plugins::webserver::WS_FAIL,
"Datasource has invalid class '$rawClassID'"
);
}
# Loop with both directions
foreach my $direction ('Tx','Rx') {
my $interfaceDevice = $interfaceGroup->{"${direction}Interface"};
# Grab stats ID
my $sid = opentrafficshaper::plugins::statistics::getSIDFromCID($interfaceDevice,$classID);
if (!defined($sid)) {
return (
opentrafficshaper::plugins::webserver::WS_FAIL,
"Datasource stats for class '$classID' on interface '$interfaceDevice' not found"
);
}
# Add SID to SID list that we need to subscribe to
push(@sidList,{'SID' => $sid, 'Conversions' => { 'Direction' => lc($direction) }});
}
}
}
if (defined($datasets->{'interface-group'})) {
# Loop with interface-group specifications
foreach my $interfaceGroupSpec (@{$datasets->{'interface-group'}}) {
# Check we have a tag, interface group ID and class ID
my ($rawInterfaceGroupID) = split(/:/,$interfaceGroupSpec);
if (!defined($rawInterfaceGroupID)) {
return (
opentrafficshaper::plugins::webserver::WS_FAIL,
"Datasource for interface group has invalid format '$interfaceGroupSpec'"
);
}
# Get more sane values...
my $interfaceGroup = getInterfaceGroup($rawInterfaceGroupID);
if (!defined($interfaceGroup)) {
return (
opentrafficshaper::plugins::webserver::WS_FAIL,
"Datasource has invalid interface group '$rawInterfaceGroupID'"
);
}
# Loop with both directions
foreach my $direction ('Tx','Rx') {
my $interfaceDevice = $interfaceGroup->{"${direction}Interface"};
# Grab stats ID using a small direction hack
my $sid = opentrafficshaper::plugins::statistics::getSIDFromCID($interfaceDevice,0);
if (!defined($sid)) {
return (
opentrafficshaper::plugins::webserver::WS_FAIL,
"Datasource stats for interface '$interfaceDevice' not found"
);
}
# Add SID to SID list that we need to subscribe to
push(@sidList,{'SID' => $sid, 'Conversions' => { 'Direction' => lc($direction) }});
}
}
}
if (defined($datasets->{'counter'})) {
# Loop with counter specifications
foreach my $counterSpec (@{$datasets->{'counter'}}) {
# Check we have a tag and counter
my ($rawCounter) = split(/:/,$counterSpec);
if (!defined($rawCounter)) {
return (
opentrafficshaper::plugins::webserver::WS_FAIL,
"Datasource for counter has invalid format '$counterSpec'"
);
}
# Grab the SID
my $sid = opentrafficshaper::plugins::statistics::getSIDFromCounter($rawCounter);
if (!defined($sid)) {
return (
opentrafficshaper::plugins::webserver::WS_FAIL,
"Datasource stats for counter not found '$rawCounter'"
);
}
# Add SID to SID list that we need to subscribe to
push(@sidList,{'SID' => $sid, 'Conversions' => { 'Name' => $rawCounter }});
}
}
# No datasources?
if (!@sidList) {
return (
opentrafficshaper::plugins::webserver::WS_FAIL,
"Failed to identify any subscription requests"
);
}
# Loop wiht subscription list
foreach my $item (@sidList) {
my $ssid = opentrafficshaper::plugins::statistics::subscribe(
$item->{'SID'},
$item->{'Conversions'},
'plugin.webserver.websockets.statistics',
'websocket.send'
);
# Save this client and the streaming id (ssid) we got back
$subscriberMap->{$ssid} = $subscribers->{$client_session_id}->{$ssid} = {
'Tag' => $tag,
'SID' => $item->{'SID'},
'Socket' => $socket
};
}
return (opentrafficshaper::plugins::webserver::WS_OK,"Subscribed");
# No command at all
} else {
return (opentrafficshaper::plugins::webserver::WS_ERROR,"Function '$request->{'function'}' does not exist");
}
return (opentrafficshaper::plugins::webserver::WS_OK,"Function not found");
}
#
# Internal functions
#
# Return a json dataset
sub _json_data
{
my $data = shift;
# Build the structure we're going to encode
my $res;
$res->{'status'} = "success";
$res->{'data'} = $data;
# Use ID of 0 to signify out of band data
$res->{'id'} = -1;
return encode_json($res);
}
1;
# vim: ts=4
# OpenTrafficShaper webserver module
# Copyright (C) 2007-2013, AllWorldIT
#
# Copyright (C) 2007-2023, AllWorldIT
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
......@@ -21,17 +21,25 @@ package opentrafficshaper::plugins::webserver;
use strict;
use warnings;
use bytes;
use HTML::Entities;
use HTTP::Response;
use HTTP::Status qw(:constants :is status_message);
use POE qw(Component::Server::TCP Filter::HTTPD);
use HTTP::Status qw( :constants :is );
use JSON;
use POE qw( Component::Server::TCP );
use URI;
use opentrafficshaper::logger;
use opentrafficshaper::plugins;
use opentrafficshaper::POE::Filter::HybridHTTP;
# Pages (this is used a little below)
use opentrafficshaper::plugins::webserver::pages::static;
use opentrafficshaper::plugins::webserver::pages::index;
use opentrafficshaper::plugins::webserver::pages::users;
use opentrafficshaper::plugins::webserver::pages::limits;
use opentrafficshaper::plugins::webserver::pages::configmanager;
# Exporter stuff
......@@ -41,10 +49,16 @@ our (@ISA,@EXPORT,@EXPORT_OK);
@EXPORT = qw(
);
@EXPORT_OK = qw(
snapin_register
);
use constant {
VERSION => '0.0.1'
VERSION => '1.0.0',
# WebSocket return status
WS_OK => 0,
WS_ERROR => -1,
WS_FAIL => -2
};
......@@ -52,120 +66,253 @@ use constant {
our $pluginInfo = {
Name => "Webserver",
Version => VERSION,
Init => \&init,
Init => \&plugin_init,
Start => \&plugin_start,
};
# Copy of system globals
# Our globals
my $globals;
# Copy of system logger
my $logger;
# This is the mapping of our pages
my $pages = {
'index' => {
'_catchall' => \&opentrafficshaper::plugins::webserver::pages::index::_catchall,
},
'static' => {
'_catchall' => \&opentrafficshaper::plugins::webserver::pages::static::_catchall,
},
'users' => {
'default' => \&opentrafficshaper::plugins::webserver::pages::users::default,
# Client connections open
#
# $globals->{'Connections'}
# Web resources
my $resources = {
# HTTP
'HTTP' => {
'index' => {
'_catchall' => \&opentrafficshaper::plugins::webserver::pages::index::_catchall,
},
'static' => {
'_catchall' => \&opentrafficshaper::plugins::webserver::pages::static::_catchall,
},
'limits' => {
'default' => \&opentrafficshaper::plugins::webserver::pages::limits::pool_list,
'pool-list' => \&opentrafficshaper::plugins::webserver::pages::limits::pool_list,
'pool-override-add' => \&opentrafficshaper::plugins::webserver::pages::limits::pool_override_addedit,
'pool-override-list' => \&opentrafficshaper::plugins::webserver::pages::limits::pool_override_list,
'pool-override-remove' => \&opentrafficshaper::plugins::webserver::pages::limits::pool_override_remove,
'pool-override-edit' => \&opentrafficshaper::plugins::webserver::pages::limits::pool_override_addedit,
'pool-add' => \&opentrafficshaper::plugins::webserver::pages::limits::pool_addedit,
'pool-list' => \&opentrafficshaper::plugins::webserver::pages::limits::pool_list,
'pool-remove' => \&opentrafficshaper::plugins::webserver::pages::limits::pool_remove,
'pool-edit' => \&opentrafficshaper::plugins::webserver::pages::limits::pool_addedit,
'poolmember-add' => \&opentrafficshaper::plugins::webserver::pages::limits::poolmember_addedit,
'poolmember-list' => \&opentrafficshaper::plugins::webserver::pages::limits::poolmember_list,
'poolmember-remove' => \&opentrafficshaper::plugins::webserver::pages::limits::poolmember_remove,
'poolmember-edit' => \&opentrafficshaper::plugins::webserver::pages::limits::poolmember_addedit,
'limit-add' => \&opentrafficshaper::plugins::webserver::pages::limits::limit_add
},
'configmanager' => {
'default' => \&opentrafficshaper::plugins::webserver::pages::configmanager::default,
'admin-config' => \&opentrafficshaper::plugins::webserver::pages::configmanager::admin_config,
},
},
};
# Add webserver snapin
sub snapin_register
{
my ($protocol,$module,$action,$data) = @_;
$logger->log(LOG_INFO,"[WEBSERVER] Registered snapin: protocol = %s, module = %s, action = %s",$protocol,$module,$action);
# Load resource
$resources->{$protocol}->{$module}->{$action} = $data;
}
# Initialize plugin
sub init
sub plugin_init
{
$globals = shift;
my $system = shift;
# Setup our environment
$logger = $globals->{'logger'};
$logger = $system->{'logger'};
$logger->log(LOG_NOTICE,"[WEBSERVER] OpenTrafficShaper Webserver Module v%s - Copyright (c) 2007-2023, AllWorldIT",VERSION);
# Initialize
$globals->{'Connections'} = { };
# Spawn a web server on port 8088 of all interfaces.
POE::Component::Server::TCP->new(
Alias => "plugin.webserver",
Port => 8088,
ClientFilter => 'POE::Filter::HTTPD',
Port => 8080,
# Handle session connections & disconnections
ClientConnected => \&server_client_connected,
ClientDisconnected => \&server_client_disconnected,
# Filter to handle HTTP
ClientFilter => 'opentrafficshaper::POE::Filter::HybridHTTP',
# Function to handle HTTP requests (as we passing through a filter)
ClientInput => \&handle_request
ClientInput => \&server_request,
# Setup the sever
Started => \&server_session_start,
Stopped => \&server_session_stop,
);
$logger->log(LOG_NOTICE,"[WEBSERVER] OpenTrafficShaper Webserver Module v".VERSION." - Copyright (c) 2013, AllWorldIT")
# Load statistics pages if the statistics module is enabled
if (isPluginLoaded('statistics')) {
# Check if we can actually load the pages
eval("use opentrafficshaper::plugins::webserver::pages::statistics");
if ($@) {
$logger->log(LOG_INFO,"[WEBSERVER] Failed to load statistics pages: %s",$@);
exit;
} else {
# Load resources
$resources->{'HTTP'}->{'statistics'} = {
'by-pool' => \&opentrafficshaper::plugins::webserver::pages::statistics::byPool,
'by-class' => \&opentrafficshaper::plugins::webserver::pages::statistics::byClass,
'jsondata' => \&opentrafficshaper::plugins::webserver::pages::statistics::jsondata,
'dashboard' => \&opentrafficshaper::plugins::webserver::pages::statistics::dashboard,
'igdashboard' => \&opentrafficshaper::plugins::webserver::pages::statistics::igdashboard
};
$logger->log(LOG_INFO,"[WEBSERVER] Loaded statistics pages as statistics module is loaded");
}
}
return 1;
}
sub handle_request
# Start the plugin
sub plugin_start
{
my ($kernel, $heap, $request) = @_[KERNEL, HEAP, ARG0];
$logger->log(LOG_INFO,"[WEBSERVER] Started");
}
# We going to init these as system so we know whats a parsing issue
my $module = "system";
my $action = "parse";
# This is our response
my $response;
# We may have a response from the filter indicating an error
if ($request->isa("HTTP::Response")) {
$heap->{client}->put($request);
goto END;
}
# Server session started
sub server_session_start
{
my $kernel = $_[KERNEL];
# Split off the URL into a module and action
my (undef,$dmodule,$daction,@dparams) = split(/\//,$request->uri);
# If any is blank, set it to the default
$dmodule = "index" if (!defined($dmodule));
$daction = "default" if (!defined($daction));
# Sanitize
($module = $dmodule) =~ s/[^A-Za-z0-9]//g;
($action = $daction) =~ s/[^A-Za-z0-9]//g;
# If module name is sneaky? then just block it
if ($module ne $dmodule) {
$response = httpDisplayFault(HTTP_FORBIDDEN,"Method Not Allowed","The requested resource '".encode_entities($module)."' is not allowed.");
goto END;
$logger->log(LOG_DEBUG,"[WEBSERVER] Initialized");
}
# Server session stopped
sub server_session_stop
{
my $kernel = $_[KERNEL];
# Tear down data
$globals = undef;
$logger->log(LOG_DEBUG,"[WEBSERVER] Shutdown");
}
# Signal that the client has connected
sub server_client_connected
{
my ($kernel,$heap,$session,$request) = @_[KERNEL, HEAP, SESSION, ARG0];
my $client_session_id = $session->ID;
# Save our socket on the client
$globals->{'Connections'}->{$client_session_id}->{'Socket'} = $heap->{'client'};
$globals->{'Connections'}->{$client_session_id}->{'Protocol'} = 'HTTP';
}
# Signal that the client has disconnected
sub server_client_disconnected
{
my ($kernel,$heap,$session,$request) = @_[KERNEL, HEAP, SESSION, ARG0];
my $client_session_id = $session->ID;
my $client = $globals->{'Connections'}->{$client_session_id};
# Check if we have a disconnection function to call
if (
defined($client->{'Resource'}) &&
defined($client->{'Resource'}->{'Handler'}) &&
ref($client->{'Resource'}->{'Handler'}) eq 'HASH' &&
defined($client->{'Resource'}->{'Handler'}->{'on_disconnect'})
) {
# Call disconnection function
$client->{'Resource'}->{'Handler'}->{'on_disconnect'}
->($kernel,$globals,$client_session_id);
}
# This is the function we going to call
# If we have a function name, try use it
if (defined($pages->{$module})) {
# If there is no specific action for this use the catchall
if (!defined($pages->{$module}->{$action}) && defined($pages->{$module}->{'_catchall'})) {
$action = "_catchall";
# Remove client session
delete($globals->{'Connections'}->{$client_session_id});
}
# Handle the HTTP request
sub server_request
{
my ($kernel,$heap,$session,$request) = @_[KERNEL, HEAP, SESSION, ARG0];
my $client_session_id = $session->ID;
my $client = $globals->{'Connections'}->{$client_session_id};
# Our response back if one, and if we should just close the connection or not
my $response;
my $closeWhenDone = 0;
# Its HTTP
if ($client->{'Protocol'} eq "HTTP") {
# Check the protocol we're currently handling
# We may have a response from the filter indicating an error
if (ref($request) eq "HTTP::Response") {
$response = $request;
# Its a normal HTTP request
} elsif (ref($request) eq "HTTP::Request") {
$response = _server_request_http($kernel,$client_session_id,$request);
}
# Check if it exists first
if (defined($pages->{$module}->{$action})) {
my ($res,$content,$extra) = $pages->{$module}->{$action}->($globals,$module,$daction,$request);
# Module return undef if they don't want to handle the request
if (!defined($res)) {
$response = httpDisplayFault(HTTP_NOT_FOUND,"Resource Not found","The requested resource '".encode_entities($daction)."' cannot be found");
} elsif (ref($res) eq "HTTP::Response") {
$response = $res;
# TODO: This is a bit dirty
} elsif ($res == HTTP_OK) {
$response = httpCreateResponse($content);
# TODO - redirect?
} else {
httpDisplayFault($res,$content,$extra);
}
} else {
$response = httpDisplayFault(HTTP_NOT_FOUND,"Method Not found","The requested method '".encode_entities($action)."' cannot be found in '".encode_entities($module)."'");
# Support for HTTP/1.1 "Connection: close" header...
my $connection = $request->header('Connection');
if (defined($connection) && $connection eq "close") {
$closeWhenDone = 1;
}
}
if (!defined($response)) {
$response = httpDisplayFault(HTTP_NOT_FOUND,"Resource Not found","The requested resource '".encode_entities($module)."' cannot be found");
# Its a websocket
} elsif ($client->{'Protocol'} eq "WebSocket") {
$response = _server_request_websocket($kernel,$client_session_id,$request);
}
# If there is a response send it
if (defined($response)) {
$client->{'Socket'}->put($response);
}
END:
$logger->log(LOG_INFO,"[WEBSERVER] Access: ".$response->code." [$module/$action] - ".$request->method." ".$request->uri." ".$request->protocol);
$heap->{client}->put($response);
$kernel->yield("shutdown");
# Check if connection must be closed
if ($closeWhenDone) {
$kernel->yield("shutdown");
}
}
......@@ -173,30 +320,40 @@ END:
# Display fault
sub httpDisplayFault
{
my ($code,$msg,$description) = @_;
my ($code,$message,$description) = @_;
# Throw out message to client to authenticate first
my $headers = HTTP::Headers->new;
$headers->content_type("text/html");
my $resp = HTTP::Response->new(
$code,$msg,
my $response = HTTP::Response->new(
$code,$message,
$headers,
<<EOF);
<!DOCTYPE html>
<html>
<head>
<title>$code $msg</title>
<title>$code $message</title>
</head>
<body>
<h1>$msg</h1>
<h1>$message</h1>
<p>$description</p>
</body>
</html>
EOF
return $resp;
return $response;
}
# Do a redirect
sub httpRedirect
{
my $url = shift;
return HTTP::Response->new(HTTP_FOUND, 'Redirect', [Location => $url]);
}
......@@ -204,102 +361,532 @@ EOF
# Create a response object
sub httpCreateResponse
{
my ($msg) = @_;
my ($module,$content,$options) = @_;
# Throw out message to client to authenticate first
my $headers = HTTP::Headers->new;
$headers->content_type("text/html");
my $payload = "";
my $resp = HTTP::Response->new(
HTTP_OK,"Ok",
$headers,
<<EOF);
# Check if we have a specific return type, if not set default
if (!defined($options->{'type'})) {
$options->{'type'} = 'webpage';
}
# If we returning a webpage, handle it that way
if ($options->{'type'} eq 'webpage') {
# Set header
$headers->content_type("text/html");
# Bootstrap stuff
my $mainCols = 12;
# Check if we have a menu structure, if we do, display the sidebar
my $menuStr = "";
my $stylesheetsStr = "";
my $stylesheetStr = "";
my $javascriptStr = "";
my $versionStr = VERSION;
if (defined($options)) {
# Check if menu exists
if (my $menu = $options->{'menu'}) {
$menuStr =<<EOF;
<div class="col-md-2">
<ul class="nav nav-pills nav-stacked">
EOF
# Loop with sub menu sections
my @menuItems = ();
foreach my $section (@{$menu}) {
my $menuItem = "";
my $sectionName = encode_entities($section->{'name'});
$menuItem .=<<EOF;
<li class="active text-center"><a href="#">$sectionName</a></li>
EOF
# Loop with menu items
foreach my $item (@{$section->{'items'}}) {
my $itemName = encode_entities($item->{'name'});
# Sanitize link
my $itemLink = "/" . $module . "/" . $item->{'link'};
$itemLink =~ s,/+$,,;
# Build sections
$menuItem .=<<EOF;
<li class="text-center"><a href="$itemLink">$itemName</a></li>
EOF
}
# Add menu item
push(@menuItems,$menuItem);
}
# Join menu items
$menuStr .= join(<<EOF,@menuItems);
<hr />
EOF
$menuStr .=<<EOF;
</ul>
</div>
EOF
# Reduce number of main cols to make way for menu
$mainCols = 10;
}
# Check if we have a list of style assets
if (defined(my $stylesheets = $options->{'stylesheets'})) {
foreach my $script (@{$stylesheets}) {
$stylesheetsStr .=<<EOF;
<link href="$script" rel="stylesheet"></script>
EOF
}
}
# Check if stylesheet snippet exists
if (defined(my $stylesheet = $options->{'stylesheet'})) {
$stylesheetStr .=<<EOF;
$stylesheet
EOF
}
# Check if we have a list of javascript assets
if (defined(my $javascripts = $options->{'javascripts'})) {
foreach my $script (@{$javascripts}) {
$javascriptStr .=<<EOF;
<script type="text/javascript" src="$script"></script>
EOF
}
}
# Check if javascript snippet exists
if (defined(my $javascript = $options->{'javascript'})) {
$javascriptStr .=<<EOF;
<script type="text/javascript">
$javascript
</script>
EOF
}
}
# Create the payload we returning
$payload = <<EOF;
<!DOCTYPE html>
<html>
<head>
<title>OpenTrafficShaper - Enterprise Traffic Shaper</title>
<!-- Meta -->
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Assets -->
<link href="/static/jquery-ui/css/ui-lightness/jquery-ui.min.css" rel="stylesheet" media="screen">
<link href="/static/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen">
<link href="/static/favicon.ico" rel="icon" />
<link href="/static/jquery-ui/css/ui-lightness/jquery-ui.min.css" rel="stylesheet" />
<link href="/static/bootstrap/css/bootstrap.min.css" rel="stylesheet" />
$stylesheetsStr
<style type="text/css">
body {
padding-top: 60px;
padding-bottom: 40px;
}
.sidebar-nav {
padding: 9px 0;
}
\@media (max-width: 980px) {
/* Enable use of floated navbar text */
.navbar-text.pull-right {
float: none;
padding-left: 5px;
padding-right: 5px;
}
padding-top: 50px;
}
$stylesheetStr
</style>
<!-- End Assets -->
<link href="/static/bootstrap/css/bootstrap-responsive.min.css" rel="stylesheet" media="screen">
</head>
<body>
<body>
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-inner">
<div class="container-fluid">
<button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<a class="navbar-brand" href="/">
<img src="/static/logo-inverted-short.png" alt="Open Traffic Shaper" width="100%" height="auto" />
</a>
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="brand" href="/">OpenTrafficShaper</a>
<div class="nav-collapse collapse">
<p class="navbar-text pull-right">Logged in as <a href="#" class="navbar-link">Username</a> </p>
<ul class="nav">
<li class="active"><a href="#">Home</a></li>
<li><a href="/users">Users</a></li>
</ul>
</div><!--/.nav-collapse -->
</div>
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li class="active"><a href="/">Home</a></li>
<li><a href="/limits">Limits</a></li>
<li><a href="/configmanager">ConfigManager</a></li>
</ul>
</div>
</div>
</div>
<div class="container-fluid">
<div class="row-fluid">
<div class="span1">
<div class="well sidebar-nav">
<ul class="nav nav-list">
<li class="nav-header">Sidebar</li>
<li class="active"><a href="#">Link</a></li>
<li><a href="#">Link</a></li>
<li class="nav-header">Sidebar</li>
<li><a href="#">Link</a></li>
</ul>
</div><!--/.well -->
</div><!--/span-->
<div class="span10">
$msg
</div><!--/span-->
</div><!--/row-->
<hr>
<div style="padding: 15px 15px">
<div class="row">
$menuStr
<div class="col-md-$mainCols">
$content
</div>
</div>
</div>
<div style="padding: 0 15px">
<hr />
<footer>
<p>v$globals->{'version'} - Copyright &copy; 2013, <a href="http://www.allworldit.com">AllWorldIT</a></p>
<p class="muted">
v$versionStr - Copyright &copy; 2007-2023, <a href="http://www.allworldit.com">AllWorldIT</a>
</p>
</footer>
</div><!--/.fluid-container-->
</div>
</body>
<script type="text/javascript" src="/static/jquery/js/jquery.min.js"></script>
<script type="text/javascript" src="/static/jquery-ui/js/jquery-ui.min.js"></script>
<script type="text/javascript" src="/static/bootstrap/js/bootstrap.min.js"></script>
$javascriptStr
<!-- Javascript -->
<script src="/static/jquery/js/jquery.min.js"></script>
<script src="/static/jquery-ui/js/jquery-ui.min.js"></script>
<script src="/static/bootstrap/js/bootstrap.min.js"></script>
</body>
</html>
EOF
# Maybe we're json?
} elsif ($options->{'type'} eq 'json') {
# Set header
$headers->content_type("application/json");
$payload = encode_json($content);
}
# Build action response
my $resp = HTTP::Response->new(
HTTP_OK,"Ok",
$headers,
$payload
);
return $resp;
}
#
# Internals
#
# Handle the normal HTTP protocol
sub _server_request_http
{
my ($kernel,$client_session_id,$request) = @_;
my $conn = $globals->{'Connections'}->{$client_session_id};
my $protocol = "HTTP"; # By default we're HTTP
# Pull off connection attributes
my $header_connection;
if (my $h_connection = $request->header('Connection')) {
foreach my $param (split(/[ ,]+/,$h_connection)) {
$header_connection->{lc($param)} = 1;
}
# Identify and split off upgrades
if (defined($header_connection->{'upgrade'}) && (my $h_upgrade = $request->header('upgrade'))) {
$h_upgrade = lc($h_upgrade);
if ($h_upgrade eq "websocket") {
$protocol = "HTTP=>WebSocket";
}
}
}
# XXX: Check method & encoding, in all protocols??
if ($request->method eq "POST") {
# We currently only accept form data
if ($request->content_type ne "application/x-www-form-urlencoded") {
return httpDisplayFault(HTTP_FORBIDDEN,"Method Not Allowed","The requested method and content type is not allowed.");
}
}
# This is going to be our response back
my $response;
# Parse our protocol into a module & action
my ($handler,$module,$action) = _parse_http_resource($request,$protocol);
# Short circuit if we had an error
if (ref($handler) eq "HTTP::Response") {
# There is no module or action
$module = ""; $action = "";
# Set our response
$response = $handler;
goto END;
}
# Function we need to call
my $function = $handler;
# Check if we're a hash... override if we are
if (ref($handler) eq "HASH") {
$function = $handler->{'on_request'};
}
# If its something else, blow up
if (ref($function) ne "CODE") {
return httpDisplayFault(HTTP_INTERNAL_SERVER_ERROR,"Internal server error","Server configuration error");
}
$logger->log(LOG_DEBUG,"[WEBSERVER] Parsed HTTP request into: module='%s', action='%s'",$module,$action);
# Save what resource we just accessed
$globals->{'Connections'}->{$client_session_id}->{'Resource'} = {
'Module' => $module,
'Action' => $action,
'Handler' => $handler,
};
my $system = {
'logger' => $logger
};
# This is normal HTTP request
if ($protocol eq "HTTP") {
# Do the function call now
my ($res,$content,$extra) = $function->($kernel,$system,$client_session_id,$request);
# Module return undef if they don't want to handle the request
if (!defined($res)) {
$response = httpDisplayFault(HTTP_NOT_FOUND,"Resource Not found","The requested resource '$action' cannot be found");
} elsif (ref($res) eq "HTTP::Response") {
$response = $res;
# TODO: This is a bit dirty
# Extra in this case is the sidebar menu items
} elsif ($res == HTTP_OK) {
$response = httpCreateResponse($module,$content,$extra);
# The content in a redirect is the URL
} elsif ($res == HTTP_TEMPORARY_REDIRECT) {
$response = httpRedirect("//".$request->header('host')."/" . $content);
# Extra in this case is the error description
} else {
$response = httpDisplayFault($res,$content,$extra);
}
# Its a websocket upgrade request
} elsif ($protocol eq "HTTP=>WebSocket") {
# Make sure we have an upgrade path to WebSocket
my ($newHandler) = _parse_http_resource($request,"WebSocket");
if (!defined($newHandler)) {
return httpDisplayFault(HTTP_INTERNAL_SERVER_ERROR,"Internal server error","Cannot upgrade to websocket");
}
# Do the function call now
my ($res,$ret1,$ret2) = $function->($kernel,$globals,$client_session_id,$request,$conn->{'Socket'});
# If we have a response defined, we rejected the upgrade
if (defined($res)) {
$response = httpDisplayFault($res,$ret1,$ret2);
} else {
# Return our upgrade response
$response = _server_request_http_wsupgrade($request,$module,$action);
# Upgrade the protocol & handler
$globals->{'Connections'}->{$client_session_id}->{'Protocol'} = 'WebSocket';
$globals->{'Connections'}->{$client_session_id}->{'Resource'}->{'Handler'} = $newHandler;
}
}
END:
$logger->log(LOG_INFO,"[WEBSERVER] %s Request: %s [%s/%s] - %s %s %s",
$protocol,
$response->code,
$module,
$action,
encode_entities($request->method),
encode_entities($request->uri),
encode_entities($request->protocol)
);
return $response;
}
# Handle the websocket request
sub _server_request_websocket
{
my ($kernel,$client_session_id,$rawRequest) = @_;
my $conn = $globals->{'Connections'}->{$client_session_id};
my $handler = $conn->{'Resource'}->{'Handler'};
# Function we need to call
my $function = $handler;
# Check if we're a hash... override if we are
if (ref($handler) eq "HASH") {
$function = $handler->{'on_request'};
}
# If its something else, blow up
if (ref($function) ne "CODE") {
$logger->log(LOG_ERR,"[WEBSERVER] No 'on_request' handler for websocket");
return encode_json({'status' => "error", 'message' => "Internal server error"});
}
# Safely decode JSON
my $request;
eval { $request = decode_json($rawRequest); };
if ($@) {
# FIXME: NOTICE: Failed to decode JSON request '&#3;&#xFFFD; , may be disconnect from websocket?
$logger->log(LOG_NOTICE,"[WEBSERVER] Failed to decode JSON request '%s'",encode_entities($rawRequest));
return encode_json({'status' => "error", 'message' => "Failed to decode JSON"});
}
# Save the command id
my $rCode = $request->{'id'};
# Check the function and args are present
if (!defined($request->{'function'}) || !defined($request->{'args'})) {
return encode_json({
'status' => "error",
'message' => "Invalid format for JSON request, must have 'function' and 'args' properties"
});
}
# Check function is a string
if (!(ref($request->{'function'}) eq "" && length($request->{'function'}) > 0)) {
return encode_json({
'status' => "error",
'message' => "The 'function' property must contain a function name"
});
}
# And the args is an array ref
if (!ref($request->{'args'}) eq "ARRAY") {
return encode_json({
'status' => "error",
'message' => "The 'args' property must contain an array of arguments"
});
}
# Do the function call now
my ($res,$content,$extra) = $function->($kernel,$globals,$client_session_id,$request,$conn->{'Socket'});
$logger->log(LOG_INFO,"[WEBSERVER] %s Request [%s/%s]",
$conn->{'Protocol'},
$conn->{'Resource'}->{'Module'},
$conn->{'Resource'}->{'Action'},
);
# Check if its a success or failure
my $ret;
if ($res == WS_OK) {
$ret->{'status'} = "success";
$ret->{'data'} = $content;
} elsif ($res == WS_ERROR) {
$ret->{'status'} = "error";
$ret->{'message'} = $content;
} elsif ($res == WS_FAIL) {
$ret->{'status'} = "fail";
$ret->{'message'} = $content;
} else {
$ret->{'status'} = "error";
$ret->{'message'} = "Internal server error";
}
# Add ID if we have one
if (defined($request->{'id'})) {
$ret->{'id'} = $request->{'id'};
}
return encode_json($ret);
}
# Function to parse a HTTP resource
sub _parse_http_resource
{
my ($request,$protocol) = @_;
my $resource = $resources->{$protocol};
# No resource defined
if (!defined($resource)) {
return httpDisplayFault(HTTP_FORBIDDEN,"Protocol Not Available","The requested protocol is not available.");
}
# Split off the URL into a module and action
my (undef,$dmodule,$daction) = $request->uri->path_segments();
# If any is blank, set it to the default
$dmodule = "index" if (!defined($dmodule) || $dmodule eq "");
$daction = "default" if (!defined($daction) || $daction eq "");
# Sanitize
(my $module = $dmodule) =~ s/[^A-Za-z0-9]//g;
(my $action = $daction) =~ s/[^A-Za-z0-9\-]//g;
# If module name is sneaky? then just block it
if ($module ne $dmodule) {
return httpDisplayFault(HTTP_FORBIDDEN,"Method Not Allowed","The requested resource '$module' is not allowed.");
}
# If there is no resource to handle this return
if (!defined($resource->{$module})) {
return httpDisplayFault(HTTP_NOT_FOUND,"Resource Not found","The requested resource '$module' cannot be found");
}
# If there is no specific action for this use the catchall
if (!defined($resource->{$module}->{$action}) && defined($resource->{$module}->{'_catchall'})) {
$action = "_catchall";
}
# Check if it exists first
if (!defined($resource->{$module}->{$action})) {
return httpDisplayFault(HTTP_NOT_FOUND,"Method Not found","The requested method '$action' cannot be found in '$module'");
}
# This is the handler data, either a CODE ref or a HASH
my $handler = $resource->{$module}->{$action};
# Check if the destination is a hash containing stuff we can treat specially
if (ref($handler) eq "HASH") {
# If we have a list of requires, check them
if (defined($handler->{'requires'})) {
foreach my $require (@{$handler->{'requires'}}) {
if (!isPluginLoaded($require)) {
return httpDisplayFault(HTTP_NOT_IMPLEMENTED,"Method Not Available","The requested method '$action' in ".
"'$module' is not currently available");
}
}
}
}
return ($handler,$module,$action);
}
# Handle Websocket
sub _server_request_http_wsupgrade
{
my ($request,$module,$action) = @_;
my $response;
# Build handshake reply
my $headers = HTTP::Headers->new(
'Upgrade' => "websocket",
'Connection' => "upgrade",
);
# Build response switching protocols
$response = HTTP::Response->new(
HTTP_SWITCHING_PROTOCOLS,"Switching Protocols",
$headers
);
$logger->log(LOG_INFO,"[WEBSERVER] WebSocket Upgrade: %s [%s/%s] - %s %s %s",
$response->code,
$module,
$action,
encode_entities($request->method),
encode_entities($request->uri),
encode_entities($request->protocol)
);
return $response;
}
1;
# vim: ts=4
# OpenTrafficShaper webserver module: users page
# Copyright (C) 2007-2013, AllWorldIT
#
# OpenTrafficShaper utility functions
# Copyright (C) 2007-2023, AllWorldIT
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
......@@ -14,7 +14,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
package opentrafficshaper::plugins::webserver::pages::users;
package opentrafficshaper::util;
use strict;
use warnings;
......@@ -25,80 +26,56 @@ require Exporter;
our (@ISA,@EXPORT,@EXPORT_OK);
@ISA = qw(Exporter);
@EXPORT = qw(
isIPv46
isIPv46CIDR
);
@EXPORT_OK = qw(
);
sub default
# Check if this is a valid IPv4 or IPv6 address
sub isIPv46
{
my ($globals,$module,$daction,$request) = @_;
# If we not passed default by the main app, just return
return if ($daction ne "default");
# Build content
my $content = "";
# Header
$content .=<<EOF;
<table class="table">
<caption>User List</caption>
<thead>
<tr>
<th>#</th>
<th>User</th>
<th>IP</th>
<th>Group</th>
<th>Class</th>
<th>Limits</th>
</tr>
</thead>
<tbody>
EOF
# Body
foreach my $userid (keys %{$globals->{'users'}}) {
my $user = $globals->{'users'}->{$userid};
# Make style a bit pretty
my $style = "";
if ($user->{'Status'} eq "offline") {
$style = "warning";
} elsif ($user->{'Status'} eq "new") {
$style = "info";
}
my ($rawIP) = @_;
$content .=<<EOF;
<tr class="$style">
<td>X</td>
<td>$user->{'Username'}</td>
<td>$user->{'IP'}</td>
<td>$user->{'GroupName'}</td>
<td>$user->{'ClassName'}</td>
<td>$user->{'Limits'}</td>
</tr>
EOF
my $ip = NetAddr::IP->new($rawIP);
if (!defined($ip)) {
return;
}
# No results
if (keys %{$globals->{'users'}} < 1) {
$content .=<<EOF;
<tr class="info">
<td colspan="6"><p class="text-center">No Results</p></td>
</tr>
EOF
if ($ip->version == 4) {
if ($ip->masklen != 32) {
return;
}
return $ip->addr;
} elsif ($ip->version == 6) {
if ($ip->masklen != 128) {
return;
}
return $ip->short;
}
return;
}
# Footer
$content .=<<EOF;
</tbody>
</table>
EOF
return (200,$content);
# Check if this is a valid IPv4 or IPv6 CIDR
sub isIPv46CIDR
{
my ($rawIP) = @_;
my $ip = NetAddr::IP->new($rawIP);
if (!defined($ip)) {
return;
}
if ($ip->version() == 4) {
return $ip->network()->addr() . "/" . $ip->masklen();
} elsif ($ip->version == 6) {
return $ip->network()->short() . "/" . $ip->masklen();
}
return;
}
1;
1;
\ No newline at end of file
# OpenTrafficShaper version package
# Copyright (C) 2007-2013, AllWorldIT
#
# Copyright (C) 2007-2023, AllWorldIT
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
......@@ -30,7 +30,7 @@ our (@ISA,@EXPORT,@EXPORT_OK);
use constant {
VERSION => "1.0.0",
VERSION => "2.0.4",
};
......
#!/usr/bin/perl
# Main OpenTrafficShaper program
# Copyright (C) 2007-2013, AllWorldIT
#
# Copyright (C) 2007-2023, AllWorldIT
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
......@@ -18,22 +18,28 @@
use strict;
use warnings;
use File::Basename qw(dirname);
# Set the dirs we look for library files in
use lib('/usr/local/lib/opentrafficshaper-1.0','/usr/lib/opentrafficshaper-1.0',
'/usr/lib64/opentrafficshaper-1.0','opentrafficshaper','awitpt');
use lib(dirname(__FILE__),dirname(__FILE__)."/awitpt/lib");
# Enable assertions
BEGIN {
package POE::Kernel;
use constant ASSERT_DEFAULT => 1;
}
# System stuff we need
use Config::IniFiles;
use Getopt::Long;
use POE;
use POSIX qw(setsid);
use Time::HiRes qw(time);
# Our own stuff
use opentrafficshaper::version;
use opentrafficshaper::logger;
use Radius::Dictionary;
use Radius::Packet;
use opentrafficshaper::plugins qw( plugin_register );
# Main config
......@@ -45,17 +51,34 @@ my $logger = new opentrafficshaper::logger;
#
# MAIN
#
$logger->log(LOG_NOTICE,"[MAIN] OpenTrafficShaper v".VERSION." - Copyright (c) 2007-2013, AllWorldIT");
parseCfgCmdLine();
init();
$logger->log(LOG_NOTICE,"[MAIN] Starting...");
# Check if we must use a log file instead
if (defined($globals->{'config'}->{'log_file'})) {
$logger->open($globals->{'config'}->{'log_file'});
}
$logger->setLevel($globals->{'config'}->{'log_level'});
# Check if we need to go background
if (defined($globals->{'config'}->{'background'})) {
daemonize();
}
displayBanner();
init();
start();
$logger->log(LOG_NOTICE,"[MAIN] Entering RUNNING state");
POE::Kernel->run();
exit;
# Function to display banner
sub displayBanner
{
$logger->log(LOG_NOTICE,"[MAIN] OpenTrafficShaper v%s - Copyright (c) 2007-2023, AllWorldIT",VERSION);
}
# Function to parse our config and commandline
sub parseCfgCmdLine
......@@ -65,16 +88,11 @@ sub parseCfgCmdLine
my $cfg;
$cfg->{'config_file'} = "/etc/opentrafficshaper.conf";
$cfg->{'timeout'} = 120;
$cfg->{'background'} = "yes";
$cfg->{'pid_file'} = "/var/run/opentrafficshaper/opentrafficshaperd.pid";
$cfg->{'pid_file'} = "/run/opentrafficshaper/opentrafficshaperd.pid";
$cfg->{'log_level'} = 2;
$cfg->{'log_file'} = "/var/log/opentrafficshaper/opentrafficshaperd.log";
# $server->{'host'} = "*";
# $server->{'port'} = [ 1812, 1813 ];
# $server->{'proto'} = 'udp';
# Parse command line params
my $cmdline;
%{$cmdline} = ();
......@@ -97,7 +115,7 @@ sub parseCfgCmdLine
# Check config file exists
if (! -f $cfg->{'config_file'}) {
die("No configuration file '".$cfg->{'config_file'}."' found!\n");
die("No configuration file '".$cfg->{'config_file'}."' found!");
}
# Use config file, ignore case
......@@ -105,20 +123,18 @@ sub parseCfgCmdLine
-file => $cfg->{'config_file'},
-nocase => 1
) or die "Failed to open config file '".$cfg->{'config_file'}."': $!";
my $inifileHandle = tied( %inifile );
# Copy config
my %config = %inifile;
# Pull in params for the server
my @server_params = (
'log_level','log_file',
'host',
'pid_file',
'user', 'group',
'timeout',
'background',
);
foreach my $param (@server_params) {
$cfg->{$param} = $config{'server'}{$param} if (defined($config{'server'}{$param}));
$cfg->{$param} = $config{'system'}{$param} if (defined($config{'system'}{$param}));
}
# Override
......@@ -128,19 +144,10 @@ sub parseCfgCmdLine
}
# If we set on commandline for foreground, keep in foreground
if ($cmdline->{'fg'} || (defined($config{'server'}{'background'}) && $config{'server'}{'background'} eq "no" )) {
if ($cmdline->{'fg'} || (defined($config{'system'}{'background'}) && $config{'system'}{'background'} eq "no" )) {
$cfg->{'background'} = undef;
$cfg->{'log_file'} = undef;
} else {
$cfg->{'setsid'} = 1;
}
# Loop with logging detail
if (defined($config{'server'}{'log_detail'})) {
# Lets see what we have to enable
foreach my $detail (split(/[,\s;]/,$config{'server'}{'log_detail'})) {
$cfg->{'logging'}{$detail} = 1;
}
$cfg->{'pid_file'} = undef;
}
#
......@@ -149,8 +156,8 @@ sub parseCfgCmdLine
if (ref($config{'plugins'}{'load'}) eq "ARRAY") {
foreach my $plugin (@{$config{'plugins'}{'load'}}) {
$plugin =~ s/\s+//g;
# Skip comments
next if ($plugin =~ /^#/);
# Skip comments
next if ($plugin =~ /^#/);
push(@{$cfg->{'plugin_list'}},$plugin);
}
} elsif (defined($config{'plugins'}{'load'})) {
......@@ -162,37 +169,25 @@ sub parseCfgCmdLine
}
}
#
# Dictionary configuration
#
# Split off dictionaries to load
if (ref($config{'dictionary'}->{'load'}) eq "ARRAY") {
foreach my $dict (@{$config{'dictionary'}->{'load'}}) {
$dict =~ s/\s+//g;
# Skip comments
next if ($dict =~ /^#/);
push(@{$cfg->{'dictionary_list'}},$dict);
}
} elsif (defined($config{'dictionary'}->{'load'})) {
my @dictList = split(/\s+/,$config{'dictionary'}->{'load'});
foreach my $dict (@dictList) {
# Skip comments
next if ($dict =~ /^#/);
push(@{$cfg->{'dictionary_list'}},$dict);
# We may have config file groups we want to remember for other plugins
foreach my $group ($inifileHandle->Groups()) {
# Loop with group members
foreach my $member ($inifileHandle->GroupMembers($group)) {
# Chop off group name and just get the member
my $cleanMember = substr($member,length($group)+1);
# Link the config...
$config{$group}->{$cleanMember} = $config{$member};
}
}
# Check if the user specified a cache_file in the config
if (defined($config{'server'}{'cache_file'})) {
$cfg->{'cache_file'} = $config{'server'}{'cache_file'};
}
$globals->{'file.config'} = \%config;
$globals->{'config'} = $cfg;
}
# Display help
sub displayHelp {
displayBanner();
print(STDERR<<EOF);
......@@ -213,72 +208,128 @@ sub init
$globals->{'logger'} = $logger;
$globals->{'version'} = VERSION;
# Load dictionaries
$logger->log(LOG_INFO,"[MAIN] Initializing dictionaries...");
my $dict = new Radius::Dictionary;
foreach my $df (@{$globals->{'config'}->{'dictionary_list'}}) {
# Load dictionary
if (!$dict->readfile($df)) {
$logger->log(LOG_WARN,"[MAIN] Failed to load dictionary '$df': $!");
}
$logger->log(LOG_DEBUG,"[MAIN] Loaded dictionary '$df'.");
}
$logger->log(LOG_INFO,"[MAIN] Dictionaries initialized.");
# Store the dictionary
$globals->{'radius'}->{'dictionary'} = $dict;
$logger->log(LOG_NOTICE,"[MAIN] Entering INITIALIZATION state");
# Setup master session
POE::Session->create(
inline_states => {
'_start' => \&main_session_start,
'_stop' => \&main_session_stop,
'main_SIGHUP' => \&main_signal_SIGHUP,
'main_SIGINT' => \&main_signal_SIGINT,
},
);
$logger->log(LOG_INFO,"[MAIN] Initializing plugins...");
# We need to set the plugins global variable
opentrafficshaper::plugins::init($globals);
# Core configuration manager
$logger->log(LOG_INFO,"[MAIN] Initializing config manager...");
my $res = eval("
use opentrafficshaper::plugins::configmanager;
plugin_register(\$globals,\"configmanager\",\$opentrafficshaper::plugins::configmanager::pluginInfo);
");
if ($@ || (defined($res) && $res != 0)) {
$logger->log(LOG_WARN,"[MAIN] Error loading config manager, things WILL BREAK! ($@)");
} else {
$logger->log(LOG_DEBUG,"[MAIN] Config manager initialized.");
}
plugin_register("configmanager",1);
# Load plugins
$logger->log(LOG_INFO,"[MAIN] Initializing plugins...");
foreach my $plugin (@{$globals->{'config'}->{'plugin_list'}}) {
# Load plugin
my $res = eval("
use opentrafficshaper::plugins::${plugin}::${plugin};
plugin_register(\$globals,\"${plugin}\",\$opentrafficshaper::plugins::${plugin}::pluginInfo);
");
if ($@ || (defined($res) && $res != 0)) {
$logger->log(LOG_WARN,"[MAIN] Error loading plugin '$plugin' ($@)");
} else {
$logger->log(LOG_DEBUG,"[MAIN] Plugin '$plugin' loaded.");
}
foreach my $pluginName (@{$globals->{'config'}->{'plugin_list'}}) {
plugin_register($pluginName,0);
}
$logger->log(LOG_INFO,"[MAIN] Plugins initialized.");
$logger->log(LOG_INFO,"[MAIN] Plugins initialized");
}
# Register plugin info
sub plugin_register {
my ($globals,$plugin,$info) = @_;
# Function to start things up
sub start
{
$logger->log(LOG_NOTICE,"[MAIN] Entering STARTING state");
# Loop with plugins and call the start function for those that exist
foreach my $pluginName (keys %{$globals->{'plugins'}})
{
my $plugin = $globals->{'plugins'}->{$pluginName};
# If no info, return
if (!defined($info)) {
$logger->log(LOG_WARN,"WARNING: Plugin info not found for plugin => $plugin\n");
return -1;
# Load the function up
my $callStart = $plugin->{'Start'};
if (defined($callStart)) {
$callStart->();
}
}
}
# Become daemon
sub daemonize {
chdir '/'
or die "Can't chdir to /: $!";
open STDIN, '/dev/null'
or die "Can't read /dev/null: $!";
# Set real module name & save
$info->{'Plugin'} = $plugin;
push(@{$globals->{'plugins'}},$info);
open STDOUT, '> /dev/null'
or die "Can't open stdout log: $!";
# If we should, init the module
if (defined($info->{'Init'})) {
$info->{'Init'}($globals);
defined(my $pid = fork)
or die "Can't fork: $!";
exit if $pid;
# Write out our PID if we have a file to do it
if (defined($globals->{'config'}->{'pid_file'})) {
if (open(FH,"> ".$globals->{'config'}->{'pid_file'})) {
print(FH $$);
close(FH);
} else {
$logger->log(LOG_WARN,"[MAIN] Unable to write PID to '%s': %s",$globals->{'config'}->{'pid_file'},$!);
}
}
return 0;
setsid
or die "Can't start a new session: $!";
open STDERR, '> /dev/null'
or die "Can't open stderr log: $!";
}
# Function to fire up our main session
sub main_session_start
{
my $kernel = $_[KERNEL];
$kernel->alias_set('main');
# Register signal handlers
$kernel->sig('HUP', 'main_SIGHUP');
$kernel->sig('INT', 'main_SIGINT');
}
# Function to dispose of anything as a final stage to shutting down
sub main_session_stop
{
$logger->log(LOG_DEBUG,"[MAIN] Shutdown");
}
# Function to handle SIGHUP
sub main_signal_SIGHUP
{
my ($kernel,$signal_name) = @_[KERNEL,ARG0];
$logger->log(LOG_NOTICE,"[MAIN] Got SIGHUP");
$kernel->sig_handled();
}
# Function to handle SIGINT
sub main_signal_SIGINT
{
my ($kernel, $signal_name) = @_[KERNEL, ARG0];
$logger->log(LOG_NOTICE,"[MAIN] Got SIGINT, shutting down...");
$kernel->signal($kernel,'handle_SIGINT');
}
# vim: ts=4