Merge branch 'kulikov-a-lvtemplates'

This commit is contained in:
Ad Schellevis 2021-04-08 17:14:38 +02:00
commit 361e336d3e
4 changed files with 381 additions and 31 deletions

View File

@ -0,0 +1,71 @@
<?php
/**
* Copyright (C) 2021 Deciso B.V.
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
namespace OPNsense\Diagnostics\Api;
use OPNsense\Base\ApiMutableModelControllerBase;
use OPNsense\Base\UserException;
use OPNsense\Core\Backend;
use OPNsense\Core\Config;
class LvtemplateController extends ApiMutableModelControllerBase
{
protected static $internalModelName = 'lvtemplate';
protected static $internalModelClass = 'OPNsense\Diagnostics\Lvtemplate';
public function searchItemAction()
{
return $this->searchBase(
"templates.template",
array('name', 'filters', 'or'), "name", null, SORT_NATURAL|SORT_FLAG_CASE
);
}
public function setItemAction($uuid)
{
return $this->setBase("template", "templates.template", $uuid);
}
public function addItemAction()
{
return $this->addBase("template", "templates.template");
}
public function getItemAction($uuid = null)
{
return $this->getBase("template", "templates.template", $uuid);
}
public function delItemAction($uuid)
{
return $this->delBase("templates.template", $uuid);
}
}

View File

@ -0,0 +1,36 @@
<?php
/**
* Copyright (C) 2021 Deciso B.V.
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\Diagnostics;
use OPNsense\Base\BaseModel;
class Lvtemplate extends BaseModel
{
}

View File

@ -0,0 +1,23 @@
<model>
<mount>//OPNsense/Firewall/Lvtemplate</mount>
<version>0.0.1</version>
<description>Firewall Live View filter templates</description>
<items>
<templates>
<template type="ArrayField">
<name type="TextField">
<Required>Y</Required>
<mask>/^.{1,255}$/u</mask>
<ValidationMessage>Should be a string between 1 and 255 characters.</ValidationMessage>
</name>
<or type="BooleanField">
<default>0</default>
<Required>Y</Required>
</or>
<filters type="CSVListField">
<Required>Y</Required>
</filters>
</template>
</templates>
</items>
</model>

View File

@ -1,6 +1,6 @@
{#
#
# Copyright (c) 2014-2016 Deciso B.V.
# Copyright (c) 2014-2021 Deciso B.V.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -37,7 +37,7 @@
let hostnameMap = {};
/**
* reverse lookup address fields (replace adres part for hostname if found)
* reverse lookup address fields (replace address part for hostname if found)
*/
function reverse_lookup() {
let to_fetch = [];
@ -74,6 +74,139 @@
}
}
/**
* set new selection
* @param items list of lexical expressions
* @param operator enable or disable global OR operator
*/
function set_selection(items, operator)
{
// remove old selection
$("#filters > span.badge").click();
// collect valid condition types
let conditions = [];
$("#filter_condition > option").each(function(){
conditions.push($(this).val());
});
items.forEach(function(value) {
let parts = value.split(new RegExp("("+conditions.join("|")+")(.+)$"));
if (parts.length >= 3 && $("#filter_tag").val(parts[0]).val() === parts[0] ) {
$("#filter_tag").val(parts[0]);
$("#filter_condition").val(parts[1]);
$("#filter_value").val(parts[2]);
$("#add_filter_condition").click();
} else if (value.toLowerCase() == "or=1") {
operator = "1";
}
});
$("#filter_or_type").prop('checked', operator === "1" ? true : false);
$(".selectpicker").selectpicker('refresh');
$("#filter_tag").change();
}
/**
* add new filters template
* @param t_data template's parameters
*/
function addTemplate(t_data) {
ajaxCall('/api/diagnostics/lvtemplate/addItem/', t_data, function(data, status) {
if (data.result == "saved") {
fetchTemplates(data.uuid);
} else {
BootstrapDialog.show({
type: BootstrapDialog.TYPE_DANGER,
title: "{{ lang._('Add filters template') }}",
message: "{{ lang._('Template save failed. Message: ') }}" + data.result,
buttons: [{
label: "{{ lang._('Close') }}",
action: function (dialogRef) {
dialogRef.close();
}
}]
});
fetchTemplates("00000");
}
})
}
/**
* set template new values
* @param t_id template uuid
* @param t_data template's parameters
*/
function editTemplate(t_id, t_data) {
ajaxCall('/api/diagnostics/lvtemplate/setItem/' + t_id, t_data, function(data, status) {
if (data.result == "saved") {
fetchTemplates(t_id);
} else {
BootstrapDialog.show({
type: BootstrapDialog.TYPE_DANGER,
title: "{{ lang._('Filters template edit') }}",
message: "{{ lang._('Template edit failed. Message: ') }}" + data.result,
buttons: [{
label: "{{ lang._('Close') }}",
action: function (dialogRef) {
dialogRef.close();
}
}]
});
fetchTemplates(t_id);
}
})
}
/**
* delete filters template
* @param t_id template uuid
*/
function delTemplate(t_id) {
ajaxCall('/api/diagnostics/lvtemplate/delItem/' + t_id, {}, function(data, status) {
if (data.result == "deleted") {
//don't reset current filters so template can be restored right after delete
$("#templates option[value=" + t_id + "]").remove();
$("#templates").val("").selectpicker('refresh');
} else {
BootstrapDialog.show({
type: BootstrapDialog.TYPE_DANGER,
title: "{{ lang._('Filters template delete') }}",
message: "{{ lang._('Template delete failed. Result: ') }}" + data.result,
buttons: [{
label: "{{ lang._('Close') }}",
action: function (dialogRef) {
dialogRef.close();
}
}]
});
}
})
}
/**
* fetch templates from config
* @param opt select value to make :selected and apply
*/
function fetchTemplates(opt) {
opt = opt || "00000";
//apply = apply || true;
$('#templ_name').val("");
$('#templates').empty();
$('#templates').append($('<option/>', {value: "00000", text: "None"}).data('template', {'filters': "0", 'or': "0"}).addClass("disp_none_opt templates"));
$('#templates').append($('<option/>', {value: "00001", text: "New"}).data('template', {'filters': "0", 'or': "0"}).data('icon','glyphicon-file').addClass("add_new_opt templ_save"));
$('#templates').selectpicker('refresh');
$('.templates').show();
$('.templ_save').hide();
ajaxGet('/api/diagnostics/lvtemplate/searchItem/', {}, function(data, status) {
let templates = data.rows;
$.each(templates, function(i, template) {
$('#templates').append(template.uuid == opt ? $('<option/>', {value:template.uuid, text:template.name, selected: "selected" }).data('template', template) : $('<option/>', {value:template.uuid, text:template.name }).data('template', template));
});
$('#templates').selectpicker('refresh');
$('.badge').click();
$("#templates").change();
});
}
function fetch_log() {
var record_spec = [];
// read heading, contains field specs
@ -354,6 +487,95 @@
fetch_log();
});
// templates actions
$("#templates").change(function () {
if ($('#templ_save_start').is(':visible')) {
//apply chosen template
let t_data = $(this).find('option:selected').data('template') ? $(this).find('option:selected').data('template') : {'filters': "0", 'or': "0"};
set_selection(t_data.filters.split(','), t_data.or);
} else {
//choose template to modify or create new one. Show Name input if New option clicked
if ($('#templates').val() === "00001") {
$('#templates').selectpicker('hide');
$('#templ_name').show().focus();
}
}
});
$('#templ_save_start').click(function () {
if ($(".badge").text() == '') {
BootstrapDialog.show({
type: BootstrapDialog.TYPE_DANGER,
title: "{{ lang._('Save filters template') }}",
message: "{{ lang._('Filters not set') }}",
buttons: [{
label: "{{ lang._('Close') }}",
action: function (dialogRef) {
dialogRef.close();
}
}]
});
} else {
$('.templates').hide();
$('.templ_save').show();
$('#templ_name').focus();
if ($("#templates option").length == 3){
//no stored templates. skip to new template name
$('#templates').val("00001").selectpicker('refresh').change();
}
}
});
$("#templ_save_cancel").click(function () {
$('#templ_name').val("").hide();
$('.templ_save').hide();
$('.templates').show();
$('#templates').val('').selectpicker('refresh').selectpicker('show');
});
$("#templ_name").on('keyup', function (e) {
if (e.key === 'Enter' || e.keyCode === 13) {
$('#templ_save_apply').click();
} else if (e.keyCode === 27) {
$('#templ_name').val("").hide();
$('#templates').val('').selectpicker('refresh').selectpicker('show');
}
});
$("#templ_save_apply").click(function () {
let fltrs = "";
$('.badge').each(function () {
fltrs += $(this).text() + ",";
});
fltrs = fltrs.slice(0, -1);
let or = $('#filter_or_type').prop("checked") ? "1" : "0";
let t_data = {
'template': {
'filters': fltrs,
'or': or
}
};
$('#templates').selectpicker('refresh').selectpicker('show');
if ($("#templ_name").val().length >= 1 && $("#templ_name").is(':visible')) {
//new template
t_data.template.name = $("#templ_name").val();
$('#templ_name').val("").hide();
addTemplate(t_data);
} else if ($("#templ_name").val().length == 0 && $("#templ_name").is(':hidden') && $("#templates").val().length == 36) {
//edit template
let t_id = $("#templates").val();
t_data.template.name = $("#templates option:selected").text();
editTemplate(t_id, t_data);
}
});
$("#template_delete").click(function () {
let t_id = $('#templates').val();
if (t_id.length == 36) {
delTemplate(t_id);
}
});
// fetch interface mappings on load
ajaxGet('/api/diagnostics/interface/getInterfaceNames', {}, function(data, status) {
interface_descriptions = data;
@ -389,36 +611,14 @@
filter_value.show();
}
}).change();
fetchTemplates("00000");
}
});
});
/**
* set new selection
* @param items list of lexical expressions
*/
function set_selection(items)
{
// remove old selection
$("#filters > span.badge").click();
// collect valid condition types
let conditions = [];
$("#filter_condition > option").each(function(){
conditions.push($(this).val());
});
items.forEach(function(value) {
let parts = value.split(new RegExp("("+conditions.join("|")+")(.+)$"));
if (parts.length >= 3 && $("#filter_tag").val(parts[0]).val() === parts[0] ) {
$("#filter_tag").val(parts[0]);
$("#filter_condition").val(parts[1]);
$("#filter_value").val(parts[2]);
$("#add_filter_condition").click();
}
});
}
// get and apply url params. ie11 compat
set_selection(window.location.search.substring(1).split("&"));
set_selection(window.location.search.substring(1).split("&"), "0");
// startup poller
poller();
@ -473,7 +673,7 @@
</select>
</td>
<td style="width:125px;">
<select id="filter_condition" class="condition" data-width="120px">
<select id="filter_condition" class="selectpicker" data-width="120px">
<option value="~" selected=selected>{{ lang._('contains') }}</option>
<option value="=">{{ lang._('is') }}</option>
<option value="!~">{{ lang._('does not contain') }}</option>
@ -505,14 +705,34 @@
<tfoot>
<tr>
<td colspan="4">
<input id="filter_or_type" type="checkbox">
{{ lang._('Select any of given criteria (or)') }}
<label>
<input id="filter_or_type" type="checkbox">
{{ lang._('Select any of given criteria (or)') }}
</label>
</td>
</tr>
</tfoot>
</table>
</div>
<div class="col-lg-6 col-sm-12">
<div class="col-lg-4 col-sm-12">
<div class="pull-right">
<button type="button" class="btn btn-default templates"
title="Save the current set of filters" id="templ_save_start"><span
class="fa fa-angle-double-right"></span></button>
<button type="button" class="btn btn-default templ_save" title="Cancel" id="templ_save_cancel"><span
class="fa fa-times"></span></button>
<div style="display: inline-block;vertical-align: top;"><select id="templates" class="selectpicker" title="Choose template" data-width="200"></select>
<input type="text" id="templ_name" placeholder="Template name" style="width:200px;vertical-align:middle;display:none;">
</div>
<button type="button" class="btn btn-default templ_save" title="Save template" id="templ_save_apply"><span class="fa fa-save"></span></button>
<span class="templates">
<button id="template_delete" type="button" class="btn btn-default" title="Deleted selected template" if="templ_del">
<span class="fa fa-trash"></span>
</button>
</span>
</div>
</div>
<div class="col-lg-2 col-sm-12">
<div class="pull-right">
<div class="checkbox-inline">
<label>