Fix Service Templates Dynamic (#12626)

* Fix Service Templates Dynamic
Remove dynamic groups type, it did not work.
Rename and simplify field names.  (fixing an issue with the builder where the rules field should be named rules)

* update schema def

* wrong method name in migration

* fix rules builder

* corerct call to apply

* sqlite can't drop columns

* correct column rename order on down.

* fix sqlite again

* fix whitespace
This commit is contained in:
Tony Murray
2021-03-18 08:14:45 -05:00
committed by GitHub
parent 92bf7d5efa
commit ac7b4539ce
9 changed files with 130 additions and 224 deletions

View File

@@ -69,11 +69,9 @@ class ServiceTemplateController extends Controller
'groups.*' => 'integer',
'devices' => 'array',
'devices.*' => 'integer',
'type' => 'string',
'dtype' => 'required|in:dynamic,static',
'dgtype' => 'required|in:dynamic,static',
'drules' => 'json|required_if:dtype,dynamic',
'dgrules' => 'json|required_if:dgtype,dynamic',
'check' => 'string',
'type' => 'required|in:dynamic,static',
'rules' => 'json|required_if:type,dynamic',
'param' => 'nullable|string',
'ip' => 'nullable|string',
'desc' => 'nullable|string',
@@ -87,11 +85,9 @@ class ServiceTemplateController extends Controller
$request->only(
[
'name',
'check',
'type',
'dtype',
'dgtype',
'drules',
'dgrules',
'rules',
'param',
'ip',
'desc',
@@ -101,16 +97,14 @@ class ServiceTemplateController extends Controller
]
)
);
$template->drules = json_decode($request->drules);
$template->dgrules = json_decode($request->dgrules);
$template->rules = json_decode($request->rules);
$template->save();
if ($request->dtype == 'static') {
if ($request->type == 'static') {
$template->devices()->sync($request->devices);
}
if ($request->dgtype == 'static') {
$template->groups()->sync($request->groups);
}
$template->groups()->sync($request->groups);
Toastr::success(__('Service Template :name created', ['name' => $template->name]));
return redirect()->route('services.templates.index');
@@ -165,15 +159,13 @@ class ServiceTemplateController extends Controller
}
),
],
'dtype' => 'required|in:dynamic,static',
'drules' => 'json|required_if:dtype,dynamic',
'type' => 'required|in:dynamic,static',
'rules' => 'json|required_if:type,dynamic',
'devices' => 'array',
'devices.*' => 'integer',
'dgtype' => 'required|in:dynamic,static',
'dgrules' => 'json|required_if:dgtype,dynamic',
'groups' => 'array',
'groups.*' => 'integer',
'type' => 'string',
'check' => 'string',
'param' => 'nullable|string',
'ip' => 'nullable|string',
'desc' => 'nullable|string',
@@ -187,11 +179,9 @@ class ServiceTemplateController extends Controller
$request->only(
[
'name',
'check',
'type',
'dtype',
'dgtype',
'drules',
'dgrules',
'rules',
'param',
'ip',
'desc',
@@ -203,27 +193,22 @@ class ServiceTemplateController extends Controller
);
$devices_updated = false;
if ($template->dtype == 'static') {
if ($template->type == 'static') {
// sync device_ids from input
$updated = $template->devices()->sync($request->get('devices', []));
// check for attached/detached/updated
$devices_updated = array_sum(array_map(function ($device_ids) {
return count($device_ids);
}, $updated)) > 0;
} else {
$template->drules = json_decode($request->drules);
}
$device_groups_updated = false;
if ($template->dgtype == 'static') {
} elseif ($template->type == 'dynamic') {
$template->rules = json_decode($request->rules);
} elseif ($template->type == 'groups') {
// sync device_group_ids from input
$updated = $template->groups()->sync($request->get('groups', []));
// check for attached/detached/updated
$device_groups_updated = array_sum(array_map(function ($device_group_ids) {
return count($device_group_ids);
}, $updated)) > 0;
} else {
$template->dgrules = json_decode($request->dgrules);
}
if ($template->isDirty() || $devices_updated || $device_groups_updated) {
@@ -237,8 +222,7 @@ class ServiceTemplateController extends Controller
}
} catch (\Illuminate\Database\QueryException $e) {
return redirect()->back()->withInput()->withErrors([
'drules' => __('Rules resulted in invalid query: ') . $e->getMessage(),
'dgrules' => __('Rules resulted in invalid query: ') . $e->getMessage(),
'rules' => __('Rules resulted in invalid query: ') . $e->getMessage(),
]);
}
} else {
@@ -264,7 +248,7 @@ class ServiceTemplateController extends Controller
],
[
'service_name' => $template->name,
'service_type' => $template->type,
'service_type' => $template->check,
'service_template_id' => $template->id,
'service_param' => $template->param,
'service_ip' => $template->ip,
@@ -295,7 +279,7 @@ class ServiceTemplateController extends Controller
],
[
'service_name' => $template->name,
'service_type' => $template->type,
'service_type' => $template->check,
'service_template_id' => $template->id,
'service_param' => $template->param,
'service_ip' => $template->ip,
@@ -318,7 +302,7 @@ class ServiceTemplateController extends Controller
public function applyAll()
{
foreach (ServiceTemplate::all() as $template) {
ServiceTemplateController::apply($template);
$this->apply($template);
}
$msg = __('All Service Templates have been applied');

View File

@@ -36,11 +36,9 @@ class ServiceTemplate extends BaseModel
protected $fillable = [
'id',
'ip',
'check',
'type',
'dtype',
'dgtype',
'drules',
'dgrules',
'rules',
'desc',
'param',
'ignore',
@@ -58,8 +56,7 @@ class ServiceTemplate extends BaseModel
protected $casts = [
'ignore' => 'integer',
'disabled' => 'integer',
'drules' => 'array',
'dgrules' => 'array',
'rules' => 'array',
];
public static function boot()
@@ -72,21 +69,15 @@ class ServiceTemplate extends BaseModel
});
static::saving(function (ServiceTemplate $template) {
if ($template->dtype == 'dynamic' and $template->isDirty('drules')) {
$template->drules = $template->getDeviceParser()->generateJoins()->toArray();
}
if ($template->dgtype == 'dynamic' and $template->isDirty('dgrules')) {
$template->dgrules = $template->getDeviceGroupParser()->generateJoins()->toArray();
if ($template->type == 'dynamic' and $template->isDirty('rules')) {
$template->rules = $template->getDeviceParser()->generateJoins()->toArray();
}
});
static::saved(function (ServiceTemplate $template) {
if ($template->dtype == 'dynamic' and $template->isDirty('drules')) {
if ($template->type == 'dynamic' and $template->isDirty('rules')) {
$template->updateDevices();
}
if ($template->dgtype == 'dynamic' and $template->isDirty('dgrules')) {
$template->updateGroups();
}
});
}
@@ -97,23 +88,12 @@ class ServiceTemplate extends BaseModel
*/
public function updateDevices()
{
if ($this->dtype == 'dynamic') {
$this->devices()->sync(QueryBuilderFluentParser::fromJSON($this->drules)->toQuery()
if ($this->type == 'dynamic') {
$this->devices()->sync(QueryBuilderFluentParser::fromJSON($this->rules)->toQuery()
->distinct()->pluck('devices.device_id'));
}
}
/**
* Update device groups included in this template (dynamic only)
*/
public function updateGroups()
{
if ($this->dgtype == 'dynamic') {
$this->groups()->sync(QueryBuilderFluentParser::fromJSON($this->dgrules)->toQuery()
->distinct()->pluck('device_groups.id'));
}
}
/**
* Update the device template groups for the given device or device_id
*
@@ -139,7 +119,7 @@ class ServiceTemplate extends BaseModel
->get()
->filter(function ($template) use ($device) {
/** @var ServiceTemplate $template */
if ($template->dtype == 'dynamic') {
if ($template->type == 'dynamic') {
try {
return $template->getDeviceParser()
->toQuery()
@@ -185,20 +165,6 @@ class ServiceTemplate extends BaseModel
}])
->get()
->filter(function ($template) use ($deviceGroup) {
/** @var ServiceTemplate $template */
if ($template->dgtype == 'dynamic') {
try {
return $template->getDeviceGroupParser()
->toQuery()
->where('device_groups.id', $deviceGroup->id)
->exists();
} catch (\Illuminate\Database\QueryException $e) {
Log::error("Service Template '$template->name' generates invalid query: " . $e->getMessage());
return false;
}
}
// for static, if this device group is include, keep it.
return $template->groups
->where('device_group_id', $deviceGroup->id)
@@ -215,17 +181,7 @@ class ServiceTemplate extends BaseModel
*/
public function getDeviceParser()
{
return QueryBuilderFluentParser::fromJson($this->drules);
}
/**
* Get a query builder parser instance from this Service Template device group rule
*
* @return QueryBuilderFluentParser
*/
public function getDeviceGroupParser()
{
return QueryBuilderFluentParser::fromJson($this->dgrules);
return QueryBuilderFluentParser::fromJson($this->rules);
}
// ---- Query Scopes ----

View File

@@ -17,9 +17,11 @@ class CreateServiceTemplatesTable extends Migration
$table->text('ip')->nullable()->default(null);
$table->string('type');
$table->string('dtype', 16)->default('static');
$table->string('dgtype', 16)->default('static');
$table->text('drules')->nullable();
$table->text('dgrules')->nullable();
if (\LibreNMS\DB\Eloquent::getDriver() !== 'sqlite') {
$table->string('dgtype', 16)->default('static');
$table->text('dgrules')->nullable();
}
$table->text('desc')->nullable()->default(null);
$table->text('param')->nullable()->default(null);
$table->boolean('ignore')->default(0);

View File

@@ -0,0 +1,55 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class ServiceTemplatesCleanup extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('service_templates', function (Blueprint $table) {
$table->renameColumn('type', 'check');
});
Schema::table('service_templates', function (Blueprint $table) {
$table->renameColumn('dtype', 'type');
});
Schema::table('service_templates', function (Blueprint $table) {
$table->renameColumn('drules', 'rules');
});
if (\LibreNMS\DB\Eloquent::getDriver() !== 'sqlite') {
Schema::table('service_templates', function (Blueprint $table) {
$table->dropColumn(['dgtype', 'dgrules']);
});
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('service_templates', function (Blueprint $table) {
$table->renameColumn('type', 'dtype');
});
Schema::table('service_templates', function (Blueprint $table) {
$table->renameColumn('check', 'type');
});
Schema::table('service_templates', function (Blueprint $table) {
$table->renameColumn('rules', 'drules');
});
if (\LibreNMS\DB\Eloquent::getDriver() !== 'sqlite') {
Schema::table('service_templates', function (Blueprint $table) {
$table->string('dgtype', 16)->default('static');
$table->text('dgrules')->nullable();
});
}
}
}

View File

@@ -1763,11 +1763,9 @@ service_templates:
Columns:
- { Field: id, Type: 'int unsigned', 'Null': false, Extra: auto_increment }
- { Field: ip, Type: text, 'Null': true, Extra: '' }
- { Field: type, Type: varchar(255), 'Null': false, Extra: '' }
- { Field: dtype, Type: varchar(16), 'Null': false, Extra: '', Default: static }
- { Field: dgtype, Type: varchar(16), 'Null': false, Extra: '', Default: static }
- { Field: drules, Type: text, 'Null': true, Extra: '' }
- { Field: dgrules, Type: text, 'Null': true, Extra: '' }
- { Field: check, Type: varchar(255), 'Null': false, Extra: '' }
- { Field: type, Type: varchar(16), 'Null': false, Extra: '', Default: static }
- { Field: rules, Type: text, 'Null': true, Extra: '' }
- { Field: desc, Type: text, 'Null': true, Extra: '' }
- { Field: param, Type: text, 'Null': true, Extra: '' }
- { Field: ignore, Type: tinyint, 'Null': false, Extra: '', Default: '0' }

View File

@@ -6,7 +6,7 @@
<div class="container">
<div class="row">
<form action="{{ route('services.templates.store') }}" method="POST" role="form"
class="form-horizontal services-templates-form col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2 col-sm-12">
class="form-horizontal service-template-form col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2 col-sm-12">
<legend><h2>@lang('Create Service Template')</h2></legend>
<div class='alert alert-info'>Service Template will created for the specified Device Group.</div>
@csrf

View File

@@ -6,9 +6,9 @@
<div class="container">
<div class="row">
<form action="{{ route('services.templates.update', $template->id) }}" method="POST" role="form"
class="form-horizontal services-templates-form col-md-10 col-md-offset-1 col-sm-12">
class="form-horizontal service-template-form col-md-10 col-md-offset-1 col-sm-12">
<legend><h2>@lang('Edit Service Template'): {{ $template->name }}</h2></legend>
<div class='alert alert-info'>Service Template will edited for the specified Device Group.</div>
<div class='alert alert-info'>Service Template will be edited for the specified Device Group.</div>
{{ method_field('PUT') }}
@csrf
<div class='well well-lg'>

View File

@@ -6,28 +6,28 @@
</div>
</div>
<div class="form-group @if($errors->has('dtype')) has-error @endif">
<label for="dtype" class="control-label col-sm-3 col-md-2">@lang('Device Type')</label>
<div class="form-group @if($errors->has('type')) has-error @endif">
<label for="type" class="control-label col-sm-3 col-md-2">@lang('Device Type')</label>
<div class="col-sm-9 col-md-10">
<select class="form-control" id="dtype" name="dtype" onchange="change_st_dtype(this)">
<select class="form-control" id="type" name="type" onchange="change_st_dtype(this)">
<option value="static"
@if(old('dtype', $template->dtype) == 'static') selected @endif>@lang('Static')</option>
@if(old('type', $template->type) == 'static') selected @endif>@lang('Static')</option>
<option value="dynamic"
@if(old('dtype', $template->dtype) == 'dynamic') selected @endif>@lang('Dynamic')</option>
@if(old('type', $template->type) == 'dynamic') selected @endif>@lang('Dynamic')</option>
</select>
<span class="help-block">{{ $errors->first('dtype') }}</span>
<span class="help-block">{{ $errors->first('type') }}</span>
</div>
</div>
<div id="dynamic-st-d-form" class="form-group @if($errors->has('drules')) has-error @endif">
<label for="drules" class="control-label col-sm-3 col-md-2 text-wrap">@lang('Define Device Rules')</label>
<div id="dynamic-st-d-form" class="form-group @if($errors->has('rules')) has-error @endif" style="display: none">
<label for="rules" class="control-label col-sm-3 col-md-2 text-wrap">@lang('Define Device Rules')</label>
<div class="col-sm-9 col-md-10">
<div id="builder"></div>
<span class="help-block">{{ $errors->first('drules') }}</span>
<span class="help-block">{{ $errors->first('rules') }}</span>
</div>
</div>
<div id="static-st-d-form" class="form-group @if($errors->has('devices')) has-error @endif" style="display: none">
<div id="static-st-d-form" class="form-group @if($errors->has('devices')) has-error @endif">
<label for="devices" class="control-label col-sm-3 col-md-2 text-nowrap">@lang('Select Devices')</label>
<div class="col-sm-9 col-md-10">
<select class="form-control" id="devices" name="devices[]" multiple>
@@ -39,28 +39,7 @@
</div>
</div>
<div class="form-group @if($errors->has('dgtype')) has-error @endif">
<label for="dgtype" class="control-label col-sm-3 col-md-2">@lang('Device Group Type')</label>
<div class="col-sm-9 col-md-10">
<select class="form-control" id="dgtype" name="dgtype" onchange="change_st_dgtype(this)">
<option value="static"
@if(old('dgtype', $template->dgtype) == 'static') selected @endif>@lang('Static')</option>
<option value="dynamic"
@if(old('dgtype', $template->dgtype) == 'dynamic') selected @endif>@lang('Dynamic')</option>
</select>
<span class="help-block">{{ $errors->first('dgtype') }}</span>
</div>
</div>
<div id="dynamic-st-dg-form" class="form-group @if($errors->has('dgrules')) has-error @endif">
<label for="dgrules" class="control-label col-sm-3 col-md-2 text-wrap">@lang('Device Group Rules')</label>
<div class="col-sm-9 col-md-10">
<div id="builder2"></div>
<span class="help-block">{{ $errors->first('dgrules') }}</span>
</div>
</div>
<div id="static-st-dg-form" class="form-group @if($errors->has('groups')) has-error @endif" style="display: none">
<div id="static-st-dg-form" class="form-group @if($errors->has('groups')) has-error @endif">
<label for="groups" class="control-label col-sm-3 col-md-2 text-wrap">@lang('Device Groups')</label>
<div class="col-sm-9 col-md-10">
<select class="form-control" id="groups" name="groups[]" multiple>
@@ -72,15 +51,15 @@
</div>
</div>
<div class="form-group @if($errors->has('type')) has-error @endif">
<label for="type" class="control-label col-sm-3 col-md-2">@lang('Check Type')</label>
<div class="form-group @if($errors->has('check')) has-error @endif">
<label for="check" class="control-label col-sm-3 col-md-2">@lang('Check Type')</label>
<div class="col-sm-9 col-md-10">
<select class="form-control" id="type" name="type">
<select class="form-control" id="check" name="check">
@foreach($services as $current_service)
<option value="{{ $current_service }}" @if($current_service == $template->type) selected @endif>{{ $current_service }}</option>
<option value="{{ $current_service }}" @if($current_service == $template->check) selected @endif>{{ $current_service }}</option>
@endforeach
</select>
<span class="help-block">{{ $errors->first('type') }}</span>
<span class="help-block">{{ $errors->first('check') }}</span>
</div>
</div>
@@ -143,20 +122,11 @@
$('#disabled').val(value);
});
function change_st_dtype(select) {
var dtype = select.options[select.selectedIndex].value;
document.getElementById("dynamic-st-d-form").style.display = (dtype === 'dynamic' ? 'block' : 'none');
document.getElementById("static-st-d-form").style.display = (dtype === 'dynamic' ? 'none' : 'block');
var type = select.options[select.selectedIndex].value;
document.getElementById("dynamic-st-d-form").style.display = (type === 'dynamic' ? 'block' : 'none');
document.getElementById("static-st-d-form").style.display = (type === 'dynamic' ? 'none' : 'block');
}
change_st_dtype(document.getElementById('dtype'));
function change_st_dgtype(select) {
var dgtype = select.options[select.selectedIndex].value;
document.getElementById("dynamic-st-dg-form").style.display = (dgtype === 'dynamic' ? 'block' : 'none');
document.getElementById("static-st-dg-form").style.display = (dgtype === 'dynamic' ? 'none' : 'block');
}
change_st_dgtype(document.getElementById('dgtype'));
change_st_dtype(document.getElementById('type'));
init_select2('#devices', 'device', {multiple: true});
init_select2('#groups', 'device-group', {multiple: true});
@@ -206,79 +176,24 @@
}
}
});
var builder2 = $('#builder2').on('afterApplyRuleFlags.queryBuilder afterCreateRuleFilters.queryBuilder', function () {
$("[name$='_filter']").each(function () {
$(this).select2({
dropdownAutoWidth: true,
width: 'auto'
});
});
}).on('ruleToSQL.queryBuilder.filter', function (e, rule) {
if (rule.operator === 'regexp') {
e.value += ' \'' + rule.value + '\'';
}
}).queryBuilder({
plugins: [
'bt-tooltip-errors'
// 'not-group'
],
filters: {!! $filters !!},
operators: [
'equal', 'not_equal', 'between', 'not_between', 'begins_with', 'not_begins_with', 'contains', 'not_contains', 'ends_with', 'not_ends_with', 'is_empty', 'is_not_empty', 'is_null', 'is_not_null', 'in', 'not_in',
{type: 'less', nb_inputs: 1, multiple: false, apply_to: ['string', 'number', 'datetime']},
{type: 'less_or_equal', nb_inputs: 1, multiple: false, apply_to: ['string', 'number', 'datetime']},
{type: 'greater', nb_inputs: 1, multiple: false, apply_to: ['string', 'number', 'datetime']},
{type: 'greater_or_equal', nb_inputs: 1, multiple: false, apply_to: ['string', 'number', 'datetime']},
{type: 'regex', nb_inputs: 1, multiple: false, apply_to: ['string', 'number']},
{type: 'not_regex', nb_inputs: 1, multiple: false, apply_to: ['string', 'number']}
],
lang: {
operators: {
regexp: 'regex',
not_regex: 'not regex'
}
},
sqlOperators: {
regexp: {op: 'REGEXP'},
not_regexp: {op: 'NOT REGEXP'}
},
sqlRuleOperator: {
'REGEXP': function (v) {
return {val: v, op: 'regexp'};
},
'NOT REGEXP': function (v) {
return {val: v, op: 'not_regexp'};
}
}
});
$('.service-template-form').submit(function (eventObj) {
if ($('#dtype').val() === 'dynamic') {
$('<input type="hidden" name="drules" />')
if ($('#type').val() === 'dynamic') {
$('<input type="hidden" name="rules" />')
.attr('value', JSON.stringify(builder.queryBuilder('getRules')))
.appendTo(this);
console.log('parsed');
console.log(this);
if (!builder.queryBuilder('validate')) {
return false;
}
}
if ($('#dgtype').val() === 'dynamic') {
$('<input type="hidden" name="dgrules" />')
.attr('value', JSON.stringify(builder2.queryBuilder('getRules')))
.appendTo(this);
if (!builder2.queryBuilder('validate')) {
eventObj.preventDefault();
return false;
}
}
return true;
});
</script>
<script>
var drules = {!! json_encode(old('drules') ? json_decode(old('drules')) : $template->drules) !!};
if (drules) {
builder.queryBuilder('setRules', drules);
}
var dgrules = {!! json_encode(old('dgrules') ? json_decode(old('dgrules')) : $template->dgrules) !!};
if (dgrules) {
builder2.queryBuilder('setRules', dgrules);
var rules = {!! json_encode(old('rules') ? json_decode(old('rules')) : $template->rules) !!};
if (rules) {
builder.queryBuilder('setRules', rules);
}
</script>

View File

@@ -31,9 +31,7 @@
<th>@lang('Devices')</th>
<th>@lang('Device Groups')</th>
<th>@lang('Device Type')</th>
<th>@lang('Device Group Type')</th>
<th>@lang('Device Rules')</th>
<th>@lang('Device Group Rules')</th>
<th>@lang('Actions')</th>
</tr>
</thead>
@@ -48,10 +46,8 @@
<td>
<a href="{{ url("/device-groups/serviceTemplates=$template->id") }}">{{ $template->groups_count }}</a>
</td>
<td>{{ __(ucfirst($template->dtype)) }}</td>
<td>{{ __(ucfirst($template->dgtype)) }}</td>
<td>{{ $template->dtype == 'dynamic' ? $template->getDeviceParser()->toSql(false) : '' }}</td>
<td>{{ $template->dgtype == 'dynamic' ? $template->getDeviceGroupParser()->toSql(false) : '' }}</td>
<td>{{ __(ucfirst($template->type)) }}</td>
<td>{{ $template->type == 'dynamic' ? $template->getDeviceParser()->toSql(false) : '' }}</td>
<td>
<button type="button" title="@lang('Apply Services for this Service Template')" class="btn btn-success btn-sm" aria-label="@lang('Apply')"
onclick="apply_st(this, '{{ $template->name }}', '{{ $template->id }}', '{{ route('services.templates.apply', $template->id) }}')">
@@ -94,7 +90,7 @@
@foreach($group->serviceTemplates as $template)
<tr id="row_{{ $template->id }}">
<td>{{ $template->name }}</td>
<td>{{ $template->type }}</td>
<td>{{ $template->check }}</td>
<td>{{ $template->param }}</td>
<td>{{ $template->ip }}</td>
<td>{{ $template->desc }}</td>
@@ -133,7 +129,7 @@
@foreach($device->serviceTemplates as $template)
<tr id="row_{{ $template->id }}">
<td>{{ $template->name }}</td>
<td>{{ $template->type }}</td>
<td>{{ $template->check }}</td>
<td>{{ $template->param }}</td>
<td>{{ $template->ip }}</td>
<td>{{ $template->desc }}</td>