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:
parent
d8931e1946
commit
1a60c44eb0
|
@ -160,7 +160,6 @@ class QueryBuilderFilter implements \JsonSerializable
|
|||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Specify data which should be serialized to JSON
|
||||
* @link http://php.net/manual/en/jsonserializable.jsonserialize.php
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -30,7 +30,7 @@ use LibreNMS\DB\Schema;
|
|||
|
||||
class QueryBuilderParser implements \JsonSerializable
|
||||
{
|
||||
private static $legacy_operators = [
|
||||
protected static $legacy_operators = [
|
||||
'=' => 'equal',
|
||||
'!=' => 'not_equal',
|
||||
'~' => 'regex',
|
||||
|
@ -40,7 +40,7 @@ class QueryBuilderParser implements \JsonSerializable
|
|||
'<=' => 'less_or_equal',
|
||||
'>=' => 'greater_or_equal',
|
||||
];
|
||||
private static $operators = [
|
||||
protected static $operators = [
|
||||
'equal' => "=",
|
||||
'not_equal' => "!=",
|
||||
'less' => "<",
|
||||
|
@ -63,7 +63,7 @@ class QueryBuilderParser implements \JsonSerializable
|
|||
'not_regex' => 'NOT REGEXP',
|
||||
];
|
||||
|
||||
private static $values = [
|
||||
protected static $values = [
|
||||
'between' => "? AND ?",
|
||||
'not_between' => "? AND ?",
|
||||
'begins_with' => "'?%'",
|
||||
|
@ -78,8 +78,8 @@ class QueryBuilderParser implements \JsonSerializable
|
|||
'is_not_empty' => "''",
|
||||
];
|
||||
|
||||
private $builder;
|
||||
private $schema;
|
||||
protected $builder;
|
||||
protected $schema;
|
||||
|
||||
private function __construct(array $builder)
|
||||
{
|
||||
|
@ -107,7 +107,7 @@ class QueryBuilderParser implements \JsonSerializable
|
|||
* @param array $rules
|
||||
* @return array List of tables found in rules
|
||||
*/
|
||||
private function findTablesRecursive($rules)
|
||||
protected function findTablesRecursive($rules)
|
||||
{
|
||||
$tables = [];
|
||||
|
||||
|
@ -167,13 +167,17 @@ class QueryBuilderParser implements \JsonSerializable
|
|||
$split = array_chunk(preg_split('/(&&|\|\|)/', $query, -1, PREG_SPLIT_DELIM_CAPTURE), 2);
|
||||
|
||||
foreach ($split as $chunk) {
|
||||
list($rule_text, $rule_operator) = $chunk;
|
||||
if (count($chunk) < 2 && empty($chunk[0])) {
|
||||
continue; // likely the ending && or ||
|
||||
}
|
||||
|
||||
@list($rule_text, $rule_operator) = $chunk;
|
||||
if (!isset($condition)) {
|
||||
// only allow one condition. Since old rules had no grouping, this should hold logically
|
||||
$condition = ($rule_operator == '||' ? 'OR' : 'AND');
|
||||
}
|
||||
|
||||
list($field, $op, $value) = preg_split('/ *([!=<>~]{1,2}) */', trim($rule_text), 2, PREG_SPLIT_DELIM_CAPTURE);
|
||||
@list($field, $op, $value) = preg_split('/ *([!=<>~]{1,2}) */', trim($rule_text), 2, PREG_SPLIT_DELIM_CAPTURE);
|
||||
$field = ltrim($field, '%');
|
||||
|
||||
// for rules missing values just use '= 1'
|
||||
|
@ -181,11 +185,12 @@ class QueryBuilderParser implements \JsonSerializable
|
|||
if (is_null($value)) {
|
||||
$value = '1';
|
||||
} else {
|
||||
$value = trim($value, '"');
|
||||
|
||||
// value is a field, mark it with backticks
|
||||
if (starts_with($value, '%')) {
|
||||
$value = '`' . ltrim($value, '%') . '`';
|
||||
} else {
|
||||
// but if it has quotes just remove the %
|
||||
$value = ltrim(trim($value, '"'), '%');
|
||||
}
|
||||
|
||||
// replace regex placeholder, don't think we can safely convert to like operators
|
||||
|
@ -236,7 +241,7 @@ class QueryBuilderParser implements \JsonSerializable
|
|||
|
||||
if ($expand) {
|
||||
$sql = 'SELECT * FROM ' .implode(',', $this->getTables());
|
||||
$sql .= ' WHERE ' . $this->generateGlue() . ' AND ';
|
||||
$sql .= ' WHERE (' . implode(' AND ', $this->generateGlue()) . ') AND ';
|
||||
|
||||
// only wrap in ( ) if the condition is OR and there is more than one rule
|
||||
$wrap = $this->builder['condition'] == 'OR' && count($this->builder['rules']) > 1;
|
||||
|
@ -281,7 +286,7 @@ class QueryBuilderParser implements \JsonSerializable
|
|||
* @param bool $expand Expand macros?
|
||||
* @return string
|
||||
*/
|
||||
private function parseRule($rule, $expand = false)
|
||||
protected function parseRule($rule, $expand = false)
|
||||
{
|
||||
$field = $rule['field'];
|
||||
$builder_op = $rule['operator'];
|
||||
|
@ -320,7 +325,7 @@ class QueryBuilderParser implements \JsonSerializable
|
|||
* @param int $depth_limit
|
||||
* @return string|array
|
||||
*/
|
||||
private function expandMacro($subject, $tables_only = false, $depth_limit = 20)
|
||||
protected function expandMacro($subject, $tables_only = false, $depth_limit = 20)
|
||||
{
|
||||
if (!str_contains($subject, 'macros.')) {
|
||||
return $subject;
|
||||
|
@ -361,9 +366,9 @@ class QueryBuilderParser implements \JsonSerializable
|
|||
* Generate glue and first part of sql query for this rule
|
||||
*
|
||||
* @param string $target the name of the table to target, for alerting, this should be devices
|
||||
* @return string
|
||||
* @return array
|
||||
*/
|
||||
private function generateGlue($target = 'devices')
|
||||
protected function generateGlue($target = 'devices')
|
||||
{
|
||||
$tables = $this->getTables(); // get all tables in query
|
||||
|
||||
|
@ -382,9 +387,7 @@ class QueryBuilderParser implements \JsonSerializable
|
|||
}
|
||||
|
||||
// remove duplicates
|
||||
$glue = array_unique($glue);
|
||||
|
||||
return '(' . implode(' AND ', $glue) . ')';
|
||||
return array_unique($glue);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -214,7 +214,7 @@ class Schema
|
|||
return [$table, $target];
|
||||
}
|
||||
|
||||
$table_relations = $relationships[$table];
|
||||
$table_relations = $relationships[$table] ?? [];
|
||||
d_echo("Searching $table: " . json_encode($table_relations) . PHP_EOL);
|
||||
|
||||
if (!empty($table_relations)) {
|
||||
|
|
|
@ -0,0 +1,173 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\DeviceGroup;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Http\Request;
|
||||
use LibreNMS\Alerting\QueryBuilderFilter;
|
||||
use LibreNMS\Alerting\QueryBuilderFluentParser;
|
||||
use Toastr;
|
||||
|
||||
class DeviceGroupController extends Controller
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->authorizeResource(DeviceGroup::class, 'device_group');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->authorize('manage', DeviceGroup::class);
|
||||
|
||||
return view('device-group.index', [
|
||||
'device_groups' => DeviceGroup::orderBy('name')->withCount('devices')->get(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
return view('device-group.create', [
|
||||
'device_group' => new DeviceGroup(),
|
||||
'filters' => json_encode(new QueryBuilderFilter('group')),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'name' => 'required|string|unique:device_groups',
|
||||
'type' => 'required|in:dynamic,static',
|
||||
'devices' => 'array|required_if:type,static',
|
||||
'devices.*' => 'integer',
|
||||
'rules' => 'json|required_if:type,dynamic',
|
||||
]);
|
||||
|
||||
$deviceGroup = DeviceGroup::make($request->only(['name', 'desc', 'type']));
|
||||
$deviceGroup->rules = json_decode($request->rules);
|
||||
$deviceGroup->save();
|
||||
|
||||
if ($request->type == 'static') {
|
||||
$deviceGroup->devices()->sync($request->devices);
|
||||
}
|
||||
|
||||
Toastr::success(__('Device Group :name created', ['name' => $deviceGroup->name]));
|
||||
|
||||
return redirect()->route('device-groups.index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param \App\Models\DeviceGroup $deviceGroup
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function show(DeviceGroup $deviceGroup)
|
||||
{
|
||||
return redirect(url('/devices/group=' . $deviceGroup->id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*
|
||||
* @param \App\Models\DeviceGroup $deviceGroup
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function edit(DeviceGroup $deviceGroup)
|
||||
{
|
||||
// convert old rules on edit
|
||||
if (is_null($deviceGroup->rules)) {
|
||||
$query_builder = QueryBuilderFluentParser::fromOld($deviceGroup->pattern);
|
||||
$deviceGroup->rules = $query_builder->toArray();
|
||||
}
|
||||
|
||||
return view('device-group.edit', [
|
||||
'device_group' => $deviceGroup,
|
||||
'filters' => json_encode(new QueryBuilderFilter('group')),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \App\Models\DeviceGroup $deviceGroup
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function update(Request $request, DeviceGroup $deviceGroup)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'name' => [
|
||||
'required',
|
||||
'string',
|
||||
Rule::unique('device_groups')->where(function ($query) use ($deviceGroup) {
|
||||
$query->where('id', '!=', $deviceGroup->id);
|
||||
}),
|
||||
],
|
||||
'type' => 'required|in:dynamic,static',
|
||||
'devices' => 'array|required_if:type,static',
|
||||
'devices.*' => 'integer',
|
||||
'rules' => 'json|required_if:type,dynamic',
|
||||
]);
|
||||
|
||||
$deviceGroup->fill($request->only(['name', 'desc', 'type']));
|
||||
|
||||
$devices_updated = false;
|
||||
if ($deviceGroup->type == 'static') {
|
||||
// sync device_ids from input
|
||||
$devices_updated = array_sum($deviceGroup->devices()->sync($request->get('devices', [])));
|
||||
} else {
|
||||
$deviceGroup->rules = json_decode($request->rules);
|
||||
}
|
||||
|
||||
if ($deviceGroup->isDirty() || $devices_updated) {
|
||||
try {
|
||||
if ($deviceGroup->save() || $devices_updated) {
|
||||
Toastr::success(__('Device Group :name updated', ['name' => $deviceGroup->name]));
|
||||
} else {
|
||||
Toastr::error(__('Failed to save'));
|
||||
return redirect()->back()->withInput();
|
||||
}
|
||||
} catch (\Illuminate\Database\QueryException $e) {
|
||||
return redirect()->back()->withInput()->withErrors([
|
||||
'rules' => __('Rules resulted in invalid query: ') . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
Toastr::info(__('No changes made'));
|
||||
}
|
||||
|
||||
return redirect()->route('device-groups.index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param \App\Models\DeviceGroup $deviceGroup
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function destroy(DeviceGroup $deviceGroup)
|
||||
{
|
||||
$deviceGroup->delete();
|
||||
|
||||
Toastr::success(__('Device Group :name deleted', ['name' => $deviceGroup->name]));
|
||||
|
||||
return redirect()->route('device-groups.index');
|
||||
}
|
||||
}
|
|
@ -25,263 +25,101 @@
|
|||
|
||||
namespace App\Models;
|
||||
|
||||
use LibreNMS\Alerting\QueryBuilderFluentParser;
|
||||
use Log;
|
||||
use Permissions;
|
||||
use DB;
|
||||
|
||||
class DeviceGroup extends BaseModel
|
||||
{
|
||||
public $timestamps = false;
|
||||
protected $appends = ['patternSql'];
|
||||
protected $fillable = ['name', 'desc', 'pattern', 'params'];
|
||||
protected $casts = ['params' => 'array'];
|
||||
protected $fillable = ['name', 'desc', 'type'];
|
||||
protected $casts = ['rules' => 'array'];
|
||||
|
||||
public static function boot()
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
static::deleting(function (DeviceGroup $deviceGroup) {
|
||||
$deviceGroup->devices()->detach();
|
||||
});
|
||||
|
||||
static::saving(function (DeviceGroup $deviceGroup) {
|
||||
if ($deviceGroup->isDirty('rules')) {
|
||||
$deviceGroup->rules = $deviceGroup->getParser()->generateJoins()->toArray();
|
||||
$deviceGroup->updateDevices();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ---- Helper Functions ----
|
||||
|
||||
public function updateRelations()
|
||||
/**
|
||||
* Update devices included in this group (dynamic only)
|
||||
*/
|
||||
public function updateDevices()
|
||||
{
|
||||
// we need an id to add relationships
|
||||
if (is_null($this->id)) {
|
||||
$this->save();
|
||||
if ($this->type == 'dynamic') {
|
||||
$this->devices()->sync(QueryBuilderFluentParser::fromJSON($this->rules)->toQuery()
|
||||
->distinct()->pluck('devices.device_id'));
|
||||
}
|
||||
|
||||
$device_ids = $this->getDeviceIdsRaw();
|
||||
|
||||
// update the relationships (deletes and adds as needed)
|
||||
$this->devices()->sync($device_ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of the device ids from this group by re-querying the database with
|
||||
* either the specified pattern or the saved pattern of this group
|
||||
* Update the device groups for the given device or device_id
|
||||
*
|
||||
* @param string $statement Optional, will use the pattern from this group if not specified
|
||||
* @param array $params array of paremeters
|
||||
* @param Device|int $device
|
||||
* @return array
|
||||
*/
|
||||
public function getDeviceIdsRaw($statement = null, $params = null)
|
||||
public static function updateGroupsFor($device)
|
||||
{
|
||||
if (is_null($statement)) {
|
||||
$statement = $this->pattern;
|
||||
$device = ($device instanceof Device ? $device : Device::find($device));
|
||||
if (!$device instanceof Device) {
|
||||
// could not load device
|
||||
return [
|
||||
"attached" => [],
|
||||
"detached" => [],
|
||||
"updated" => [],
|
||||
];
|
||||
}
|
||||
|
||||
if (is_null($params)) {
|
||||
if (empty($this->params)) {
|
||||
if (!starts_with($statement, '%')) {
|
||||
// can't build sql
|
||||
return [];
|
||||
}
|
||||
} else {
|
||||
$params = $this->params;
|
||||
}
|
||||
}
|
||||
|
||||
$statement = $this->applyGroupMacros($statement);
|
||||
$tables = $this->getTablesFromPattern($statement);
|
||||
|
||||
$query = null;
|
||||
if (count($tables) == 1) {
|
||||
$query = DB::table($tables[0])->select('device_id')->distinct();
|
||||
} else {
|
||||
$query = DB::table('devices')->select('devices.device_id')->distinct();
|
||||
|
||||
foreach ($tables as $table) {
|
||||
// skip devices table, we used that as the base.
|
||||
if ($table == 'devices') {
|
||||
continue;
|
||||
$device_group_ids = static::query()
|
||||
->with(['devices' => function ($query) {
|
||||
$query->select('devices.device_id');
|
||||
}])
|
||||
->get()
|
||||
->filter(function ($device_group) use ($device) {
|
||||
/** @var DeviceGroup $device_group */
|
||||
if ($device_group->type == 'dynamic') {
|
||||
try {
|
||||
return $device_group->getParser()
|
||||
->toQuery()
|
||||
->where('devices.device_id', $device->device_id)
|
||||
->exists();
|
||||
} catch (\Illuminate\Database\QueryException $e) {
|
||||
Log::error("Device Group '$device_group->name' generates invalid query: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$query = $query->join($table, 'devices.device_id', '=', $table.'.device_id');
|
||||
}
|
||||
}
|
||||
// for static, if this device is include, keep it.
|
||||
return $device_group->devices
|
||||
->where('device_id', $device->device_id)
|
||||
->isNotEmpty();
|
||||
})->pluck('id');
|
||||
|
||||
// match the device ids
|
||||
if (is_null($params)) {
|
||||
return $query->whereRaw($statement)->pluck('device_id')->toArray();
|
||||
} else {
|
||||
return $query->whereRaw($statement, $params)->pluck('device_id')->toArray();
|
||||
}
|
||||
return $device->groups()->sync($device_group_ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process Macros
|
||||
* Get a query builder parser instance from this device group
|
||||
*
|
||||
* @param string $pattern Rule to process
|
||||
* @param int $x Recursion-Anchor, do not pass
|
||||
* @return string|boolean
|
||||
* @return QueryBuilderFluentParser
|
||||
*/
|
||||
public static function applyGroupMacros($pattern, $x = 1)
|
||||
public function getParser()
|
||||
{
|
||||
if (!str_contains($pattern, 'macros.')) {
|
||||
return $pattern;
|
||||
}
|
||||
|
||||
foreach (\LibreNMS\Config::get('alert.macros.group', []) as $macro => $value) {
|
||||
$value = str_replace(['%', '&&', '||'], ['', 'AND', 'OR'], $value); // this might need something more complex
|
||||
if (!str_contains($macro, ' ')) {
|
||||
$pattern = str_replace('macros.'.$macro, '('.$value.')', $pattern);
|
||||
}
|
||||
}
|
||||
|
||||
if (str_contains($pattern, 'macros.')) {
|
||||
if (++$x < 30) {
|
||||
$pattern = self::applyGroupMacros($pattern, $x);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return $pattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract an array of tables in a pattern
|
||||
*
|
||||
* @param string $pattern
|
||||
* @return array
|
||||
*/
|
||||
private function getTablesFromPattern($pattern)
|
||||
{
|
||||
preg_match_all('/[A-Za-z_]+(?=\.[A-Za-z_]+ )/', $pattern, $tables);
|
||||
if (is_null($tables)) {
|
||||
return [];
|
||||
}
|
||||
return array_keys(array_flip($tables[0])); // unique tables only
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a v1 device group pattern to sql that can be ingested by jQuery-QueryBuilder
|
||||
*
|
||||
* @param $pattern
|
||||
* @return array
|
||||
*/
|
||||
private function convertV1Pattern($pattern)
|
||||
{
|
||||
$pattern = rtrim($pattern, ' &&');
|
||||
$pattern = rtrim($pattern, ' ||');
|
||||
|
||||
$ops = ['=', '!=', '<', '<=', '>', '>='];
|
||||
$parts = str_getcsv($pattern, ' '); // tokenize the pattern, respecting quoted parts
|
||||
$out = "";
|
||||
|
||||
$count = count($parts);
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$cur = $parts[$i];
|
||||
|
||||
if (starts_with($cur, '%')) {
|
||||
// table and column or macro
|
||||
$out .= substr($cur, 1).' ';
|
||||
} elseif (substr($cur, -1) == '~') {
|
||||
// like operator
|
||||
$content = $parts[++$i]; // grab the content so we can format it
|
||||
|
||||
if (starts_with($cur, '!')) {
|
||||
// prepend NOT
|
||||
$out .= 'NOT ';
|
||||
}
|
||||
|
||||
$out .= "LIKE('".$this->convertRegexToLike($content)."') ";
|
||||
} elseif ($cur == '&&') {
|
||||
$out .= 'AND ';
|
||||
} elseif ($cur == '||') {
|
||||
$out .= 'OR ';
|
||||
} elseif (in_array($cur, $ops)) {
|
||||
// pass-through operators
|
||||
$out .= $cur.' ';
|
||||
} else {
|
||||
// user supplied input
|
||||
$out .= "'".trim($cur, '"\'')."' "; // TODO: remove trim, only needed with invalid input
|
||||
}
|
||||
}
|
||||
return rtrim($out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert sql regex to like, many common uses can be converted
|
||||
* Should only be used to convert v1 patterns
|
||||
*
|
||||
* @param $pattern
|
||||
* @return string
|
||||
*/
|
||||
private function convertRegexToLike($pattern)
|
||||
{
|
||||
$startAnchor = starts_with($pattern, '^');
|
||||
$endAnchor = ends_with($pattern, '$');
|
||||
|
||||
$pattern = trim($pattern, '^$');
|
||||
|
||||
$wildcards = ['@', '.*'];
|
||||
if (str_contains($pattern, $wildcards)) {
|
||||
// contains wildcard
|
||||
$pattern = str_replace($wildcards, '%', $pattern);
|
||||
}
|
||||
|
||||
// add ends appropriately
|
||||
if ($startAnchor && !$endAnchor) {
|
||||
$pattern .= '%';
|
||||
} elseif (!$startAnchor && $endAnchor) {
|
||||
$pattern = '%'.$pattern;
|
||||
}
|
||||
|
||||
// if there are no wildcards, assume substring
|
||||
if (!str_contains($pattern, '%')) {
|
||||
$pattern = '%'.$pattern.'%';
|
||||
}
|
||||
|
||||
return $pattern;
|
||||
}
|
||||
|
||||
// ---- Accessors/Mutators ----
|
||||
|
||||
/**
|
||||
* Returns an sql formatted string
|
||||
* Mostly, this is for ingestion by JQuery-QueryBuilder
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPatternSqlAttribute()
|
||||
{
|
||||
$sql = $this->pattern;
|
||||
|
||||
// fill in parameters
|
||||
foreach ((array)$this->params as $value) {
|
||||
if (!is_numeric($value) && !starts_with($value, "'")) {
|
||||
$value = "'".$value."'";
|
||||
}
|
||||
$sql = preg_replace('/\?/', $value, $sql, 1);
|
||||
}
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom mutator for params attribute
|
||||
* Allows already encoded json to pass through
|
||||
*
|
||||
* @param array|string $params
|
||||
*/
|
||||
// public function setParamsAttribute($params)
|
||||
// {
|
||||
// if (!Util::isJson($params)) {
|
||||
// $params = json_encode($params);
|
||||
// }
|
||||
//
|
||||
// $this->attributes['params'] = $params;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Check if the stored pattern is v1
|
||||
* Convert it to v2 for display
|
||||
* Currently, it will only be updated in the database if the user saves the rule in the ui
|
||||
*
|
||||
* @param $pattern
|
||||
* @return string
|
||||
*/
|
||||
public function getPatternAttribute($pattern)
|
||||
{
|
||||
// If this is a v1 pattern, convert it to sql
|
||||
if (starts_with($pattern, '%')) {
|
||||
return $this->convertV1Pattern($pattern);
|
||||
}
|
||||
|
||||
return $pattern;
|
||||
return !empty($this->rules) ?
|
||||
QueryBuilderFluentParser::fromJson($this->rules) :
|
||||
QueryBuilderFluentParser::fromOld($this->pattern);
|
||||
}
|
||||
|
||||
// ---- Query Scopes ----
|
||||
|
@ -297,17 +135,6 @@ class DeviceGroup extends BaseModel
|
|||
|
||||
// ---- Define Relationships ----
|
||||
|
||||
|
||||
public function alertSchedules()
|
||||
{
|
||||
return $this->morphToMany('App\Models\AlertSchedule', 'alert_schedulable', 'alert_schedulables', 'schedule_id', 'schedule_id');
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return $this->belongsToMany('App\Models\AlertRule', 'alert_group_map', 'group_id', 'rule_id');
|
||||
}
|
||||
|
||||
public function devices()
|
||||
{
|
||||
return $this->belongsToMany('App\Models\Device', 'device_group_device', 'device_group_id', 'device_id');
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\DeviceGroup;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class DeviceGroupPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function before($user, $ability)
|
||||
{
|
||||
if ($user->isAdmin()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can manage device groups.
|
||||
*
|
||||
* @param \App\Models\User $user
|
||||
* @return bool
|
||||
*/
|
||||
public function manage(User $user)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can view the device group.
|
||||
*
|
||||
* @param \App\Models\User $user
|
||||
* @param \App\Models\DeviceGroup $deviceGroup
|
||||
* @return mixed
|
||||
*/
|
||||
public function view(User $user, DeviceGroup $deviceGroup)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can create device groups.
|
||||
*
|
||||
* @param \App\Models\User $user
|
||||
* @return mixed
|
||||
*/
|
||||
public function create(User $user)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can update the device group.
|
||||
*
|
||||
* @param \App\Models\User $user
|
||||
* @param \App\Models\DeviceGroup $deviceGroup
|
||||
* @return mixed
|
||||
*/
|
||||
public function update(User $user, DeviceGroup $deviceGroup)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can delete the device group.
|
||||
*
|
||||
* @param \App\Models\User $user
|
||||
* @param \App\Models\DeviceGroup $deviceGroup
|
||||
* @return mixed
|
||||
*/
|
||||
public function delete(User $user, DeviceGroup $deviceGroup)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can restore the device group.
|
||||
*
|
||||
* @param \App\Models\User $user
|
||||
* @param \App\Models\DeviceGroup $deviceGroup
|
||||
* @return mixed
|
||||
*/
|
||||
public function restore(User $user, DeviceGroup $deviceGroup)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can permanently delete the device group.
|
||||
*
|
||||
* @param \App\Models\User $user
|
||||
* @param \App\Models\DeviceGroup $deviceGroup
|
||||
* @return mixed
|
||||
*/
|
||||
public function forceDelete(User $user, DeviceGroup $deviceGroup)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -2,7 +2,9 @@
|
|||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Models\DeviceGroup;
|
||||
use App\Models\User;
|
||||
use App\Policies\DeviceGroupPolicy;
|
||||
use App\Policies\UserPolicy;
|
||||
use App\Guards\ApiTokenGuard;
|
||||
use Auth;
|
||||
|
@ -17,7 +19,8 @@ class AuthServiceProvider extends ServiceProvider
|
|||
* @var array
|
||||
*/
|
||||
protected $policies = [
|
||||
User::class => UserPolicy::class
|
||||
User::class => UserPolicy::class,
|
||||
DeviceGroup::class => DeviceGroupPolicy::class,
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
21
daily.php
21
daily.php
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
use App\Models\Device;
|
||||
use App\Models\DeviceGroup;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use LibreNMS\Config;
|
||||
use LibreNMS\Exceptions\LockException;
|
||||
|
@ -287,6 +288,26 @@ if ($options['f'] === 'refresh_alert_rules') {
|
|||
}
|
||||
}
|
||||
|
||||
if ($options['f'] === 'refresh_device_groups') {
|
||||
try {
|
||||
if (Config::get('distributed_poller')) {
|
||||
MemcacheLock::lock('refresh_device_groups', 0, 86000);
|
||||
}
|
||||
|
||||
echo 'Refreshing device group table relationships' . PHP_EOL;
|
||||
DeviceGroup::all()->each(function ($deviceGroup) {
|
||||
if ($deviceGroup->type == 'dynamic') {
|
||||
/** @var DeviceGroup $deviceGroup */
|
||||
$deviceGroup->rules = $deviceGroup->getParser()->generateJoins()->toArray();
|
||||
$deviceGroup->save();
|
||||
}
|
||||
});
|
||||
} catch (LockException $e) {
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
if ($options['f'] === 'notify') {
|
||||
if (isset($config['alert']['default_mail'])) {
|
||||
send_mail(
|
||||
|
|
1
daily.sh
1
daily.sh
|
@ -262,6 +262,7 @@ main () {
|
|||
# Cleanups
|
||||
local options=("refresh_alert_rules"
|
||||
"refresh_os_cache"
|
||||
"refresh_device_groups"
|
||||
"recalculate_device_dependencies"
|
||||
"syslog"
|
||||
"eventlog"
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class DeviceGroupsRewrite extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('device_groups', function (Blueprint $table) {
|
||||
$table->string('desc')->nullable()->change();
|
||||
$table->string('type', 16)->default('dynamic')->after('desc');
|
||||
$table->text('rules')->nullable()->after('type');
|
||||
$table->dropColumn('params');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('device_groups', function (Blueprint $table) {
|
||||
$table->string('desc')->change();
|
||||
$table->dropColumn('type');
|
||||
$table->dropColumn('rules');
|
||||
$table->text('params')->nullable()->after('pattern');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -3,33 +3,24 @@ path: blob/master/doc/
|
|||
|
||||
LibreNMS supports grouping your devices together in much the same way as you can configure alerts. This document will hopefully help you get started.
|
||||
|
||||
### Patterns
|
||||
# Dynamic Groups
|
||||
|
||||
Patterns work in the same way as Entities within the alerting system, the format of the pattern is based on the MySQL structure your data is in. Such
|
||||
as __tablename.columnname__. If you already know the entity you want, you can browse around inside MySQL using `show tables` and `desc <tablename>`.
|
||||
### Rule Editor
|
||||
|
||||
As a working example and a common question, let's assume you want to group devices by hostname. If your hostname format is dcX.[devicetype].example.com. You would use the pattern
|
||||
`devices.hostname`. Select the condition which in this case would be `Like` and then enter `dc1\..*\.example.com`. This would then match dc1.sw01.example.com, dc1.rtr01.example.com but not
|
||||
dc2.sw01.example.com.
|
||||
The rule is based on the MySQL structure your data is in. Such as __tablename.columnname__.
|
||||
If you already know the entity you want, you can browse around inside MySQL using `show tables` and `desc <tablename>`.
|
||||
|
||||
#### Wildcards
|
||||
As a working example and a common question, let's assume you want to group devices by hostname. If your hostname format is dcX.[devicetype].example.com. You would use the field
|
||||
`devices.hostname`.
|
||||
|
||||
As with alerts, the `Like` operation allows MySQL Regular expressions.
|
||||
If you want to group them by device type, you would add a rule for routers of `devices.hostname` endswith `rtr.example.com`.
|
||||
|
||||
A list of common entities is maintained in our [Alerting docs](/Alerting/Entities/).
|
||||
If you want to group them by DC, you could use the rule `devices.hostname` regex `dc1\..*\.example\.com` (Don't forget to escape periods in the regex)
|
||||
|
||||
### Conditions
|
||||
# Static Groups
|
||||
|
||||
Please see our [Alerting docs](/Alerting/Rules/#syntax) for explanations.
|
||||
|
||||
### Connection
|
||||
|
||||
If you only want to group based on one pattern then select And. If however you want to build a group based on multiple patterns then you can build a SQL like
|
||||
query using And / Or.
|
||||
|
||||
As an example, we want to base our group on the devices hostname AND it's type. Use the pattern as before, `devices.hostname`, select
|
||||
the condition which in this case would be `Like` and then enter `dc1.@.example.com` then click And. Now enter `devices.type` in the pattern, select `Equals`
|
||||
and enter `firewall`. This would then match dc1.fw01.example.com but not dc1.sw01.example.com as that is a network type.
|
||||
You can create static groups (and convert dynamic groups to static) to put specific devices in a group.
|
||||
Just select static as the type and select the devices you want in the group.
|
||||
|
||||
![Device Groups](/img/device_groups.png)
|
||||
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 56 KiB |
|
@ -1,138 +0,0 @@
|
|||
<?php
|
||||
/*
|
||||
* Copyright (C) 2014 Daniel Preussker <f0o@devilcode.org>
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Rule Suggestion-AJAX
|
||||
* @author Daniel Preussker <f0o@devilcode.org>
|
||||
* @copyright 2014 f0o, LibreNMS
|
||||
* @license GPL
|
||||
* @package LibreNMS/Alerts
|
||||
*/
|
||||
|
||||
use LibreNMS\Authentication\LegacyAuth;
|
||||
|
||||
$init_modules = array('web', 'auth');
|
||||
require realpath(__DIR__ . '/..') . '/includes/init.php';
|
||||
|
||||
if (!LegacyAuth::check()) {
|
||||
die('Unauthorized');
|
||||
}
|
||||
|
||||
set_debug($_REQUEST['debug']);
|
||||
|
||||
/**
|
||||
* Levenshtein Sort
|
||||
* @param string $base Comparison basis
|
||||
* @param array $obj Object to sort
|
||||
* @return array
|
||||
*/
|
||||
function levsort($base, $obj)
|
||||
{
|
||||
$ret = array();
|
||||
foreach ($obj as $elem) {
|
||||
$lev = levenshtein($base, $elem, 1, 10, 10);
|
||||
if ($lev == 0) {
|
||||
return array(array('name' => $elem));
|
||||
} else {
|
||||
while (isset($ret["$lev"])) {
|
||||
$lev += 0.1;
|
||||
}
|
||||
|
||||
$ret["$lev"] = array('name' => $elem);
|
||||
}
|
||||
}
|
||||
|
||||
ksort($ret);
|
||||
return $ret;
|
||||
}
|
||||
|
||||
header('Content-type: application/json');
|
||||
$obj = array(array('name' => 'Error: No suggestions found.'));
|
||||
$term = array();
|
||||
$current = false;
|
||||
if (isset($_GET['term'], $_GET['device_id'])) {
|
||||
$chk = array();
|
||||
$_GET['term'] = mres($_GET['term']);
|
||||
$_GET['device_id'] = mres($_GET['device_id']);
|
||||
if (strstr($_GET['term'], '.')) {
|
||||
$term = explode('.', $_GET['term']);
|
||||
if ($term[0] == 'macros') {
|
||||
foreach ($config['alert']['macros']['rule'] as $macro => $v) {
|
||||
$chk[] = 'macros.'.$macro;
|
||||
}
|
||||
} else {
|
||||
$tmp = dbFetchRows('SHOW COLUMNS FROM '.$term[0]);
|
||||
foreach ($tmp as $tst) {
|
||||
if (isset($tst['Field'])) {
|
||||
$chk[] = $term[0].'.'.$tst['Field'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$current = true;
|
||||
} else {
|
||||
$tmp = dbFetchRows("SELECT TABLE_NAME FROM information_schema.COLUMNS WHERE COLUMN_NAME = 'device_id'");
|
||||
foreach ($tmp as $tst) {
|
||||
$chk[] = $tst['TABLE_NAME'].'.';
|
||||
}
|
||||
|
||||
$chk[] = 'macros.';
|
||||
$chk[] = 'bills.';
|
||||
}
|
||||
if (sizeof($chk) > 0) {
|
||||
$obj = levsort($_GET['term'], $chk);
|
||||
$obj = array_chunk($obj, 20, true);
|
||||
$obj = $obj[0];
|
||||
$flds = array();
|
||||
if ($current === true) {
|
||||
foreach ($obj as $fld) {
|
||||
$flds[] = $fld['name'];
|
||||
}
|
||||
|
||||
$qry = dbFetchRows('SELECT '.implode(', ', $flds).' FROM '.$term[0].' WHERE device_id = ?', array($_GET['device_id']));
|
||||
$ret = array();
|
||||
foreach ($obj as $lev => $fld) {
|
||||
list($tbl, $chk) = explode('.', $fld['name']);
|
||||
$val = array();
|
||||
foreach ($qry as $row) {
|
||||
$val[] = $row[$chk];
|
||||
}
|
||||
|
||||
$ret[$lev] = array(
|
||||
'name' => $fld['name'],
|
||||
'current' => $val,
|
||||
);
|
||||
}
|
||||
|
||||
$obj = $ret;
|
||||
}
|
||||
}
|
||||
} elseif ($vars['type'] === 'alert_rule_collection') {
|
||||
$x=0;
|
||||
foreach (get_rules_from_json() as $rule) {
|
||||
if (str_i_contains($rule['name'], $vars['term'])) {
|
||||
$rule['id'] = $x;
|
||||
$tmp[] = $rule;
|
||||
}
|
||||
$x++;
|
||||
}
|
||||
if (is_array($tmp)) {
|
||||
$obj = $tmp;
|
||||
}
|
||||
}
|
||||
|
||||
die(json_encode($obj));
|
|
@ -1833,21 +1833,3 @@ function array_by_column($array, $column)
|
|||
{
|
||||
return array_combine(array_column($array, $column), $array);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all consecutive pairs of values in an array.
|
||||
* [1,2,3,4] -> [[1,2],[2,3],[3,4]]
|
||||
*
|
||||
* @param array $array
|
||||
* @return array
|
||||
*/
|
||||
function array_pairs($array)
|
||||
{
|
||||
$pairs = [];
|
||||
|
||||
for ($i = 1; $i < count($array); $i++) {
|
||||
$pairs[] = [$array[$i -1], $array[$i]];
|
||||
}
|
||||
|
||||
return $pairs;
|
||||
}
|
||||
|
|
|
@ -959,9 +959,6 @@ $config['leaflet']['tile_url'] = "{s}.tile.openstreetma
|
|||
// General GUI options
|
||||
$config['gui']['network-map']['style'] = 'new';//old is also valid
|
||||
|
||||
// Navbar variables
|
||||
$config['navbar']['manage_groups']['hide'] = 0;
|
||||
|
||||
// Show errored ports in the summary table on the dashboard
|
||||
$config['summary_errors'] = 0;
|
||||
|
||||
|
|
|
@ -1,371 +0,0 @@
|
|||
<?php
|
||||
/*
|
||||
* Copyright (C) 2015 Daniel Preussker <f0o@devilcode.org>
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Device-Grouping
|
||||
* @author Daniel Preussker <f0o@devilcode.org>
|
||||
* @author Tony Murray <murrayotony@gmail.com>
|
||||
* @copyright 2016 f0o, murrant, LibreNMS
|
||||
* @license GPL
|
||||
* @package LibreNMS
|
||||
* @subpackage Devices
|
||||
*/
|
||||
|
||||
use LibreNMS\Config;
|
||||
|
||||
/**
|
||||
* Add a new device group
|
||||
* @param $pattern
|
||||
* @param $name
|
||||
* @param $desc
|
||||
* @return int|string
|
||||
*/
|
||||
function AddDeviceGroup($name, $desc, $pattern)
|
||||
{
|
||||
$group_id = dbInsert(array('name' => $name, 'desc' => $desc, 'pattern' => $pattern), 'device_groups');
|
||||
if ($group_id) {
|
||||
UpdateDeviceGroup($group_id);
|
||||
}
|
||||
return $group_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a device group
|
||||
* @param $group_id
|
||||
* @param $pattern
|
||||
* @param $name
|
||||
* @param $desc
|
||||
* @return bool
|
||||
*/
|
||||
function EditDeviceGroup($group_id, $name = null, $desc = null, $pattern = null)
|
||||
{
|
||||
$vars = array();
|
||||
if (!is_null($name)) {
|
||||
$vars['name'] = $name;
|
||||
}
|
||||
if (!is_null($desc)) {
|
||||
$vars['desc'] = $desc;
|
||||
}
|
||||
if (!is_null($pattern)) {
|
||||
$vars['pattern'] = $pattern;
|
||||
}
|
||||
|
||||
$success = dbUpdate($vars, 'device_groups', 'id=?', array($group_id)) >= 0;
|
||||
|
||||
if ($success) {
|
||||
UpdateDeviceGroup($group_id);
|
||||
}
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate SQL from Group-Pattern
|
||||
* @param string $pattern Pattern to generate SQL for
|
||||
* @param string $search What to searchid for
|
||||
* @param int $extra
|
||||
* @return string sql to perform the search
|
||||
*/
|
||||
function GenGroupSQL($pattern, $search = '', $extra = 0)
|
||||
{
|
||||
$pattern = RunGroupMacros($pattern);
|
||||
if ($pattern === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (starts_with($pattern, '%')) {
|
||||
// v1 pattern
|
||||
$tables = array();
|
||||
$words = explode(' ', $pattern);
|
||||
foreach ($words as $word) {
|
||||
if (starts_with($word, '%') && str_contains($word, '.')) {
|
||||
list($table, $column) = explode('.', $word, 2);
|
||||
$table = str_replace('%', '', $table);
|
||||
$tables[] = mres(str_replace('(', '', $table));
|
||||
$pattern = str_replace($word, $table . '.' . $column, $pattern);
|
||||
}
|
||||
}
|
||||
$tables = array_keys(array_flip($tables));
|
||||
} else {
|
||||
// v2 pattern
|
||||
$tables = getTablesFromPattern($pattern);
|
||||
}
|
||||
|
||||
$pattern = rtrim($pattern, '&|');
|
||||
|
||||
if ($tables[0] != 'devices' && dbFetchCell('SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_NAME = ? && COLUMN_NAME = ?', array($tables[0],'device_id')) != 1) {
|
||||
//Our first table has no valid glue, prepend the 'devices' table to it!
|
||||
array_unshift($tables, 'devices');
|
||||
$tables = array_unique($tables); // remove devices from later in the array if it exists
|
||||
}
|
||||
$x = sizeof($tables)-1;
|
||||
$i = 0;
|
||||
$join = "";
|
||||
while ($i < $x) {
|
||||
if (isset($tables[$i+1])) {
|
||||
$gtmp = ResolveGlues(array($tables[$i+1]), 'device_id');
|
||||
if ($gtmp === false) {
|
||||
//Cannot resolve glue-chain. Rule is invalid.
|
||||
return false;
|
||||
}
|
||||
$last = "";
|
||||
$qry = "";
|
||||
foreach ($gtmp as $glue) {
|
||||
if (empty($last)) {
|
||||
list($tmp,$last) = explode('.', $glue);
|
||||
$qry .= $glue.' = ';
|
||||
} else {
|
||||
$parts = explode('.', $glue);
|
||||
if (count($parts) == 3) {
|
||||
list($tmp, $new, $last) = $parts;
|
||||
} else {
|
||||
list($tmp,$new) = $parts;
|
||||
}
|
||||
$qry .= $tmp.'.'.$last.' && '.$tmp.'.'.$new.' = ';
|
||||
$last = $new;
|
||||
}
|
||||
if (!in_array($tmp, $tables)) {
|
||||
$tables[] = $tmp;
|
||||
}
|
||||
}
|
||||
$join .= "( ".$qry.$tables[0].".device_id ) && ";
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
if ($extra === 1) {
|
||||
$sql_extra = ",`devices`.*";
|
||||
}
|
||||
if (!empty($search)) {
|
||||
$search = str_replace("(", "", $tables[0]).'.'.$search. ' AND';
|
||||
}
|
||||
if (!empty($join)) {
|
||||
$join = '('.rtrim($join, '& ').') &&';
|
||||
}
|
||||
$sql = 'SELECT DISTINCT('.str_replace('(', '', $tables[0]).'.device_id)'.$sql_extra.' FROM '.implode(',', $tables).' WHERE '.$join.' '.$search.' ('.str_replace(array('%', '@', '!~', '~'), array('', '.*', 'NOT REGEXP', 'REGEXP'), $pattern).')';
|
||||
return $sql;
|
||||
}//end GenGroupSQL()
|
||||
|
||||
|
||||
/**
|
||||
* Extract an array of tables in a pattern
|
||||
*
|
||||
* @param string $pattern
|
||||
* @return array
|
||||
*/
|
||||
function getTablesFromPattern($pattern)
|
||||
{
|
||||
preg_match_all('/[A-Za-z_]+(?=\.[A-Za-z_]+ )/', $pattern, $tables);
|
||||
if (is_null($tables)) {
|
||||
return array();
|
||||
}
|
||||
return array_keys(array_flip($tables[0])); // unique tables only
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the group queries again to get fresh list of devices for this group
|
||||
* @param integer $group_id Group-ID
|
||||
* @return string
|
||||
*/
|
||||
function QueryDevicesFromGroup($group_id)
|
||||
{
|
||||
$group = dbFetchRow('SELECT pattern,params FROM device_groups WHERE id = ?', array($group_id));
|
||||
$pattern = rtrim($group['pattern'], '&|');
|
||||
$params = (array)json_decode($group['params']);
|
||||
if (!empty($pattern)) {
|
||||
$result = dbFetchColumn(GenGroupSQL($pattern), $params);
|
||||
return $result;
|
||||
}
|
||||
|
||||
return false;
|
||||
}//end QueryDevicesFromGroup()
|
||||
|
||||
/**
|
||||
* Get an array of all the device ids belonging to this group_id
|
||||
* @param $group_id
|
||||
* @param bool $nested Return an array of arrays containing 'device_id'. (for API compatibility)
|
||||
* @param string $full Return all fields from devices_id
|
||||
* @return array
|
||||
*/
|
||||
function GetDevicesFromGroup($group_id, $nested = false, $full = '')
|
||||
{
|
||||
if ($full) {
|
||||
$query = 'SELECT `device_groups`.`name`, `devices`.* FROM `device_groups` INNER JOIN `device_group_device` ON `device_groups`.`id` = `device_group_device`.`device_group_id` INNER JOIN `devices` ON `device_group_device`.`device_id` = `devices`.`device_id` WHERE `device_groups`.`id` = ?';
|
||||
} else {
|
||||
$query = 'SELECT `device_id` FROM `device_group_device` WHERE `device_group_id` = ? ';
|
||||
}
|
||||
if ($nested) {
|
||||
return dbFetchRows($query, array($group_id));
|
||||
} else {
|
||||
return dbFetchColumn($query, array($group_id));
|
||||
}
|
||||
}//end GetDevicesFromGroup()
|
||||
|
||||
/**
|
||||
* Get all Device-Groups
|
||||
* @return array
|
||||
*/
|
||||
function GetDeviceGroups()
|
||||
{
|
||||
return dbFetchRows('SELECT * FROM device_groups ORDER BY name');
|
||||
}//end GetDeviceGroups()
|
||||
|
||||
/**
|
||||
* Run the group queries again to get fresh list of groups for this device
|
||||
* @param integer $device_id Device-ID
|
||||
* @param int $extra Return extra info about the groups (name, desc, pattern)
|
||||
* @return array
|
||||
*/
|
||||
function QueryGroupsFromDevice($device_id, $extra = 0)
|
||||
{
|
||||
$ret = array();
|
||||
foreach (GetDeviceGroups() as $group) {
|
||||
$params = (array)json_decode($group['params']);
|
||||
array_unshift($params, $device_id);
|
||||
if (dbFetchCell(GenGroupSQL($group['pattern'], 'device_id=?', $extra).' LIMIT 1', $params) == $device_id) {
|
||||
if ($extra === 0) {
|
||||
$ret[] = $group['id'];
|
||||
} else {
|
||||
$ret[] = $group;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}//end QueryGroupsFromDevice()
|
||||
|
||||
/**
|
||||
* Get the Device Group IDs of a Device from the database
|
||||
* @param $device_id
|
||||
* @param int $extra Return extra info about the groups (name, desc, pattern)
|
||||
* @return array
|
||||
*/
|
||||
function GetGroupsFromDevice($device_id, $extra = 0)
|
||||
{
|
||||
$ret = array();
|
||||
if ($extra === 0) {
|
||||
$ret = dbFetchColumn('SELECT `device_group_id` FROM `device_group_device` WHERE `device_id`=?', array($device_id));
|
||||
} else {
|
||||
$ret = dbFetchRows('SELECT `device_groups`.* FROM `device_group_device` LEFT JOIN `device_groups` ON `device_group_device`.`device_group_id`=`device_groups`.`id` WHERE `device_group_device`.`device_id`=?', array($device_id));
|
||||
}
|
||||
return $ret;
|
||||
}//end GetGroupsFromDeviceDB()
|
||||
|
||||
/**
|
||||
* Process Macros
|
||||
* @param string $rule Rule to process
|
||||
* @param int $x Recursion-Anchor
|
||||
* @return string|boolean
|
||||
*/
|
||||
function RunGroupMacros($rule, $x = 1)
|
||||
{
|
||||
$macros = Config::get('alert.macros.group', []);
|
||||
|
||||
krsort($macros);
|
||||
foreach ($macros as $macro => $value) {
|
||||
if (!strstr($macro, " ")) {
|
||||
$rule = str_replace('%macros.'.$macro, '('.$value.')', $rule);
|
||||
}
|
||||
}
|
||||
if (strstr($rule, "%macros")) {
|
||||
if (++$x < 30) {
|
||||
$rule = RunGroupMacros($rule, $x);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return $rule;
|
||||
}//end RunGroupMacros()
|
||||
|
||||
|
||||
/**
|
||||
* Update device-group relationship for the given device id
|
||||
* @param $device_id
|
||||
*/
|
||||
function UpdateGroupsForDevice($device_id)
|
||||
{
|
||||
d_echo("### Start Device Groups ###\n");
|
||||
$queried_groups = QueryGroupsFromDevice($device_id);
|
||||
$db_groups = GetGroupsFromDevice($device_id);
|
||||
|
||||
// compare the arrays to get the added and removed groups
|
||||
$added_groups = array_diff($queried_groups, $db_groups);
|
||||
$removed_groups = array_diff($db_groups, $queried_groups);
|
||||
|
||||
d_echo("Groups Added: ".implode(',', $added_groups).PHP_EOL);
|
||||
d_echo("Groups Removed: ".implode(',', $removed_groups).PHP_EOL);
|
||||
|
||||
// insert new groups
|
||||
$insert = array();
|
||||
foreach ($added_groups as $group_id) {
|
||||
$insert[] = array('device_id' => $device_id, 'device_group_id' => $group_id);
|
||||
}
|
||||
if (!empty($insert)) {
|
||||
dbBulkInsert($insert, 'device_group_device');
|
||||
}
|
||||
|
||||
// remove old groups
|
||||
if (!empty($removed_groups)) {
|
||||
dbDelete('device_group_device', '`device_id`=? AND `device_group_id` IN ' . dbGenPlaceholders(count($removed_groups)), array_merge([$device_id], $removed_groups));
|
||||
}
|
||||
d_echo("### End Device Groups ###\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the device-group relationship for the given group id
|
||||
* @param $group_id
|
||||
*/
|
||||
function UpdateDeviceGroup($group_id)
|
||||
{
|
||||
$queried_devices = QueryDevicesFromGroup($group_id);
|
||||
$db_devices = GetDevicesFromGroup($group_id);
|
||||
|
||||
// compare the arrays to get the added and removed devices
|
||||
$added_devices = array_diff($queried_devices, $db_devices);
|
||||
$removed_devices = array_diff($db_devices, $queried_devices);
|
||||
|
||||
// insert new devices
|
||||
$insert = array();
|
||||
foreach ($added_devices as $device_id) {
|
||||
$insert[] = array('device_id' => $device_id, 'device_group_id' => $group_id);
|
||||
}
|
||||
if (!empty($insert)) {
|
||||
dbBulkInsert($insert, 'device_group_device');
|
||||
}
|
||||
|
||||
// remove old devices
|
||||
if (!empty($removed_devices)) {
|
||||
dbDelete('device_group_device', '`device_group_id`=? AND `device_id` IN ' . dbGenPlaceholders(count($removed_devices)), array_merge([$group_id], $removed_devices));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill in params into the pattern, replacing placeholders (?)
|
||||
* If $params is empty or null, just returns $pattern
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function formatDeviceGroupPattern($pattern, $params)
|
||||
{
|
||||
// fill in parameters
|
||||
foreach ((array)$params as $value) {
|
||||
if (!is_numeric($value) && !starts_with($value, "'")) {
|
||||
$value = "'".$value."'";
|
||||
}
|
||||
$pattern = preg_replace('/\?/', $value, $pattern, 1);
|
||||
}
|
||||
|
||||
return $pattern;
|
||||
}
|
|
@ -86,3 +86,23 @@ if (!function_exists('set_debug')) {
|
|||
return $debug;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('array_pairs')) {
|
||||
/**
|
||||
* Get all consecutive pairs of values in an array.
|
||||
* [1,2,3,4] -> [[1,2],[2,3],[3,4]]
|
||||
*
|
||||
* @param array $array
|
||||
* @return array
|
||||
*/
|
||||
function array_pairs($array)
|
||||
{
|
||||
$pairs = [];
|
||||
|
||||
for ($i = 1; $i < count($array); $i++) {
|
||||
$pairs[] = [$array[$i - 1], $array[$i]];
|
||||
}
|
||||
|
||||
return $pairs;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
* the source code distribution for details.
|
||||
*/
|
||||
|
||||
use App\Models\Device;
|
||||
use App\Models\DeviceGroup;
|
||||
use LibreNMS\Alerting\QueryBuilderParser;
|
||||
use LibreNMS\Authentication\LegacyAuth;
|
||||
use LibreNMS\Config;
|
||||
|
@ -826,7 +828,7 @@ function list_available_health_graphs()
|
|||
'name' => 'device_'.$graph['sensor_class'],
|
||||
);
|
||||
}
|
||||
$device = \App\Models\Device::find($device_id);
|
||||
$device = Device::find($device_id);
|
||||
|
||||
if ($device) {
|
||||
if ($device->processors()->count() > 0) {
|
||||
|
@ -1820,21 +1822,24 @@ function get_device_groups()
|
|||
{
|
||||
$app = \Slim\Slim::getInstance();
|
||||
$router = $app->router()->getCurrentRoute()->getParams();
|
||||
$status = 'error';
|
||||
$code = 404;
|
||||
$hostname = $router['hostname'];
|
||||
// use hostname as device_id if it's all digits
|
||||
$device_id = ctype_digit($hostname) ? $hostname : getidbyname($hostname);
|
||||
if (is_numeric($device_id)) {
|
||||
$groups = GetGroupsFromDevice($device_id, 1);
|
||||
|
||||
if (!empty($router['hostname'])) {
|
||||
$device = ctype_digit($router['hostname']) ? Device::find($router['hostname']) : Device::findByHostname($router['hostname']);
|
||||
if (is_null($device)) {
|
||||
api_error(404, 'Device not found');
|
||||
}
|
||||
$query = $device->groups();
|
||||
} else {
|
||||
$groups = GetDeviceGroups();
|
||||
$query = DeviceGroup::query();
|
||||
}
|
||||
if (empty($groups)) {
|
||||
|
||||
$groups = $query->orderBy('name')->get();
|
||||
|
||||
if ($groups->isEmpty()) {
|
||||
api_error(404, 'No device groups found');
|
||||
}
|
||||
|
||||
api_success($groups, 'groups', 'Found ' . count($groups) . ' device groups');
|
||||
api_success($groups->makeHidden('pivot')->toArray(), 'groups', 'Found ' . $groups->count() . ' device groups');
|
||||
}
|
||||
|
||||
function get_devices_by_group()
|
||||
|
@ -1842,19 +1847,25 @@ function get_devices_by_group()
|
|||
check_is_read();
|
||||
$app = \Slim\Slim::getInstance();
|
||||
$router = $app->router()->getCurrentRoute()->getParams();
|
||||
$name = urldecode($router['name']);
|
||||
$devices = array();
|
||||
$full = $_GET['full'];
|
||||
if (empty($name)) {
|
||||
|
||||
if (empty($router['name'])) {
|
||||
api_error(400, 'No device group name provided');
|
||||
}
|
||||
$group_id = dbFetchCell("SELECT `id` FROM `device_groups` WHERE `name`=?", array($name));
|
||||
$devices = GetDevicesFromGroup($group_id, true, $full);
|
||||
if (empty($devices)) {
|
||||
$name = urldecode($router['name']);
|
||||
|
||||
$device_group = ctype_digit($name) ? DeviceGroup::find($name) : DeviceGroup::where('name', $name)->first();
|
||||
|
||||
if (empty($device_group)) {
|
||||
api_error(404, 'Device group not found');
|
||||
}
|
||||
|
||||
$devices = $device_group->devices()->get(empty($_GET['full']) ? ['devices.device_id'] : ['*']);
|
||||
|
||||
if ($devices->isEmpty()) {
|
||||
api_error(404, 'No devices found in group ' . $name);
|
||||
}
|
||||
|
||||
api_success($devices, 'devices');
|
||||
api_success($devices->makeHidden('pivot')->toArray(), 'devices');
|
||||
}
|
||||
|
||||
|
||||
|
@ -2050,7 +2061,7 @@ function get_fdb()
|
|||
}
|
||||
check_device_permission($device_id);
|
||||
|
||||
$device = \App\Models\Device::find($device_id);
|
||||
$device = Device::find($device_id);
|
||||
if ($device) {
|
||||
$fdb = $device->portsFdb;
|
||||
api_success($fdb, 'ports_fdb');
|
||||
|
|
|
@ -115,11 +115,8 @@ if (defined('SHOW_SETTINGS')) {
|
|||
<select class="form-control" name="group">';
|
||||
$common_output[] = '<option value=""' . ($current_group == '' ? ' selected' : '') . '>any group</option>';
|
||||
|
||||
$device_groups = GetDeviceGroups();
|
||||
$common_output[] = "<!-- " . print_r($device_groups, true) . " -->";
|
||||
foreach ($device_groups as $group) {
|
||||
$group_id = $group['id'];
|
||||
$common_output[] = "<option value=\"$group_id\"" . (is_numeric($current_group) && $current_group == $group_id ? ' selected' : '') . ">" . $group['name'] . " - " . $group['description'] . "</option>";
|
||||
foreach (\App\Models\DeviceGroup::orderBy('name')->get(['id', 'name', 'desc']) as $group) {
|
||||
$common_output[] = "<option value=\"$group->id\"" . (is_numeric($current_group) && $current_group == $group->id ? ' selected' : '') . ">" . $group->name . " - " . $group->desc . "</option>";
|
||||
}
|
||||
$common_output[] = '
|
||||
</select>
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* LibreNMS
|
||||
*
|
||||
* Copyright (c) 2014 Neil Lathwood <https://github.com/laf/ http://www.lathwood.co.uk/fa>
|
||||
*
|
||||
* 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. Please see LICENSE.txt at the top level of
|
||||
* the source code distribution for details.
|
||||
*/
|
||||
|
||||
use LibreNMS\Authentication\LegacyAuth;
|
||||
|
||||
if (!LegacyAuth::user()->hasGlobalAdmin()) {
|
||||
die('ERROR: You need to be admin');
|
||||
}
|
||||
|
||||
$pattern = $_POST['patterns'];
|
||||
$group_id = $_POST['group_id'];
|
||||
$name = mres($_POST['name']);
|
||||
$desc = mres($_POST['desc']);
|
||||
|
||||
if (is_array($pattern)) {
|
||||
$pattern = implode(' ', $pattern);
|
||||
} elseif (!empty($_POST['pattern']) && !empty($_POST['condition']) && !empty($_POST['value'])) {
|
||||
$pattern = '%'.$_POST['pattern'].' '.$_POST['condition'].' ';
|
||||
if (is_numeric($_POST['value'])) {
|
||||
$pattern .= $_POST['value'];
|
||||
} else {
|
||||
$pattern .= '"'.$_POST['value'].'"';
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($pattern)) {
|
||||
$update_message = 'ERROR: No group was generated';
|
||||
} elseif (is_numeric($group_id) && $group_id > 0) {
|
||||
if (EditDeviceGroup($group_id, $name, $desc, $pattern)) {
|
||||
$update_message = "Edited Group: <i>$name: $pattern</i>";
|
||||
} else {
|
||||
$update_message = 'ERROR: Failed to edit Group: <i>'.$pattern.'</i>';
|
||||
}
|
||||
} else {
|
||||
if (AddDeviceGroup($name, $desc, $pattern)) {
|
||||
$update_message = "Added Group: <i>$name: $pattern</i>";
|
||||
} else {
|
||||
$update_message = 'ERROR: Failed to add Group: <i>'.$pattern.'</i>';
|
||||
}
|
||||
}
|
||||
|
||||
echo $update_message;
|
|
@ -1,36 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* LibreNMS
|
||||
*
|
||||
* Copyright (c) 2014 Neil Lathwood <https://github.com/laf/ http://www.lathwood.co.uk/fa>
|
||||
*
|
||||
* 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. Please see LICENSE.txt at the top level of
|
||||
* the source code distribution for details.
|
||||
*/
|
||||
|
||||
use LibreNMS\Authentication\LegacyAuth;
|
||||
|
||||
header('Content-type: text/plain');
|
||||
|
||||
if (!LegacyAuth::user()->hasGlobalAdmin()) {
|
||||
die('ERROR: You need to be admin');
|
||||
}
|
||||
|
||||
if (!is_numeric($_POST['group_id'])) {
|
||||
echo 'ERROR: No group selected';
|
||||
exit;
|
||||
} else {
|
||||
if (dbDelete('device_groups', '`id` = ?', array($_POST['group_id']))) {
|
||||
dbDelete('alert_group_map', 'group_id=?', [$_POST['group_id']]);
|
||||
|
||||
echo 'Group has been deleted.';
|
||||
exit;
|
||||
} else {
|
||||
echo 'ERROR: Group has not been deleted.';
|
||||
exit;
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* LibreNMS
|
||||
*
|
||||
* Copyright (c) 2014 Neil Lathwood <https://github.com/laf/ http://www.lathwood.co.uk/fa>
|
||||
*
|
||||
* 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. Please see LICENSE.txt at the top level of
|
||||
* the source code distribution for details.
|
||||
*/
|
||||
|
||||
use LibreNMS\Authentication\LegacyAuth;
|
||||
|
||||
if (!LegacyAuth::user()->hasGlobalAdmin()) {
|
||||
header('Content-type: text/plain');
|
||||
die('ERROR: You need to be admin');
|
||||
}
|
||||
|
||||
$group_id = $_POST['group_id'];
|
||||
|
||||
if (is_numeric($group_id) && $group_id > 0) {
|
||||
$group = dbFetchRow('SELECT * FROM `device_groups` WHERE `id` = ? LIMIT 1', array($group_id));
|
||||
$group_split = preg_split('/([a-zA-Z0-9_\-\.\=\%\<\>\ \"\'\!\~\(\)\*\/\@\[\]\^\$]+[&&\|\|]+)/', $group['pattern'], -1, (PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY));
|
||||
$count = (count($group_split) - 1);
|
||||
if (preg_match('/\&\&$/', $group_split[$count]) == 1 || preg_match('/\|\|$/', $group_split[$count]) == 1) {
|
||||
$group_split[$count] = $group_split[$count];
|
||||
} else {
|
||||
$group_split[$count] = $group_split[$count].' &&';
|
||||
}
|
||||
|
||||
$output = array(
|
||||
'name' => $group['name'],
|
||||
'desc' => $group['desc'],
|
||||
'pattern' => $group_split,
|
||||
);
|
||||
header('Content-type: application/json');
|
||||
echo _json_encode($output);
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
<?php
|
||||
/*
|
||||
* LibreNMS
|
||||
*
|
||||
* Copyright (c) 2014 Neil Lathwood <https://github.com/laf/ http://www.lathwood.co.uk/fa>
|
||||
*
|
||||
* 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. Please see LICENSE.txt at the top level of
|
||||
* the source code distribution for details.
|
||||
*/
|
||||
|
||||
use LibreNMS\Authentication\LegacyAuth;
|
||||
|
||||
if (!LegacyAuth::user()->hasGlobalAdmin()) {
|
||||
die('ERROR: You need to be admin');
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<div class="modal fade" id="confirm-delete" tabindex="-1" role="dialog" aria-labelledby="Delete" aria-hidden="true">
|
||||
<div class="modal-dialog modal-sm">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h5 class="modal-title" id="Delete">Confirm Delete</h5>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>If you would like to remove the device group then please click Delete.</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<form role="form" class="remove_token_form">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-danger danger" id="device-group-removal" data-target="device-group-removal">Delete</button>
|
||||
<input type="hidden" name="group_id" id="group_id" value="">
|
||||
<input type="hidden" name="confirm" id="confirm" value="yes">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$('#confirm-delete').on('show.bs.modal', function(e) {
|
||||
group_id = $(e.relatedTarget).data('group_id');
|
||||
$("#group_id").val(group_id);
|
||||
});
|
||||
|
||||
$('#device-group-removal').click('', function(event) {
|
||||
event.preventDefault();
|
||||
var group_id = $("#group_id").val();
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: 'ajax_form.php',
|
||||
data: { type: "delete-device-group", group_id: group_id },
|
||||
dataType: "html",
|
||||
success: function(msg) {
|
||||
if(msg.indexOf("ERROR:") <= -1) {
|
||||
$("#row_"+group_id).remove();
|
||||
}
|
||||
$("#message").html('<div class="alert alert-info">'+msg+'</div>');
|
||||
$("#confirm-delete").modal('hide');
|
||||
},
|
||||
error: function() {
|
||||
$("#message").html('<div class="alert alert-info">The device group could not be deleted.</div>');
|
||||
$("#confirm-delete").modal('hide');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
|
@ -1,252 +0,0 @@
|
|||
<?php
|
||||
/*
|
||||
* LibreNMS
|
||||
*
|
||||
* Copyright (c) 2014 Neil Lathwood <https://github.com/laf/ http://www.lathwood.co.uk/fa>
|
||||
*
|
||||
* 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. Please see LICENSE.txt at the top level of
|
||||
* the source code distribution for details.
|
||||
*/
|
||||
|
||||
use LibreNMS\Authentication\LegacyAuth;
|
||||
|
||||
if (LegacyAuth::user()->hasGlobalAdmin()) {
|
||||
?>
|
||||
|
||||
<div class="modal fade bs-example-modal-sm" id="create-group" tabindex="-1" role="dialog" aria-labelledby="Create" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h5 class="modal-title" id="Create">Device Groups</h5>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form method="post" role="form" id="devices-group" class="form-horizontal group-form">
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<span id="ajax_response"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class='form-group'>
|
||||
<label for='name' class='col-sm-3 control-label'>Name: </label>
|
||||
<div class='col-sm-9'>
|
||||
<input type='text' id='name' name='name' class='form-control has-feedback' maxlength='200'>
|
||||
</div>
|
||||
</div>
|
||||
<div class='form-group'>
|
||||
<label for='desc' class='col-sm-3 control-label'>Description: </label>
|
||||
<div class='col-sm-9'>
|
||||
<input type='text' id='desc' name='desc' class='form-control has-feedback' maxlength='200'>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="group_id" id="group_id" value="">
|
||||
<input type="hidden" name="type" id="type" value="create-device-group">
|
||||
<div class="form-group">
|
||||
<label for='pattern' class='col-sm-3 control-label'>Pattern: </label>
|
||||
<div class="col-sm-5">
|
||||
<input type='text' id='suggest' name='pattern' class='form-control has-feedback' placeholder='I.e: devices.status'/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-3 col-sm-9">
|
||||
<p>Start typing for suggestions, use '.' for indepth selection</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for='condition' class='col-sm-3 control-label'>Condition: </label>
|
||||
<div class="col-sm-5">
|
||||
<select id='condition' name='condition' placeholder='Condition' class='form-control has-feedback'>
|
||||
<option value='='>Equals</option>
|
||||
<option value='!='>Not Equals</option>
|
||||
<option value='~'>Like</option>
|
||||
<option value='!~'>Not Like</option>
|
||||
<option value='>'>Larger than</option>
|
||||
<option value='>='>Larger than or Equals</option>
|
||||
<option value='<'>Smaller than</option>
|
||||
<option value='<='>Smaller than or Equals</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for='value' class='col-sm-3 control-label'>Value: </label>
|
||||
<div class="col-sm-5">
|
||||
<input type='text' id='value' name='value' class='form-control has-feedback'/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for='group-glue' class='col-sm-3 control-label'>Connection: </label>
|
||||
<div class="col-sm-5">
|
||||
<button class="btn btn-warning btn-sm" type="submit" name="group-glue" value="&&" id="and" name="and">And</button>
|
||||
<button class="btn btn-warning btn-sm" type="submit" name="group-glue" value="||" id="or" name="or">Or</button>
|
||||
<span id="next-step-and"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<span id="response"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-3 col-sm-3">
|
||||
<button class="btn btn-success btn-sm" type="submit" name="group-submit" id="group-submit" value="save">Save Group</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
$('#create-group').on('hide.bs.modal', function (event) {
|
||||
$('#response').data('tagmanager').empty();
|
||||
$('#name').val('');
|
||||
$('#desc').val('');
|
||||
});
|
||||
|
||||
$('#create-group').on('show.bs.modal', function (event) {
|
||||
var button = $(event.relatedTarget);
|
||||
var group_id = button.data('group_id');
|
||||
var modal = $(this)
|
||||
$('#group_id').val(group_id);
|
||||
$('#tagmanager').tagmanager();
|
||||
$('#response').tagmanager({
|
||||
strategy: 'array',
|
||||
tagFieldName: 'patterns[]'
|
||||
});
|
||||
if (group_id > 0) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "ajax_form.php",
|
||||
data: {type: "parse-device-group", group_id: group_id},
|
||||
dataType: "json",
|
||||
success: function (output) {
|
||||
var arr = [];
|
||||
$.each(output['pattern'], function (key, value) {
|
||||
arr.push(value);
|
||||
});
|
||||
$('#response').data('tagmanager').populate(arr);
|
||||
$('#name').val(output['name']);
|
||||
$('#desc').val(output['desc']);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
var cache = {};
|
||||
var suggestions = new Bloodhound({
|
||||
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'),
|
||||
queryTokenizer: Bloodhound.tokenizers.whitespace,
|
||||
remote: {
|
||||
url: "ajax_rulesuggest.php?device_id=-1&term=%QUERY",
|
||||
filter: function (output) {
|
||||
return $.map(output, function (item) {
|
||||
return {
|
||||
name: item.name,
|
||||
};
|
||||
});
|
||||
},
|
||||
wildcard: "%QUERY"
|
||||
}
|
||||
});
|
||||
suggestions.initialize();
|
||||
$('#suggest').typeahead({
|
||||
hint: true,
|
||||
highlight: true,
|
||||
minLength: 1,
|
||||
classNames: {
|
||||
menu: 'typeahead-left'
|
||||
}
|
||||
},
|
||||
{
|
||||
source: suggestions.ttAdapter(),
|
||||
async: true,
|
||||
displayKey: 'name',
|
||||
valueKey: name,
|
||||
templates: {
|
||||
suggestion: Handlebars.compile('<p> {{name}}</p>')
|
||||
}
|
||||
});
|
||||
|
||||
$('#and, #or').click('', function(e) {
|
||||
e.preventDefault();
|
||||
$("#next-step-and").html("");
|
||||
var entity = $('#suggest').val();
|
||||
var condition = $('#condition').val();
|
||||
var value = $('#value').val();
|
||||
var glue = $(this).val();
|
||||
if(entity != '' && condition != '') {
|
||||
$('#response').tagmanager({
|
||||
strategy: 'array',
|
||||
tagFieldName: 'patterns[]'
|
||||
});
|
||||
if(value.indexOf("%") < 0 && isNaN(value)) {
|
||||
value = '"'+value+'"';
|
||||
}
|
||||
if(entity.indexOf("%") < 0) {
|
||||
entity = '%'+entity;
|
||||
}
|
||||
$('#response').data('tagmanager').populate([ entity+' '+condition+' '+value+' '+glue ]);
|
||||
}
|
||||
});
|
||||
|
||||
$('#group-submit').click('', function(e) {
|
||||
e.preventDefault();
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "ajax_form.php",
|
||||
data: $('form.group-form').serialize(),
|
||||
success: function(msg){
|
||||
if(msg.indexOf("ERROR:") <= -1) {
|
||||
$("#message").html('<div class="alert alert-info">'+msg+'</div>');
|
||||
$("#create-group").modal('hide');
|
||||
$('#response').data('tagmanager').empty();
|
||||
setTimeout(function() {
|
||||
location.reload(1);
|
||||
}, 1000);
|
||||
} else {
|
||||
$('#ajax_response').html('<div class="alert alert-danger alert-dismissible"><button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>'+msg+'</div>');
|
||||
}
|
||||
},
|
||||
error: function(){
|
||||
$("#message").html('<div class="alert alert-info">An error occurred creating this group.</div>');
|
||||
$("#create-group").modal('hide');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$( "#name, #suggest, #value" ).blur(function() {
|
||||
var $this = $(this);
|
||||
var name = $('#name').val();
|
||||
var suggest = $('#suggest').val();
|
||||
var value = $('#value').val();
|
||||
if (name == "") {
|
||||
$("#next-step-and").html("");
|
||||
$("#suggest").closest('.form-group').removeClass('has-error');
|
||||
$("#value").closest('.form-group').removeClass('has-error');
|
||||
$("#name").closest('.form-group').addClass('has-error');
|
||||
} else if (suggest == "") {
|
||||
$("#next-step-and").html("");
|
||||
$("#name").closest('.form-group').removeClass('has-error');
|
||||
$("#value").closest('.form-group').removeClass('has-error');
|
||||
$("#suggest").closest('.form-group').addClass('has-error');
|
||||
} else if (value == "") {
|
||||
$("#next-step-and").html("");
|
||||
$("#name").closest('.form-group').removeClass('has-error');
|
||||
$("#suggest").closest('.form-group').removeClass('has-error');
|
||||
$("#value").closest('.form-group').addClass('has-error');
|
||||
} else {
|
||||
$("#name").closest('.form-group').removeClass('has-error');
|
||||
$("#suggest").closest('.form-group').removeClass('has-error');
|
||||
$("#value").closest('.form-group').removeClass('has-error');
|
||||
$("#next-step-and").html('<i class="fa fa-long-arrow-left fa-col-danger"></i> Click AND / OR');
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<?php
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
<?php
|
||||
require_once 'includes/html/modal/new_device_group.inc.php';
|
||||
require_once 'includes/html/modal/delete_device_group.inc.php';
|
||||
|
||||
$no_refresh = true;
|
||||
$group_count_check = array_filter(GetDeviceGroups());
|
||||
if (!empty($group_count_check)) {
|
||||
echo '<div class="row"><div class="col-sm-12"><span id="message"></span></div></div>';
|
||||
echo '<div class="table-responsive">';
|
||||
echo '<table class="table table-condensed table-hover"><thead><tr>';
|
||||
echo '<th>Name</th><th>Description</th><th>Pattern</th><th>Actions</th>';
|
||||
echo '</tr></thead><tbody>';
|
||||
foreach (GetDeviceGroups() as $group) {
|
||||
echo '<tr id="row_'.$group['id'].'">';
|
||||
echo '<td>'.$group['name'].'</td>';
|
||||
echo '<td>'.$group['desc'].'</td>';
|
||||
echo '<td>'.formatDeviceGroupPattern($group['pattern'], json_decode($group['params'])).'</td>';
|
||||
echo '<td>';
|
||||
echo "<button type='button' class='btn btn-primary btn-sm' aria-label='Edit' data-toggle='modal' data-target='#create-group' data-group_id='".$group['id']."' name='edit-device-group'";
|
||||
if (!is_null($group['params'])) {
|
||||
echo " disabled title='LibreNMS V2 device groups cannot be edited in LibreNMS V1'";
|
||||
}
|
||||
echo "><i class='fa fa-pencil' aria-hidden='true'></i></button> ";
|
||||
echo "<button type='button' class='btn btn-danger btn-sm' aria-label='Delete' data-toggle='modal' data-target='#confirm-delete' data-group_id='".$group['id']."' name='delete-device-group'><i class='fa fa-trash' aria-hidden='true'></i></button>";
|
||||
echo '</td>';
|
||||
echo '</tr>';
|
||||
}
|
||||
echo '</tbody></table></div>';
|
||||
} else { //if $group_count_check is empty, aka no group found, then display a message to the user.
|
||||
echo "<center>Looks like no groups have been created, let's create one now. Click on <b>Create New Group</b> to create one.</center><br>";
|
||||
echo "<center><button type='button' class='btn btn-primary btn-sm' aria-label='Add' data-toggle='modal' data-target='#create-group' data-group_id='' name='create-device-group'>Create new Group</button></center>";
|
||||
}
|
||||
|
||||
if (!empty($group_count_check)) { //display create new node group when $group_count_check has a value so that the user can define more groups in the future.
|
||||
echo "<hr>";
|
||||
echo "<center><button type='button' class='btn btn-primary btn-sm' aria-label='Add' data-toggle='modal' data-target='#create-group' data-group_id='' name='create-device-group'>Create new Group</button></center>";
|
||||
}
|
|
@ -180,7 +180,7 @@ if ($format == "graph") {
|
|||
}
|
||||
if (!empty($vars['group'])) {
|
||||
$where .= " AND ( ";
|
||||
foreach (GetDevicesFromGroup($vars['group']) as $dev) {
|
||||
foreach (DB::table('device_group_device')->where('device_group_id', $vars['group'])->pluck('device_id') as $dev) {
|
||||
$where .= "device_id = ? OR ";
|
||||
$sql_param[] = $dev;
|
||||
}
|
||||
|
|
|
@ -72,7 +72,6 @@ if (module_selected('mocksnmp', $init_modules)) {
|
|||
require_once $install_dir . '/includes/services.inc.php';
|
||||
require_once $install_dir . '/includes/functions.php';
|
||||
require_once $install_dir . '/includes/rewrites.php';
|
||||
require_once $install_dir . '/includes/device-groups.inc.php';
|
||||
|
||||
if (module_selected('web', $init_modules)) {
|
||||
require_once $install_dir . '/includes/html/functions.inc.php';
|
||||
|
|
|
@ -347,7 +347,14 @@ function poll_device($device, $force_module = false)
|
|||
}
|
||||
|
||||
// Update device_groups
|
||||
UpdateGroupsForDevice($device['device_id']);
|
||||
echo "### Start Device Groups ###\n";
|
||||
$dg_start = microtime(true);
|
||||
|
||||
$group_changes = \App\Models\DeviceGroup::updateGroupsFor($device['device_id']);
|
||||
d_echo("Groups Added: " . implode(',', $group_changes['attached']) . PHP_EOL);
|
||||
d_echo("Groups Removed: " . implode(',', $group_changes['detached']) . PHP_EOL);
|
||||
|
||||
echo "### End Device Groups, runtime: " . round(microtime(true) - $dg_start, 4) . "s ### \n\n";
|
||||
|
||||
if (!$force_module && !empty($graphs)) {
|
||||
echo "Enabling graphs: ";
|
||||
|
|
|
@ -516,9 +516,10 @@ device_groups:
|
|||
Columns:
|
||||
- { Field: id, Type: 'int(10) unsigned', 'Null': false, Extra: auto_increment }
|
||||
- { Field: name, Type: varchar(255), 'Null': false, Extra: '', Default: '' }
|
||||
- { Field: desc, Type: varchar(255), 'Null': false, Extra: '', Default: '' }
|
||||
- { Field: desc, Type: varchar(255), 'Null': true, Extra: '', Default: '' }
|
||||
- { Field: type, Type: varchar(16), 'Null': false, Extra: '', Default: dynamic }
|
||||
- { Field: rules, Type: text, 'Null': true, Extra: '' }
|
||||
- { Field: pattern, Type: text, 'Null': true, Extra: '' }
|
||||
- { Field: params, Type: text, 'Null': true, Extra: '' }
|
||||
Indexes:
|
||||
PRIMARY: { Name: PRIMARY, Columns: [id], Unique: true, Type: BTREE }
|
||||
name: { Name: name, Columns: [name], Unique: true, Type: BTREE }
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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>
|
|
@ -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
|
||||
|
|
|
@ -22,6 +22,7 @@ Route::group(['middleware' => ['auth', '2fa'], 'guard' => 'auth'], function () {
|
|||
});
|
||||
|
||||
// pages
|
||||
Route::resource('device-groups', 'DeviceGroupController');
|
||||
Route::get('locations', 'LocationController@index');
|
||||
Route::resource('preferences', 'UserPreferencesController', ['only' => ['index', 'store']]);
|
||||
Route::resource('users', 'UserController');
|
||||
|
|
|
@ -25,10 +25,11 @@
|
|||
|
||||
namespace LibreNMS\Tests;
|
||||
|
||||
use LibreNMS\Alerting\QueryBuilderFluentParser;
|
||||
use LibreNMS\Alerting\QueryBuilderParser;
|
||||
use LibreNMS\Config;
|
||||
|
||||
class QueryBuilderTest extends TestCase
|
||||
class QueryBuilderTest extends LaravelTestCase
|
||||
{
|
||||
private $data_file = 'tests/data/misc/querybuilder.json';
|
||||
|
||||
|
@ -48,14 +49,19 @@ class QueryBuilderTest extends TestCase
|
|||
* @param string $display
|
||||
* @param string $sql
|
||||
*/
|
||||
public function testQueryConversion($legacy, $builder, $display, $sql)
|
||||
public function testQueryConversion($legacy, $builder, $display, $sql, $query)
|
||||
{
|
||||
if (!empty($legacy)) {
|
||||
// some rules don't have a legacy representation
|
||||
$this->assertEquals($builder, QueryBuilderParser::fromOld($legacy)->toArray());
|
||||
}
|
||||
$this->assertEquals($display, QueryBuilderParser::fromJson($builder)->toSql(false));
|
||||
$this->assertEquals($sql, QueryBuilderParser::fromJson($builder)->toSql());
|
||||
$qb = QueryBuilderFluentParser::fromJson($builder);
|
||||
$this->assertEquals($display, $qb->toSql(false));
|
||||
$this->assertEquals($sql, $qb->toSql());
|
||||
|
||||
$qbq = $qb->toQuery();
|
||||
$this->assertEquals($query[0], $qbq->toSql(), 'Fluent SQL does not match');
|
||||
$this->assertEquals($query[1], $qbq->getBindings(), 'Fluent bindings do not match');
|
||||
}
|
||||
|
||||
public function loadQueryData()
|
||||
|
|
|
@ -3,114 +3,174 @@
|
|||
"",
|
||||
{"condition":"OR","rules":[{"id":"devices.hostname","field":"devices.hostname","type":"string","input":"text","operator":"begins_with","value":"begin"}],"valid":true},
|
||||
"devices.hostname LIKE 'begin%'",
|
||||
"SELECT * FROM devices WHERE (devices.device_id = ?) AND devices.hostname LIKE 'begin%'"
|
||||
"SELECT * FROM devices WHERE (devices.device_id = ?) AND devices.hostname LIKE 'begin%'",
|
||||
["select * from `devices` where (`devices`.`hostname` LIKE ?)", ["begin%"]]
|
||||
],
|
||||
[
|
||||
"",
|
||||
{"condition":"AND","rules":[{"id":"devices.hostname","field":"devices.hostname","type":"string","input":"text","operator":"not_begins_with","value":"notbegin"}],"valid":true},
|
||||
"devices.hostname NOT LIKE 'notbegin%'",
|
||||
"SELECT * FROM devices WHERE (devices.device_id = ?) AND devices.hostname NOT LIKE 'notbegin%'"
|
||||
"SELECT * FROM devices WHERE (devices.device_id = ?) AND devices.hostname NOT LIKE 'notbegin%'",
|
||||
["select * from `devices` where (`devices`.`hostname` NOT LIKE ?)", ["notbegin%"]]
|
||||
],
|
||||
[
|
||||
"",
|
||||
{"condition":"AND","rules":[{"id":"devices.hostname","field":"devices.hostname","type":"string","input":"text","operator":"contains","value":"contains"}],"valid":true},
|
||||
"devices.hostname LIKE '%contains%'",
|
||||
"SELECT * FROM devices WHERE (devices.device_id = ?) AND devices.hostname LIKE '%contains%'"
|
||||
"SELECT * FROM devices WHERE (devices.device_id = ?) AND devices.hostname LIKE '%contains%'",
|
||||
["select * from `devices` where (`devices`.`hostname` LIKE ?)", ["%contains%"]]
|
||||
],
|
||||
[
|
||||
"",
|
||||
{"condition":"AND","rules":[{"id":"devices.hostname","field":"devices.hostname","type":"string","input":"text","operator":"not_contains","value":"notcontains"}],"valid":true},
|
||||
"devices.hostname NOT LIKE '%notcontains%'",
|
||||
"SELECT * FROM devices WHERE (devices.device_id = ?) AND devices.hostname NOT LIKE '%notcontains%'"
|
||||
"SELECT * FROM devices WHERE (devices.device_id = ?) AND devices.hostname NOT LIKE '%notcontains%'",
|
||||
["select * from `devices` where (`devices`.`hostname` NOT LIKE ?)", ["%notcontains%"]]
|
||||
],
|
||||
[
|
||||
"",
|
||||
{"condition":"AND","rules":[{"id":"devices.hostname","field":"devices.hostname","type":"string","input":"text","operator":"ends_with","value":"ends"}],"valid":true},
|
||||
"devices.hostname LIKE '%ends'",
|
||||
"SELECT * FROM devices WHERE (devices.device_id = ?) AND devices.hostname LIKE '%ends'"
|
||||
"SELECT * FROM devices WHERE (devices.device_id = ?) AND devices.hostname LIKE '%ends'",
|
||||
["select * from `devices` where (`devices`.`hostname` LIKE ?)", ["%ends"]]
|
||||
],
|
||||
[
|
||||
"",
|
||||
{"condition":"AND","rules":[{"id":"devices.hostname","field":"devices.hostname","type":"string","input":"text","operator":"not_ends_with","value":"notends"}],"valid":true},
|
||||
"devices.hostname NOT LIKE '%notends'",
|
||||
"SELECT * FROM devices WHERE (devices.device_id = ?) AND devices.hostname NOT LIKE '%notends'"
|
||||
"SELECT * FROM devices WHERE (devices.device_id = ?) AND devices.hostname NOT LIKE '%notends'",
|
||||
["select * from `devices` where (`devices`.`hostname` NOT LIKE ?)", ["%notends"]]
|
||||
],
|
||||
[
|
||||
"",
|
||||
{"condition":"AND","rules":[{"id":"ports.ifDescr","field":"ports.ifDescr","type":"string","input":"text","operator":"is_null","value":""}],"valid":true},
|
||||
"ports.ifDescr IS NULL",
|
||||
"SELECT * FROM devices,ports WHERE (devices.device_id = ? AND devices.device_id = ports.device_id) AND ports.ifDescr IS NULL"
|
||||
"SELECT * FROM devices,ports WHERE (devices.device_id = ? AND devices.device_id = ports.device_id) AND ports.ifDescr IS NULL",
|
||||
["select * from `devices` left join `ports` on `devices`.`device_id` = `ports`.`device_id` where (`ports`.`ifDescr` is null)", []]
|
||||
],
|
||||
[
|
||||
"",
|
||||
{"condition":"AND","rules":[{"id":"ports.ifDescr","field":"ports.ifDescr","type":"string","input":"text","operator":"is_not_null","value":""}],"valid":true},
|
||||
"ports.ifDescr IS NOT NULL",
|
||||
"SELECT * FROM devices,ports WHERE (devices.device_id = ? AND devices.device_id = ports.device_id) AND ports.ifDescr IS NOT NULL"
|
||||
"SELECT * FROM devices,ports WHERE (devices.device_id = ? AND devices.device_id = ports.device_id) AND ports.ifDescr IS NOT NULL",
|
||||
["select * from `devices` left join `ports` on `devices`.`device_id` = `ports`.`device_id` where (`ports`.`ifDescr` is not null)", []]
|
||||
],
|
||||
[
|
||||
"",
|
||||
{"condition":"AND","rules":[{"id":"devices.hardware","field":"devices.hardware","type":"string","input":"text","operator":"is_empty","value":null}],"valid":true},
|
||||
"devices.hardware = ''",
|
||||
"SELECT * FROM devices WHERE (devices.device_id = ?) AND devices.hardware = ''"
|
||||
"SELECT * FROM devices WHERE (devices.device_id = ?) AND devices.hardware = ''",
|
||||
["select * from `devices` where (`devices`.`hardware` = ?)", [""]]
|
||||
],
|
||||
[
|
||||
"",
|
||||
{"condition":"AND","rules":[{"id":"devices.hardware","field":"devices.hardware","type":"string","input":"text","operator":"is_not_empty","value":null}],"valid":true},
|
||||
"devices.hardware != ''",
|
||||
"SELECT * FROM devices WHERE (devices.device_id = ?) AND devices.hardware != ''"
|
||||
"SELECT * FROM devices WHERE (devices.device_id = ?) AND devices.hardware != ''",
|
||||
["select * from `devices` where (`devices`.`hardware` != ?)", [""]]
|
||||
],
|
||||
[
|
||||
"",
|
||||
{"condition":"AND","rules":[{"id":"devices.device_id","field":"devices.device_id","type":"integer","input":"number","operator":"between","value":["3","99"]}],"valid":true},
|
||||
"devices.device_id BETWEEN 3 AND 99",
|
||||
"SELECT * FROM devices WHERE (devices.device_id = ?) AND devices.device_id BETWEEN 3 AND 99"
|
||||
"SELECT * FROM devices WHERE (devices.device_id = ?) AND devices.device_id BETWEEN 3 AND 99",
|
||||
["select * from `devices` where (`devices`.`device_id` between ? and ?)", ["3","99"]]
|
||||
],
|
||||
[
|
||||
"",
|
||||
{"condition":"AND","rules":[{"id":"devices.device_id","field":"devices.device_id","type":"integer","input":"number","operator":"not_between","value":["2","4"]}],"valid":true},
|
||||
"devices.device_id NOT BETWEEN 2 AND 4",
|
||||
"SELECT * FROM devices WHERE (devices.device_id = ?) AND devices.device_id NOT BETWEEN 2 AND 4"
|
||||
"SELECT * FROM devices WHERE (devices.device_id = ?) AND devices.device_id NOT BETWEEN 2 AND 4",
|
||||
["select * from `devices` where (`devices`.`device_id` not between ? and ?)", ["2","4"]]
|
||||
],
|
||||
[
|
||||
"%macros.device_up = 1",
|
||||
{"condition":"AND","rules":[{"id":"macros.device_up","field":"macros.device_up","type":"integer","input":"radio","operator":"equal","value":"1"}],"valid":true},
|
||||
"macros.device_up = 1",
|
||||
"SELECT * FROM devices WHERE (devices.device_id = ?) AND (devices.status = 1 && (devices.disabled = 0 && devices.ignore = 0)) = 1"
|
||||
"SELECT * FROM devices WHERE (devices.device_id = ?) AND (devices.status = 1 && (devices.disabled = 0 && devices.ignore = 0)) = 1",
|
||||
["select * from `devices` where ((devices.status = 1 && (devices.disabled = 0 && devices.ignore = 0)) = ?)", ["1"]]
|
||||
],
|
||||
[
|
||||
"%sensors.sensor_current > %sensors.sensor_limit",
|
||||
{"condition":"AND","rules":[{"id":"sensors.sensor_current","field":"sensors.sensor_current","type":"string","input":"text","operator":"greater","value":"`sensors.sensor_limit`"}],"valid":true},
|
||||
"sensors.sensor_current > sensors.sensor_limit",
|
||||
"SELECT * FROM devices,sensors WHERE (devices.device_id = ? AND devices.device_id = sensors.device_id) AND sensors.sensor_current > sensors.sensor_limit"
|
||||
"SELECT * FROM devices,sensors WHERE (devices.device_id = ? AND devices.device_id = sensors.device_id) AND sensors.sensor_current > sensors.sensor_limit",
|
||||
["select * from `devices` left join `sensors` on `devices`.`device_id` = `sensors`.`device_id` where (`sensors`.`sensor_current` > sensors.sensor_limit)", []]
|
||||
],
|
||||
[
|
||||
"%devices.hostname ~ \"@ocal@\" ",
|
||||
{"condition":"AND","rules":[{"id":"devices.hostname","field":"devices.hostname","type":"string","input":"text","operator":"regex","value":".*ocal.*"}],"valid":true},
|
||||
"devices.hostname REGEXP \".*ocal.*\"",
|
||||
"SELECT * FROM devices WHERE (devices.device_id = ?) AND devices.hostname REGEXP \".*ocal.*\""
|
||||
"SELECT * FROM devices WHERE (devices.device_id = ?) AND devices.hostname REGEXP \".*ocal.*\"",
|
||||
["select * from `devices` where (`devices`.`hostname` REGEXP ?)", [".*ocal.*"]]
|
||||
],
|
||||
[
|
||||
"%macros.state_sensor_critical",
|
||||
{"condition":"AND","rules":[{"id":"macros.state_sensor_critical","field":"macros.state_sensor_critical","type":"integer","input":"radio","operator":"equal","value":"1"}],"valid":true},
|
||||
"macros.state_sensor_critical = 1",
|
||||
"SELECT * FROM devices,sensors,sensors_to_state_indexes,state_indexes,state_translations WHERE (devices.device_id = ? AND devices.device_id = sensors.device_id AND sensors.sensor_id = sensors_to_state_indexes.sensor_id AND sensors_to_state_indexes.state_index_id = state_indexes.state_index_id AND state_indexes.state_index_id = state_translations.state_index_id) AND (sensors.sensor_current = state_translations.state_value && state_translations.state_generic_value = 2) = 1"
|
||||
"SELECT * FROM devices,sensors,sensors_to_state_indexes,state_indexes,state_translations WHERE (devices.device_id = ? AND devices.device_id = sensors.device_id AND sensors.sensor_id = sensors_to_state_indexes.sensor_id AND sensors_to_state_indexes.state_index_id = state_indexes.state_index_id AND state_indexes.state_index_id = state_translations.state_index_id) AND (sensors.sensor_current = state_translations.state_value && state_translations.state_generic_value = 2) = 1",
|
||||
["select * from `devices` left join `sensors` on `devices`.`device_id` = `sensors`.`device_id` left join `sensors_to_state_indexes` on `sensors`.`sensor_id` = `sensors_to_state_indexes`.`sensor_id` left join `state_indexes` on `sensors_to_state_indexes`.`state_index_id` = `state_indexes`.`state_index_id` left join `state_translations` on `state_indexes`.`state_index_id` = `state_translations`.`state_index_id` where ((sensors.sensor_current = state_translations.state_value && state_translations.state_generic_value = 2) = ?)", ["1"]]
|
||||
],
|
||||
[
|
||||
"",
|
||||
{"condition":"AND","rules":[{"id":"macros.device","field":"macros.device","type":"integer","input":"radio","operator":"equal","value":"1"},{"condition":"OR","rules":[{"id":"ports.ifName","field":"ports.ifName","type":"string","input":"text","operator":"equal","value":"Ge12"},{"id":"ports.ifName","field":"ports.ifName","type":"string","input":"text","operator":"equal","value":"Ge13"}]},{"id":"ports.ifInOctets_delta","field":"ports.ifInOctets_delta","type":"integer","input":"number","operator":"greater","value":"42"}],"valid":true},
|
||||
"macros.device = 1 AND (ports.ifName = \"Ge12\" OR ports.ifName = \"Ge13\") AND ports.ifInOctets_delta > 42",
|
||||
"SELECT * FROM devices,ports WHERE (devices.device_id = ? AND devices.device_id = ports.device_id) AND (devices.disabled = 0 && devices.ignore = 0) = 1 AND (ports.ifName = \"Ge12\" OR ports.ifName = \"Ge13\") AND ports.ifInOctets_delta > 42"
|
||||
"SELECT * FROM devices,ports WHERE (devices.device_id = ? AND devices.device_id = ports.device_id) AND (devices.disabled = 0 && devices.ignore = 0) = 1 AND (ports.ifName = \"Ge12\" OR ports.ifName = \"Ge13\") AND ports.ifInOctets_delta > 42",
|
||||
["select * from `devices` left join `ports` on `devices`.`device_id` = `ports`.`device_id` where ((devices.disabled = 0 && devices.ignore = 0) = ? AND (`ports`.`ifName` = ? OR `ports`.`ifName` = ?) AND `ports`.`ifInOctets_delta` > ?)", ["1","Ge12","Ge13","42"]]
|
||||
],
|
||||
[
|
||||
"%bills.bill_name = \"Neil's Bill\"",
|
||||
{"condition":"AND","rules":[{"id":"bills.bill_name","field":"bills.bill_name","type":"string","input":"text","operator":"equal","value":"Neil's Bill"}],"valid":true},
|
||||
"bills.bill_name = \"Neil's Bill\"",
|
||||
"SELECT * FROM devices,ports,bill_ports,bills WHERE (devices.device_id = ? AND devices.device_id = ports.device_id AND ports.port_id = bill_ports.port_id AND bill_ports.bill_id = bills.bill_id) AND bills.bill_name = \"Neil's Bill\""
|
||||
"SELECT * FROM devices,ports,bill_ports,bills WHERE (devices.device_id = ? AND devices.device_id = ports.device_id AND ports.port_id = bill_ports.port_id AND bill_ports.bill_id = bills.bill_id) AND bills.bill_name = \"Neil's Bill\"",
|
||||
["select * from `devices` left join `ports` on `devices`.`device_id` = `ports`.`device_id` left join `bill_ports` on `ports`.`port_id` = `bill_ports`.`port_id` left join `bills` on `bill_ports`.`bill_id` = `bills`.`bill_id` where (`bills`.`bill_name` = ?)", ["Neil's Bill"]]
|
||||
],
|
||||
[
|
||||
"%ports.ifOutErrors_rate >= \"100\" || %ports.ifInErrors_rate >= \"100\"",
|
||||
{"condition":"OR","rules":[{"id":"ports.ifOutErrors_rate","field":"ports.ifOutErrors_rate","type":"string","input":"text","operator":"greater_or_equal","value":"100"},{"id":"ports.ifInErrors_rate","field":"ports.ifInErrors_rate","type":"string","input":"text","operator":"greater_or_equal","value":"100"}],"valid":true},
|
||||
"ports.ifOutErrors_rate >= 100 OR ports.ifInErrors_rate >= 100",
|
||||
"SELECT * FROM devices,ports WHERE (devices.device_id = ? AND devices.device_id = ports.device_id) AND (ports.ifOutErrors_rate >= 100 OR ports.ifInErrors_rate >= 100)"
|
||||
"SELECT * FROM devices,ports WHERE (devices.device_id = ? AND devices.device_id = ports.device_id) AND (ports.ifOutErrors_rate >= 100 OR ports.ifInErrors_rate >= 100)",
|
||||
["select * from `devices` left join `ports` on `devices`.`device_id` = `ports`.`device_id` where (`ports`.`ifOutErrors_rate` >= ? OR `ports`.`ifInErrors_rate` >= ?)", ["100","100"]]
|
||||
],
|
||||
[
|
||||
"%devices.hostname = %devices.sysName &&",
|
||||
{"condition":"AND","rules":[{"id":"devices.hostname","field":"devices.hostname","type":"string","input":"text","operator":"equal","value":"`devices.sysName`"}],"valid":true},
|
||||
"devices.hostname = devices.sysName",
|
||||
"SELECT * FROM devices WHERE (devices.device_id = ?) AND devices.hostname = devices.sysName",
|
||||
["select * from `devices` where (`devices`.`hostname` = devices.sysName)", []]
|
||||
],
|
||||
[
|
||||
"%devices.hostname ~ \"%router\" &&",
|
||||
{"condition":"AND","rules":[{"id":"devices.hostname","field":"devices.hostname","type":"string","input":"text","operator":"regex","value":"router"}],"valid":true},
|
||||
"devices.hostname REGEXP \"router\"",
|
||||
"SELECT * FROM devices WHERE (devices.device_id = ?) AND devices.hostname REGEXP \"router\"",
|
||||
["select * from `devices` where (`devices`.`hostname` REGEXP ?)", ["router"]]
|
||||
],
|
||||
[
|
||||
"syslog.timestamp > %macros.past_5m && syslog.msg ~ \"@Port Down@\"",
|
||||
{"condition":"AND","rules":[{"id":"syslog.timestamp","field":"syslog.timestamp","type":"datetime","input":"text","operator":"greater","value":"`macros.past_5m`"},{"id":"syslog.msg","field":"syslog.msg","type":"string","input":"text","operator":"regex","value":".*Port Down.*"}],"valid":true},
|
||||
"syslog.timestamp > macros.past_5m AND syslog.msg REGEXP \".*Port Down.*\"",
|
||||
"SELECT * FROM devices,syslog WHERE (devices.device_id = ? AND devices.device_id = syslog.device_id) AND syslog.timestamp > (DATE_SUB(NOW(),INTERVAL 5 MINUTE)) AND syslog.msg REGEXP \".*Port Down.*\"",
|
||||
["select * from `devices` left join `syslog` on `devices`.`device_id` = `syslog`.`device_id` where (`syslog`.`timestamp` > (DATE_SUB(NOW(),INTERVAL 5 MINUTE)) AND `syslog`.`msg` REGEXP ?)", [".*Port Down.*"]]
|
||||
],
|
||||
[
|
||||
"%macros.port_usage_perc > 80",
|
||||
{"condition":"AND","rules":[{"id":"macros.port_usage_perc","field":"macros.port_usage_perc","type":"integer","input":"text","operator":"greater","value":"80"}],"valid":true},
|
||||
"macros.port_usage_perc > 80",
|
||||
"SELECT * FROM devices,ports WHERE (devices.device_id = ? AND devices.device_id = ports.device_id) AND (((ports.ifInOctets_rate*8) / ports.ifSpeed)*100) > 80",
|
||||
["select * from `devices` left join `ports` on `devices`.`device_id` = `ports`.`device_id` where ((((ports.ifInOctets_rate*8) / ports.ifSpeed)*100) > ?)", ["80"]]],
|
||||
[
|
||||
"%devices.sysName ~ \"..domain.com\" || %devices.sysName ~ \"switch.\" &&",
|
||||
{"condition":"OR","rules":[{"id":"devices.sysName","field":"devices.sysName","type":"string","input":"text","operator":"regex","value":"..domain.com"},{"id":"devices.sysName","field":"devices.sysName","type":"string","input":"text","operator":"regex","value":"switch."}],"valid":true},
|
||||
"devices.sysName REGEXP \"..domain.com\" OR devices.sysName REGEXP \"switch.\"",
|
||||
"SELECT * FROM devices WHERE (devices.device_id = ?) AND (devices.sysName REGEXP \"..domain.com\" OR devices.sysName REGEXP \"switch.\")",
|
||||
["select * from `devices` where (`devices`.`sysName` REGEXP ? OR `devices`.`sysName` REGEXP ?)", ["..domain.com","switch."]]
|
||||
],
|
||||
[
|
||||
"",
|
||||
{"condition":"AND","rules":[{"id":"devices.hostname","field":"devices.hostname","type":"string","input":"text","operator":"contains","value":"one"},{"condition":"OR","rules":[{"id":"devices.hostname","field":"devices.hostname","type":"string","input":"text","operator":"contains","value":"two"},{"id":"devices.hostname","field":"devices.hostname","type":"string","input":"text","operator":"contains","value":"three"},{"condition":"AND","rules":[{"id":"devices.hostname","field":"devices.hostname","type":"string","input":"text","operator":"begins_with","value":"six"},{"id":"devices.hostname","field":"devices.hostname","type":"string","input":"text","operator":"equal","value":"seven"}]}]},{"condition":"OR","rules":[{"id":"devices.hostname","field":"devices.hostname","type":"string","input":"text","operator":"not_contains","value":"four"},{"id":"devices.hostname","field":"devices.hostname","type":"string","input":"text","operator":"not_contains","value":"five"}]}],"valid":true},
|
||||
"devices.hostname LIKE '%one%' AND (devices.hostname LIKE '%two%' OR devices.hostname LIKE '%three%' OR (devices.hostname LIKE 'six%' AND devices.hostname = \"seven\")) AND (devices.hostname NOT LIKE '%four%' OR devices.hostname NOT LIKE '%five%')",
|
||||
"SELECT * FROM devices WHERE (devices.device_id = ?) AND devices.hostname LIKE '%one%' AND (devices.hostname LIKE '%two%' OR devices.hostname LIKE '%three%' OR (devices.hostname LIKE 'six%' AND devices.hostname = \"seven\")) AND (devices.hostname NOT LIKE '%four%' OR devices.hostname NOT LIKE '%five%')",
|
||||
["select * from `devices` where (`devices`.`hostname` LIKE ? AND (`devices`.`hostname` LIKE ? OR `devices`.`hostname` LIKE ? OR (`devices`.`hostname` LIKE ? AND `devices`.`hostname` = ?)) AND (`devices`.`hostname` NOT LIKE ? OR `devices`.`hostname` NOT LIKE ?))", ["%one%","%two%","%three%","six%","seven","%four%","%five%"]]
|
||||
]
|
||||
]
|
||||
|
|
Loading…
Reference in New Issue