mirror of
https://github.com/librenms/librenms.git
synced 2024-10-07 16:52:45 +00:00
Device groups rewrite (#10346)
* Device Groups rewrite Updated web ui Static or dynamic groups allowed Alert rule query builder Translation support Permissions support * cleanup, make relationship save, and validate it * builder WIP * rules builder and rules saving/loading * Parse query builder to Laravel Fluent query * Upgrade existing groups when editing. Properly update only dynamic groups when polling. * remove unused old code Update API and other places to use Eloquent * debug output in poller restored * Fix up some things creating static improved validation fix js error on creation Fix static groups in polling * hide pattern for static group * Implement authorization Use in the menu too * update schema * fix rollback * Don't abort on invalid queries * fixes to query builder * add test data, looks like macros aren't handled (omitted them because groups don't use them generally) * Add macro support for QueryBuilderFluentParser * add test for macro that accepts value * More space in forms Retain rules when converted to static no duplicate names allowed * Better error feedback Update related devices on save * Add button icon * format * update docs * fix tests * Fix some QueryBuilderFluentParser issues with OR updated/more test data * Show device groups runtime fix querybuilder.json format * Store table joins in the rules to minimize polling time Update group joins in daily.sh (and when they are saved) * Update daily.php * Add units to time
This commit is contained in:
29
resources/views/device-group/create.blade.php
Normal file
29
resources/views/device-group/create.blade.php
Normal file
@@ -0,0 +1,29 @@
|
||||
@extends('layouts.librenmsv1')
|
||||
|
||||
@section('title', __('Create Device Group'))
|
||||
|
||||
@section('content')
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<form action="{{ route('device-groups.store') }}" method="POST" role="form"
|
||||
class="form-horizontal device-group-form col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2 col-sm-12">
|
||||
<legend>@lang('Create Device Group')</legend>
|
||||
|
||||
@include('device-group.form')
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-sm-offset-2">
|
||||
<button type="submit" class="btn btn-primary">@lang('Save')</button>
|
||||
<a type="button" class="btn btn-danger"
|
||||
href="{{ route('device-groups.index') }}">@lang('Cancel')</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('javascript')
|
||||
<script src="{{ asset('js/sql-parser.min.js') }}"></script>
|
||||
<script src="{{ asset('js/query-builder.standalone.min.js') }}"></script>
|
||||
@endsection
|
30
resources/views/device-group/edit.blade.php
Normal file
30
resources/views/device-group/edit.blade.php
Normal file
@@ -0,0 +1,30 @@
|
||||
@extends('layouts.librenmsv1')
|
||||
|
||||
@section('title', __('Edit Device Group'))
|
||||
|
||||
@section('content')
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<form action="{{ route('device-groups.update', $device_group->id) }}" method="POST" role="form"
|
||||
class="form-horizontal device-group-form col-md-10 col-md-offset-1 col-sm-12">
|
||||
<legend>@lang('Edit Device Group'): {{ $device_group->name }}</legend>
|
||||
{{ method_field('PUT') }}
|
||||
|
||||
@include('device-group.form')
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-sm-offset-2">
|
||||
<button type="submit" class="btn btn-primary">@lang('Save')</button>
|
||||
<a type="button" class="btn btn-danger"
|
||||
href="{{ route('device-groups.index') }}">@lang('Cancel')</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('javascript')
|
||||
<script src="{{ asset('js/sql-parser.min.js') }}"></script>
|
||||
<script src="{{ asset('js/query-builder.standalone.min.js') }}"></script>
|
||||
@endsection
|
128
resources/views/device-group/form.blade.php
Normal file
128
resources/views/device-group/form.blade.php
Normal file
@@ -0,0 +1,128 @@
|
||||
<div class="form-group @if($errors->has('name')) has-error @endif">
|
||||
<label for="name" class="control-label col-sm-3 col-md-2 text-nowrap">@lang('Name')</label>
|
||||
<div class="col-sm-9 col-md-10">
|
||||
<input type="text" class="form-control" id="name" name="name" value="{{ old('name', $device_group->name) }}">
|
||||
<span class="help-block">{{ $errors->first('name') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group @if($errors->has('desc')) has-error @endif">
|
||||
<label for="desc" class="control-label col-sm-3 col-md-2 text-nowrap">@lang('Description')</label>
|
||||
<div class="col-sm-9 col-md-10">
|
||||
<input type="text" class="form-control" id="desc" name="desc" value="{{ old('desc', $device_group->desc) }}">
|
||||
<span class="help-block">{{ $errors->first('desc') }}</span>
|
||||
</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('Type')</label>
|
||||
<div class="col-sm-9 col-md-10">
|
||||
<select class="form-control" id="type" name="type" onchange="change_dg_type(this)">
|
||||
<option value="dynamic"
|
||||
@if(old('type', $device_group->type) == 'dynamic') selected @endif>@lang('Dynamic')</option>
|
||||
<option value="static"
|
||||
@if(old('type', $device_group->type) == 'static') selected @endif>@lang('Static')</option>
|
||||
</select>
|
||||
<span class="help-block">{{ $errors->first('type') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="dynamic-dg-form" class="form-group @if($errors->has('rules')) has-error @endif">
|
||||
<label for="pattern" class="control-label col-sm-3 col-md-2 text-nowrap">@lang('Define Rules')</label>
|
||||
<div class="col-sm-9 col-md-10">
|
||||
<div id="builder"></div>
|
||||
<span class="help-block">{{ $errors->first('rules') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="static-dg-form" class="form-group @if($errors->has('devices')) has-error @endif" style="display: none">
|
||||
<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>
|
||||
@foreach($device_group->devices as $device)
|
||||
<option value="{{ $device->device_id }}" selected>{{ $device->displayName() }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<span class="help-block">{{ $errors->first('devices') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function change_dg_type(select) {
|
||||
var type = select.options[select.selectedIndex].value;
|
||||
document.getElementById("dynamic-dg-form").style.display = (type === 'dynamic' ? 'block' : 'none');
|
||||
document.getElementById("static-dg-form").style.display = (type === 'dynamic' ? 'none' : 'block');
|
||||
}
|
||||
|
||||
change_dg_type(document.getElementById('type'));
|
||||
|
||||
init_select2('#devices', 'device', {multiple: true});
|
||||
|
||||
var builder = $('#builder').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'};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$('.device-group-form').submit(function (eventObj) {
|
||||
if ($('#type').val() === 'static') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!builder.queryBuilder('validate')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$('<input type="hidden" name="rules" />')
|
||||
.attr('value', JSON.stringify(builder.queryBuilder('getRules')))
|
||||
.appendTo(this);
|
||||
return true;
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
var rules = {!! json_encode(old('rules') ? json_decode(old('rules')) : $device_group->rules) !!};
|
||||
if (rules) {
|
||||
builder.queryBuilder('setRules', rules);
|
||||
}
|
||||
</script>
|
92
resources/views/device-group/index.blade.php
Normal file
92
resources/views/device-group/index.blade.php
Normal file
@@ -0,0 +1,92 @@
|
||||
@extends('layouts.librenmsv1')
|
||||
|
||||
@section('title', __('Device Groups'))
|
||||
|
||||
@section('content')
|
||||
<div class="container-fluid">
|
||||
<div id="manage-device-groups-panel" class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">
|
||||
<i class="fa fa-th fa-fw fa-lg" aria-hidden="true"></i> @lang('Device Groups')
|
||||
</h4>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<a type="button" class="btn btn-primary" href="{{ route('device-groups.create') }}">
|
||||
<i class="fa fa-plus"></i> @lang('New Device Group')
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table id="manage-device-groups-table" class="table table-condensed table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>@lang('Name')</th>
|
||||
<th>@lang('Description')</th>
|
||||
<th>@lang('Type')</th>
|
||||
<th>@lang('Devices')</th>
|
||||
<th>@lang('Pattern')</th>
|
||||
<th>@lang('Actions')</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($device_groups as $device_group)
|
||||
<tr id="row_{{ $device_group->id }}">
|
||||
<td>{{ $device_group->name }}</td>
|
||||
<td>{{ $device_group->desc }}</td>
|
||||
<td>{{ __(ucfirst($device_group->type)) }}</td>
|
||||
<td>
|
||||
<a href="{{ url("/devices/group=$device_group->id") }}">{{ $device_group->devices_count }}</a>
|
||||
</td>
|
||||
<td>{{ $device_group->type == 'dynamic' ? $device_group->getParser()->toSql(false) : '' }}</td>
|
||||
<td>
|
||||
<a type="button" class="btn btn-primary btn-sm" aria-label="@lang('Edit')"
|
||||
href="{{ route('device-groups.edit', $device_group->id) }}">
|
||||
<i class="fa fa-pencil" aria-hidden="true"></i></a>
|
||||
<button type="button" class="btn btn-danger btn-sm" aria-label="@lang('Delete')"
|
||||
onclick="delete_dg(this, '{{ $device_group->name }}', '{{ route('device-groups.destroy', $device_group->id) }}')">
|
||||
<i
|
||||
class="fa fa-trash" aria-hidden="true"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('scripts')
|
||||
<script>
|
||||
function delete_dg(button, name, url) {
|
||||
var index = button.parentNode.parentNode.rowIndex;
|
||||
|
||||
if (confirm('@lang('Are you sure you want to delete ')' + name + '?')) {
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: 'DELETE',
|
||||
success: function (msg) {
|
||||
document.getElementById("manage-device-groups-table").deleteRow(index);
|
||||
toastr.success(msg);
|
||||
},
|
||||
error: function () {
|
||||
toastr.error('@lang('The device group could not be deleted')');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
</script>
|
||||
@endsection
|
||||
|
||||
@section('css')
|
||||
<style>
|
||||
.table-responsive {
|
||||
padding-top: 16px
|
||||
}
|
||||
</style>
|
||||
@endsection
|
@@ -178,12 +178,11 @@
|
||||
</li>
|
||||
<li role="presentation" class="divider"></li>
|
||||
@endconfig
|
||||
|
||||
@notconfig('navbar.manage_groups.hide')
|
||||
<li><a href="{{ url('device-groups') }}"><i class="fa fa-th fa-fw fa-lg"
|
||||
aria-hidden="true"></i> @lang('Manage Groups')</a>
|
||||
</li>
|
||||
@endconfig
|
||||
@can('manage', \App\Models\DeviceGroup::class)
|
||||
<li><a href="{{ url('device-groups') }}"><i class="fa fa-th fa-fw fa-lg"
|
||||
aria-hidden="true"></i> @lang('Manage Groups')
|
||||
</a></li>
|
||||
@endcan
|
||||
<li><a href="{{ url('device-dependencies') }}"><i class="fa fa-group fa-fw fa-lg"></i> @lang('Device Dependencies')</a></li>
|
||||
@if($show_vmwinfo)
|
||||
<li><a href="{{ url('vminfo') }}"><i
|
||||
|
Reference in New Issue
Block a user