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:
188
LibreNMS/Alerting/QueryBuilderFluentParser.php
Normal file
188
LibreNMS/Alerting/QueryBuilderFluentParser.php
Normal file
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
/**
|
||||
* QueryBuilderFluentParser.php
|
||||
*
|
||||
* -Description-
|
||||
*
|
||||
* 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 LibreNMS
|
||||
* @link http://librenms.org
|
||||
* @copyright 2019 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace LibreNMS\Alerting;
|
||||
|
||||
use DB;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Log;
|
||||
|
||||
class QueryBuilderFluentParser extends QueryBuilderParser
|
||||
{
|
||||
/**
|
||||
* Convert the query builder rules to a Laravel Fluent builder
|
||||
*
|
||||
* @return Builder
|
||||
*/
|
||||
public function toQuery()
|
||||
{
|
||||
if (empty($this->builder) || !array_key_exists('condition', $this->builder)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$query = DB::table('devices');
|
||||
|
||||
$this->joinTables($query);
|
||||
|
||||
$this->parseGroupToQuery($query, $this->builder);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Builder $query
|
||||
* @param array $rule
|
||||
* @param string $parent_condition AND or OR (for root, this should be null)
|
||||
* @return Builder
|
||||
*/
|
||||
protected function parseGroupToQuery($query, $rule, $parent_condition = null)
|
||||
{
|
||||
return $query->where(function ($query) use ($rule, $parent_condition) {
|
||||
foreach ($rule['rules'] as $group_rule) {
|
||||
if (array_key_exists('condition', $group_rule)) {
|
||||
$this->parseGroupToQuery($query, $group_rule, $rule['condition']);
|
||||
} else {
|
||||
$this->parseRuleToQuery($query, $group_rule, $rule['condition']);
|
||||
}
|
||||
}
|
||||
}, null, null, $parent_condition ?? $rule['condition']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Builder $query
|
||||
* @param array $rule
|
||||
* @param string $condition AND or OR
|
||||
* @return Builder
|
||||
*/
|
||||
protected function parseRuleToQuery($query, $rule, $condition)
|
||||
{
|
||||
list($field, $op, $value) = $this->expandRule($rule);
|
||||
|
||||
switch ($op) {
|
||||
case 'equal':
|
||||
case 'not_equal':
|
||||
case 'less':
|
||||
case 'less_or_equal':
|
||||
case 'greater':
|
||||
case 'greater_or_equal':
|
||||
case 'regex':
|
||||
case 'not_regex':
|
||||
return $query->where($field, self::$operators[$op], $value, $condition);
|
||||
case 'contains':
|
||||
case 'not_contains':
|
||||
return $query->where($field, self::$operators[$op], "%$value%", $condition);
|
||||
case 'begins_with':
|
||||
case 'not_begins_with':
|
||||
return $query->where($field, self::$operators[$op], "$value%", $condition);
|
||||
case 'ends_with':
|
||||
case 'not_ends_with':
|
||||
return $query->where($field, self::$operators[$op], "%$value", $condition);
|
||||
case 'is_empty':
|
||||
case 'is_not_empty':
|
||||
return $query->where($field, self::$operators[$op], '');
|
||||
case 'is_null':
|
||||
case 'is_not_null':
|
||||
return $query->whereNull($field, $condition, $op == 'is_not_null');
|
||||
case 'between':
|
||||
case 'not_between':
|
||||
return $query->whereBetween($field, $value, $condition, $op == 'not_between');
|
||||
case 'in':
|
||||
case 'not_in':
|
||||
$values = preg_split('/[, ]/', $value);
|
||||
if ($values !== false) {
|
||||
return $query->whereIn($field, $values, $condition, $op == 'not_in');
|
||||
}
|
||||
Log::error('Could not parse in values, use comma or space delimiters');
|
||||
break;
|
||||
default:
|
||||
Log::error('Unhandled QueryBuilderFluentParser operation: ' . $op);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract field, operator and value from the rule and expand macros and raw values
|
||||
*
|
||||
* @param array $rule
|
||||
* @return array [field, operator, value]
|
||||
*/
|
||||
protected function expandRule($rule)
|
||||
{
|
||||
$field = $rule['field'];
|
||||
if (starts_with($field, 'macros.')) {
|
||||
$field = DB::raw($this->expandMacro($field));
|
||||
}
|
||||
|
||||
$op = $rule['operator'];
|
||||
|
||||
$value = $rule['value'];
|
||||
if (!is_array($value) && starts_with($value, '`') && ends_with($value, '`')) {
|
||||
$value = DB::raw($this->expandMacro(trim($value, '`')));
|
||||
}
|
||||
|
||||
return [$field, $op, $value];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Builder $query
|
||||
* @return Builder
|
||||
*/
|
||||
protected function joinTables($query)
|
||||
{
|
||||
if (empty($this->builder['joins'])) {
|
||||
$this->generateJoins();
|
||||
}
|
||||
|
||||
foreach ($this->builder['joins'] as $join) {
|
||||
list($rightTable, $left, $right) = $join;
|
||||
$query->leftJoin($rightTable, $left, $right);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the joins for this rule and store them in the rule.
|
||||
* This is an expensive operation.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function generateJoins()
|
||||
{
|
||||
$joins = [];
|
||||
foreach ($this->generateGlue() as $glue) {
|
||||
list($left, $right) = explode(' = ', $glue, 2);
|
||||
if (str_contains($right, '.')) { // last line is devices.device_id = ? for alerting... ignore it
|
||||
list($rightTable, $rightKey) = explode('.', $right);
|
||||
$joins[] = [$rightTable, $left, $right];
|
||||
}
|
||||
}
|
||||
|
||||
$this->builder['joins'] = $joins;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user