Commit e160efa7 authored by Ad Schellevis's avatar Ad Schellevis

(trafficshaper) work in progress traffic shaper middleware and frontend components

parent 3926ad51
...@@ -40,22 +40,122 @@ use \OPNsense\Base\UIModelGrid; ...@@ -40,22 +40,122 @@ use \OPNsense\Base\UIModelGrid;
class SettingsController extends ApiControllerBase class SettingsController extends ApiControllerBase
{ {
/** /**
* retrieve pipe settings * validate and save model after update or insertion
* @param $mdlShaper
* @param $node reference node, to use as relative offset
* @return array result / validation output
*/
private function savePipe($mdlShaper, $node)
{
$result = array("result"=>"failed");
// perform validation
$valMsgs = $mdlShaper->performValidation();
foreach ($valMsgs as $field => $msg) {
if (!array_key_exists("validations", $result)) {
$result["validations"] = array();
}
// replace absolute path to attribute for relative one at uuid.
$fieldnm = $msg->getField();
$fieldnm = str_replace($node->__reference, "pipe", $fieldnm);
$result["validations"][$fieldnm] = $msg->getMessage();
}
// serialize model to config and save when there are no validation errors
if ($valMsgs->count() == 0) {
$mdlShaper->serializeToConfig();
// save config if validated correctly
Config::getInstance()->save();
$result["result"] = "saved";
}
return $result;
}
/**
* retrieve pipe settings or return defaults
* @param $uuid item unique id * @param $uuid item unique id
* @return array * @return array
*/ */
public function getPipeAction($uuid) public function getPipeAction($uuid = null)
{ {
$mdlShaper = new TrafficShaper();
if ($uuid != null) { if ($uuid != null) {
$mdlShaper = new TrafficShaper();
$node = $mdlShaper->getNodeByReference('pipes.pipe.'.$uuid); $node = $mdlShaper->getNodeByReference('pipes.pipe.'.$uuid);
if ($node != null) { if ($node != null) {
return $node->getNodes(); // return node
return array("pipe" => $node->getNodes());
} }
} else {
// generate new node, but don't save to disc
$node = $mdlShaper->pipes->pipe->add() ;
return array("pipe" => $node->getNodes());
} }
return array(); return array();
} }
/**
* update pipe with given properties
* @param $uuid item unique id
* @return array
*/
public function setPipeAction($uuid)
{
$result = array("result"=>"failed");
if ($this->request->isPost() && $this->request->hasPost("pipe")) {
$mdlShaper = new TrafficShaper();
if ($uuid != null) {
$node = $mdlShaper->getNodeByReference('pipes.pipe.'.$uuid);
if ($node != null) {
$node->setNodes($this->request->getPost("pipe"));
return $this->savePipe($mdlShaper, $node);
}
}
}
return $result;
}
/**
* add new pipe
* @return array
*/
public function addPipeAction()
{
$result = array("result"=>"failed");
if ($this->request->isPost() && $this->request->hasPost("pipe")) {
$mdlShaper = new TrafficShaper();
$node = $mdlShaper->addPipe();
$node->setNodes($this->request->getPost("pipe"));
return $this->savePipe($mdlShaper, $node);
}
return $result;
}
/**
* delete pipe by uuid
* @param $uuid item unique id
* @return array status
*/
public function delPipeAction($uuid)
{
$result = array("result"=>"failed");
if ($this->request->isPost()) {
$mdlShaper = new TrafficShaper();
if ($uuid != null) {
if ($mdlShaper->pipes->pipe->del($uuid)) {
// if item is removed, serialize to config and save
$mdlShaper->serializeToConfig();
Config::getInstance()->save();
$result['result'] = 'deleted';
} else {
$result['result'] = 'not found';
}
}
}
return $result;
}
/** /**
* search traffic shaper pipes * search traffic shaper pipes
* @return array * @return array
......
...@@ -37,6 +37,7 @@ class IndexController extends \OPNsense\Base\IndexController ...@@ -37,6 +37,7 @@ class IndexController extends \OPNsense\Base\IndexController
public function indexAction() public function indexAction()
{ {
$this->view->title = "Traffic Shaper"; $this->view->title = "Traffic Shaper";
$this->view->formDialogPipe = $this->getForm("dialogPipe");
$this->view->pick('OPNsense/TrafficShaper/index'); $this->view->pick('OPNsense/TrafficShaper/index');
} }
} }
<form>
<field>
<id>pipe.bandwidth</id>
<label>bandwidth</label>
<type>text</type>
<help>test
</help>
</field>
<field>
<id>pipe.bandwidthMetric</id>
<label>Enable traffic management.</label>
<type>dropdown</type>
<help><![CDATA[test]]></help>
</field>
</form>
\ No newline at end of file
...@@ -32,4 +32,47 @@ use OPNsense\Base\BaseModel; ...@@ -32,4 +32,47 @@ use OPNsense\Base\BaseModel;
class TrafficShaper extends BaseModel class TrafficShaper extends BaseModel
{ {
/**
* Add new pipe to shaper, generate new number if none is given.
* The first 10000 id's are automatically reserved for internal usage.
* @param null $pipenr new pipe number
* @return ArrayField
*/
public function addPipe($pipenr = null)
{
$allpipes = array();
foreach ($this->pipes->pipe->__items as $uuid => $pipe) {
if ($pipenr != null && $pipenr == $pipe->number->__toString()) {
// pipe found, return
return $pipe;
} elseif ($pipenr == null) {
// collect pipe numbers to find first possible item
$allpipes[] = $pipe->number->__toString();
}
}
sort($allpipes);
if ($pipenr == null) {
// generate new pipe number
$newId = 10000;
for ($i=0; $i < count($allpipes); ++$i) {
if ($allpipes[$i] > $newId && isset($allpipes[$i+1])) {
if ($allpipes[$i+1] - $allpipes[$i] > 1) {
// gap found
$newId = $allpipes[$i] + 1;
break;
}
} elseif ($allpipes[$i] >= $newId) {
// last item is higher than target
$newId = $allpipes[$i] + 1;
}
}
} else {
$newId = $pipenr;
}
$pipe = $this->pipes->pipe->add();
$pipe->number = $newId;
return $pipe;
}
} }
...@@ -34,8 +34,10 @@ POSSIBILITY OF SUCH DAMAGE. ...@@ -34,8 +34,10 @@ POSSIBILITY OF SUCH DAMAGE.
$( document ).ready(function() { $( document ).ready(function() {
/**
var grid =$("#grid-basic").bootgrid({ * Render pipe grid using searchPipes api
*/
var gridPipes =$("#grid-pipes").bootgrid({
ajax: true, ajax: true,
selection: true, selection: true,
multiSelect: true, multiSelect: true,
...@@ -49,32 +51,120 @@ POSSIBILITY OF SUCH DAMAGE. ...@@ -49,32 +51,120 @@ POSSIBILITY OF SUCH DAMAGE.
} }
}); });
grid.on("loaded.rs.jquery.bootgrid", function(){ /**
/* Executes after data is loaded and rendered */ * Link pipe grid command controls (edit/delete)
grid.find(".command-edit").on("click", function(e) */
gridPipes.on("loaded.rs.jquery.bootgrid", function(){
// edit item
gridPipes.find(".command-edit").on("click", function(e)
{ {
$('#form_uuid').attr('value',$(this).data("row-id")); var uuid=$(this).data("row-id");
$('#myModal').modal(); mapDataToFormUI({'frm_DialogPipe':"/api/trafficshaper/settings/getPipe/"+uuid}).done(function(){
}).end().find(".command-delete").on("click", function(e) // update selectors
formatTokenizersUI();
$('.selectpicker').selectpicker('refresh');
// clear validation errors (if any)
clearFormValidation('frm_DialogPipe');
});
// show dialog for pipe edit
$('#DialogPipe').modal();
// curry uuid to save action
$("#btn_DialogPipe_save").unbind('click').click(savePipe.bind(undefined, uuid));
}).end();
// delete item
gridPipes.find(".command-delete").on("click", function(e)
{ {
alert("You pressed delete on row: " + $(this).data("row-id")); var uuid = $(this).data("row-id");
BootstrapDialog.confirm({
title: 'Remove',
message: 'Remove selected item?',
type: BootstrapDialog.TYPE_DANGER,
btnCancelLabel: 'Cancel',
btnOKLabel: 'Yes',
btnOKClass: 'btn-primary',
callback: function(result) {
if(result) {
var url = "/api/trafficshaper/settings/delPipe/" + uuid;
ajaxCall(url=url,sendData={},callback=function(data,status){
// reload grid after delete
$("#grid-pipes").bootgrid("reload");
});
}
}
});
}).end();
});
/**
* save form data to end point for existing pipe
*/
function savePipe(uuid) {
saveFormToEndpoint(url="/api/trafficshaper/settings/setPipe/"+uuid,
formid="frm_DialogPipe", callback_ok=function(){
$("#DialogPipe").modal('hide');
$("#grid-pipes").bootgrid("reload");
});
}
/**
* save form data to end point for new pipe
*/
function addPipe() {
saveFormToEndpoint(url="/api/trafficshaper/settings/addPipe/",
formid="frm_DialogPipe", callback_ok=function(){
$("#DialogPipe").modal('hide');
$("#grid-pipes").bootgrid("reload");
});
}
/**
* Delete list of uuids on click event
*/
$("#deletePipes").click(function(){
BootstrapDialog.confirm({
title: 'Remove',
message: 'Remove selected items?',
type: BootstrapDialog.TYPE_DANGER,
btnCancelLabel: 'Cancel',
btnOKLabel: 'Yes',
btnOKClass: 'btn-primary',
callback: function(result) {
if(result) {
var rows =$("#grid-pipes").bootgrid('getSelectedRows');
if (rows != undefined){
var deferreds = [];
$.each(rows, function(key,uuid){
deferreds.push(ajaxCall(url="/api/trafficshaper/settings/delPipe/" + uuid, sendData={}));
});
// refresh after load
$.when.apply(null, deferreds).done(function(){
$("#grid-pipes").bootgrid("reload");
});
}
}
}
}); });
}); });
/**
* Add new pipe on click event
*/
$("#addPipe").click(function(){
mapDataToFormUI({'frm_DialogPipe':"/api/trafficshaper/settings/getPipe/"}).done(function(){
// update selectors
formatTokenizersUI();
$('.selectpicker').selectpicker('refresh');
// clear validation errors (if any)
clearFormValidation('frm_DialogPipe');
});
$("#test").click(function(){ // show dialog for pipe edit
var rows =$("#grid-basic").bootgrid('getSelectedRows'); $('#DialogPipe').modal();
alert(rows); // curry uuid to save action
$("#grid-basic").bootgrid("reload"); $("#btn_DialogPipe_save").unbind('click').click(addPipe);
setFormData('testfrm')
// var rowIds = [];
// for (var i = 0; i < rows.length; i++)
// {
// rowIds.push(rows[i]);
// }
// alert("Select: " + rowIds.join(","));
//alert(JSON.stringify($("#grid-basic").bootgrid('getSelectedRows')));
}); });
}); });
...@@ -82,63 +172,35 @@ POSSIBILITY OF SUCH DAMAGE. ...@@ -82,63 +172,35 @@ POSSIBILITY OF SUCH DAMAGE.
</script> </script>
<table id="grid-basic" class="table table-condensed table-hover table-striped"> <table id="grid-pipes" class="table table-condensed table-hover table-striped">
<thead> <thead>
<tr> <tr>
<th data-column-id="number" data-type="number">Number</th> <th data-column-id="number" data-type="number">Number</th>
<th data-column-id="bandwidth" data-type="number">Bandwidth</th> <th data-column-id="bandwidth" data-type="number">Bandwidth</th>
<th data-column-id="bandwidthMetric" data-type="string">BandwidthMetric</th> <th data-column-id="bandwidthMetric" data-type="string">BandwidthMetric</th>
<th data-column-id="description" data-type="string">description</th> <th data-column-id="description" data-type="string">description</th>
<th data-column-id="commands" data-formatter="commands" data-sortable="false">Commands</th> <th data-column-id="commands" data-formatter="commands" data-sortable="false">Commands</th>
<th data-column-id="uuid" data-type="string" data-identifier="true" data-visible="false">ID</th> <th data-column-id="uuid" data-type="string" data-identifier="true" data-visible="false">ID</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
</tbody> </tbody>
<tfoot> <tfoot>
<th></th> <tr>
<th></th> <td></td>
<th></th> <td></td>
<th></th> <td></td>
<th><button id="test">test</button></th> <td></td>
<td></td>
<td>
<button type="button" id="addPipe" class="btn btn-xs btn-default"><span class="fa fa-pencil"></span></button>
<button type="button" id="deletePipes" class="btn btn-xs btn-default"><span class="fa fa-trash-o"></span></button>
</td>
</tr>
</tfoot> </tfoot>
</table> </table>
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"> {# include dialogs #}
<div class="modal-dialog"> {{ partial("layout_partials/base_dialog",['fields':formDialogPipe,'id':'DialogPipe','label':'Edit pipe'])}}
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="myModalLabel">Modal title</h4>
</div>
<div class="modal-body">
<form id="testfrm">
<input id="form_uuid">
<table class="table table-striped table-condensed table-responsive">
<colgroup>
<col class="col-md-3"/>
<col class="col-md-4"/>
<col class="col-md-5"/>
</colgroup>
<tbody>
{{ partial("layout_partials/form_input_tr",
['id': 'general.port',
'label':'port',
'type':'text',
'help':'kjdhkjashdkjds'
])
}}
</tbody>
</table>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</div>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment