mirror of
https://github.com/librenms/librenms.git
synced 2024-10-07 16:52:45 +00:00
feature: Added new alert rule builder UI and rule mapping UI (#8293)
* feature: Added new alert rule builder UI * Updated to export sql queries * More updates * more changes * removed debug * fix scrut * Updated to include import options + various other fixes * fix rule * Populate name from collection rules. * Fix default rule import Allow new and old style rules in the collection. Don't add new yet as I'm not sure GenSQL() is working. * Fix GenSQL call * Extract filter building to class so it is nicely contained in one place * moved schema * some fixes and tweaks * travis fixes * Some more features / updates * Fix up my mistakes when adding default rules * Use a modal for new alert (Incomplete) Larger dialog!! Remove page loading stuff. Working: Loading rules, resetting dialog, importing from collection. Not working yet: select width device limited rule access? don't know what this is... Lots of unused stuff to delete... * reload "table" after save * fixed editing rule * Auto select2 width * Reload window on save * Restore per-device alert. Remove debug. * Small cleanups. Rule Name first. * Restore button to button type. Rename schema. * Fixes: wrong command to reload window, remove extra attributes, rule is never passed * Fixed old rule editing * some small updates for old imports * travis update to use trusty * maybe travis fix * Ability to set alert rule mappings on the rule edit screen * pip installs one line, no quiet for deploy * update schema def * Fix style and some copyright headers * fix docs missing file * Allow new versions of snmpsim and libraries * Parser WIP * Fix default rules insert * reorganize * Legacy import first draft done * Implement saving Skip translation to sql for now * Working on glues * small rule collection fix * Working on glues * Working on glues * Docs updates + small UI changes * Parser WIP * reorganize * Legacy import first draft done * Implement saving Skip translation to sql for now * Working on glues * Working on glues * Working on glues * Add table mapping, should move to it's own class * WIP * Glue working!! * Extract Schema class * Some final touches. revert alerts_rules.json for now. * Finish up initial implementation Needs more tests * Fix a few places * small doc updates * Fix finding tables in grouped rules. * remove unused code * code format fixes * Some quick tests for Schema Simplified output for findRelationshipPath. Always includes start and target in the result. This simplifies a lot of code in QueryBuilderParser.php This also always loads the target table data now (which we want) * Make bill_id the PRIMARY index for the bills table * Load macros from a json file in misc instead of the database. * Fix whitespace and wrong key for collection. * Handle IN properly when generating SQL * Fix glue (devices.device_id = ports.port_id) is incorrect :D Show ALL tables we can resolve relationships for in the query builder filter. * Remove all macros from the database Remove insert statements, leave updates to update user's existing rules.
This commit is contained in:
@ -31,10 +31,7 @@ before_install:
|
||||
|
||||
install:
|
||||
- composer install --prefer-dist --no-interaction
|
||||
- pip install --user pyasn1==0.2.2
|
||||
- pip install --user snmpsim==0.3.1
|
||||
- pip install --user pylint
|
||||
- pip install --user mysql-python
|
||||
- pip install --user snmpsim pylint mysql-python
|
||||
|
||||
after_failure:
|
||||
- tail /tmp/snmpsimd.log
|
||||
|
179
LibreNMS/Alerting/QueryBuilderFilter.php
Normal file
179
LibreNMS/Alerting/QueryBuilderFilter.php
Normal file
@ -0,0 +1,179 @@
|
||||
<?php
|
||||
/**
|
||||
* QueryBuilderFilter.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 2018 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace LibreNMS\Alerting;
|
||||
|
||||
use LibreNMS\Config;
|
||||
use LibreNMS\DB\Schema;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class QueryBuilderFilter implements \JsonSerializable
|
||||
{
|
||||
private static $table_blacklist = [
|
||||
'device_group_device'
|
||||
];
|
||||
|
||||
private $filter = [];
|
||||
private $schema;
|
||||
|
||||
/**
|
||||
* QueryBuilderFilter constructor.
|
||||
* @param string $type alert|group
|
||||
*/
|
||||
public function __construct($type = 'alert')
|
||||
{
|
||||
$this->schema = new Schema();
|
||||
|
||||
if ($type == 'alert') {
|
||||
$this->generateMacroFilter('alert.macros.rule');
|
||||
} elseif ($type == 'group') {
|
||||
$this->generateMacroFilter('alert.macros.group');
|
||||
}
|
||||
|
||||
$this->generateTableFilter();
|
||||
}
|
||||
|
||||
private function generateMacroFilter($config_location)
|
||||
{
|
||||
$macros = Config::get($config_location, []);
|
||||
krsort($macros);
|
||||
|
||||
foreach ($macros as $key => $value) {
|
||||
$field = 'macros.' . $key;
|
||||
|
||||
if (ends_with($key, '_usage_perc')) {
|
||||
$this->filter[$field] = [
|
||||
'id' => $field,
|
||||
'type' => 'integer',
|
||||
];
|
||||
} else {
|
||||
$this->filter[$field] = [
|
||||
'id' => $field,
|
||||
'type' => 'integer',
|
||||
'input' => 'radio',
|
||||
'values' => ['1' => 'Yes', '0' => 'No'],
|
||||
'operators' => ['equal'],
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function generateTableFilter()
|
||||
{
|
||||
$db_schema = $this->schema->getSchema();
|
||||
$valid_tables = array_diff(array_keys($this->schema->getAllRelationshipPaths()), self::$table_blacklist);
|
||||
|
||||
foreach ((array)$db_schema as $table => $data) {
|
||||
$columns = array_column($data['Columns'], 'Type', 'Field');
|
||||
|
||||
// only allow tables with a direct association to device_id
|
||||
if (!in_array($table, $valid_tables)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($columns as $column => $column_type) {
|
||||
// ignore device id columns, except in the devices table
|
||||
if ($column == 'device_id' && $table != 'devices') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$type = $this->getColumnType($column_type);
|
||||
|
||||
// ignore unsupported types (such as binary and blob)
|
||||
if (is_null($type)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$field = "$table.$column";
|
||||
|
||||
// format enums as radios
|
||||
if ($type == 'enum') {
|
||||
$values = explode(',', substr($column_type, 4));
|
||||
$values = array_map(function ($val) {
|
||||
return trim($val, "()' ");
|
||||
}, $values);
|
||||
|
||||
$this->filter[$field] = [
|
||||
'id' => $field,
|
||||
'type' => 'integer',
|
||||
'input' => 'radio',
|
||||
'values' => $values,
|
||||
'operators' => ['equal'],
|
||||
];
|
||||
} else {
|
||||
$this->filter[$field] = [
|
||||
'id' => $field,
|
||||
'type' => $type,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function getColumnType($type)
|
||||
{
|
||||
if (starts_with($type, ['varchar', 'text', 'double', 'float'])) {
|
||||
return 'string';
|
||||
} elseif (starts_with($type, ['int', 'tinyint', 'smallint', 'mediumint', 'bigint'])) {
|
||||
return 'integer';
|
||||
} elseif (starts_with($type, ['timestamp', 'datetime'])) {
|
||||
return 'datetime';
|
||||
} elseif (starts_with($type, 'enum')) {
|
||||
return 'enum';
|
||||
}
|
||||
|
||||
// binary, blob
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Specify data which should be serialized to JSON
|
||||
* @link http://php.net/manual/en/jsonserializable.jsonserialize.php
|
||||
* @return mixed data which can be serialized by <b>json_encode</b>,
|
||||
* which is a value of any type other than a resource.
|
||||
* @since 5.4.0
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return array_values($this->filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filter for a specific item
|
||||
*
|
||||
* @param string $id
|
||||
* @return array|null
|
||||
*/
|
||||
public function getFilter($id)
|
||||
{
|
||||
if (array_key_exists($id, $this->filter)) {
|
||||
return $this->filter[$id];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
442
LibreNMS/Alerting/QueryBuilderParser.php
Normal file
442
LibreNMS/Alerting/QueryBuilderParser.php
Normal file
@ -0,0 +1,442 @@
|
||||
<?php
|
||||
/**
|
||||
* QueryBuilderParser.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 2018 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace LibreNMS\Alerting;
|
||||
|
||||
use LibreNMS\Config;
|
||||
use LibreNMS\DB\Schema;
|
||||
|
||||
class QueryBuilderParser implements \JsonSerializable
|
||||
{
|
||||
private static $legacy_operators = [
|
||||
'=' => 'equal',
|
||||
'!=' => 'not_equal',
|
||||
'~' => 'regex',
|
||||
'!~' => 'not_regex',
|
||||
'<' => 'less',
|
||||
'>' => 'greater',
|
||||
'<=' => 'less_or_equal',
|
||||
'>=' => 'greater_or_equal',
|
||||
];
|
||||
private static $operators = [
|
||||
'equal' => "=",
|
||||
'not_equal' => "!=",
|
||||
'in' => "IN (?)",
|
||||
'not_in' => "NOT IN (?)",
|
||||
'less' => "<",
|
||||
'less_or_equal' => "<=",
|
||||
'greater' => ">",
|
||||
'greater_or_equal' => ">=",
|
||||
'begins_with' => "ILIKE",
|
||||
'not_begins_with' => "NOT ILIKE",
|
||||
'contains' => "ILIKE",
|
||||
'not_contains' => "NOT ILIKE",
|
||||
'ends_with' => "ILIKE",
|
||||
'not_ends_with' => "NOT ILIKE",
|
||||
'is_empty' => "=''",
|
||||
'is_not_empty' => "!=''",
|
||||
'is_null' => "IS NULL",
|
||||
'is_not_null' => "IS NOT NULL",
|
||||
'regex' => 'REGEXP',
|
||||
'not_regex' => 'NOT REGEXP',
|
||||
];
|
||||
|
||||
private $builder;
|
||||
private $schema;
|
||||
|
||||
private function __construct(array $builder)
|
||||
{
|
||||
$this->builder = $builder;
|
||||
$this->schema = new Schema();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all tables used by this rule
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getTables()
|
||||
{
|
||||
if (!isset($this->tables)) {
|
||||
$this->tables = $this->findTablesRecursive($this->builder);
|
||||
}
|
||||
|
||||
return $this->tables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively find tables (including expanding macros) in the given rules
|
||||
*
|
||||
* @param array $rules
|
||||
* @return array List of tables found in rules
|
||||
*/
|
||||
private function findTablesRecursive($rules)
|
||||
{
|
||||
$tables = [];
|
||||
|
||||
foreach ($rules['rules'] as $rule) {
|
||||
if (array_key_exists('rules', $rule)) {
|
||||
$tables = array_merge($this->findTablesRecursive($rule), $tables);
|
||||
} elseif (str_contains($rule['field'], '.')) {
|
||||
list($table, $column) = explode('.', $rule['field']);
|
||||
|
||||
if ($table == 'macros') {
|
||||
$tables = array_merge($this->expandMacro($rule['field'], true), $tables);
|
||||
} else {
|
||||
$tables[] = $table;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// resolve glue tables (remove duplicates)
|
||||
foreach (array_keys(array_flip($tables)) as $table) {
|
||||
$rp = $this->schema->findRelationshipPath($table);
|
||||
if ($rp) {
|
||||
$tables = array_merge($rp, $tables);
|
||||
}
|
||||
}
|
||||
|
||||
// remove duplicates
|
||||
return array_keys(array_flip($tables));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize this from json generated by jQuery QueryBuilder
|
||||
*
|
||||
* @param string|array $json
|
||||
* @return static
|
||||
*/
|
||||
public static function fromJson($json)
|
||||
{
|
||||
if (!is_array($json)) {
|
||||
$json = json_decode($json, true);
|
||||
}
|
||||
|
||||
return new static($json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize this from a legacy LibreNMS rule
|
||||
*
|
||||
* @param string $query
|
||||
* @return static
|
||||
*/
|
||||
public static function fromOld($query)
|
||||
{
|
||||
$condition = null;
|
||||
$rules = [];
|
||||
$filter = new QueryBuilderFilter();
|
||||
|
||||
$split = array_chunk(preg_split('/(&&|\|\|)/', $query, -1, PREG_SPLIT_DELIM_CAPTURE), 2);
|
||||
|
||||
foreach ($split as $chunk) {
|
||||
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);
|
||||
$field = ltrim($field, '%');
|
||||
|
||||
// for rules missing values just use '= 1'
|
||||
$operator = isset(self::$legacy_operators[$op]) ? self::$legacy_operators[$op] : 'equal';
|
||||
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, '%') . '`';
|
||||
}
|
||||
|
||||
// replace regex placeholder, don't think we can safely convert to like operators
|
||||
if ($operator == 'regex' || $operator == 'not_regex') {
|
||||
$value = str_replace('@', '.*', $value);
|
||||
}
|
||||
}
|
||||
|
||||
$filter_item = $filter->getFilter($field);
|
||||
|
||||
$type = $filter_item['type'];
|
||||
$input = isset($filter_item['input']) ? $filter_item['input'] : 'text';
|
||||
|
||||
$rules[] = [
|
||||
'id' => $field,
|
||||
'field' => $field,
|
||||
'type' => $type,
|
||||
'input' => $input,
|
||||
'operator' => $operator,
|
||||
'value' => $value,
|
||||
];
|
||||
}
|
||||
|
||||
$builder = [
|
||||
'condition' => $condition,
|
||||
'rules' => $rules,
|
||||
'valid' => true,
|
||||
];
|
||||
|
||||
return new static($builder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SQL for this rule, ready to execute with device_id supplied as the parameter
|
||||
* If $expand is false, this will return a more readable representation of the rule, but not executable.
|
||||
*
|
||||
* @param bool $expand
|
||||
* @return null|string The rule or null if this is invalid.
|
||||
*/
|
||||
public function toSql($expand = true)
|
||||
{
|
||||
if (empty($this->builder) || !array_key_exists('condition', $this->builder)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$result = [];
|
||||
foreach ($this->builder['rules'] as $rule) {
|
||||
if (array_key_exists('condition', $rule)) {
|
||||
$result[] = $this->parseGroup($rule, $expand);
|
||||
} else {
|
||||
$result[] = $this->parseRule($rule, $expand);
|
||||
}
|
||||
}
|
||||
|
||||
$sql = '';
|
||||
if ($expand) {
|
||||
$sql = 'SELECT * FROM ' . implode(',', $this->getTables());
|
||||
$sql .= ' WHERE ' . $this->generateGlue() . ' AND ';
|
||||
}
|
||||
return $sql . implode(" {$this->builder['condition']} ", $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a rule group
|
||||
*
|
||||
* @param $rule
|
||||
* @param bool $expand Expand macros?
|
||||
* @return string
|
||||
*/
|
||||
private function parseGroup($rule, $expand = false)
|
||||
{
|
||||
$group_rules = [];
|
||||
|
||||
foreach ($rule['rules'] as $group_rule) {
|
||||
if (array_key_exists('condition', $group_rule)) {
|
||||
$group_rules[] = $this->parseGroup($group_rule, $expand);
|
||||
} else {
|
||||
$group_rules[] = $this->parseRule($group_rule, $expand);
|
||||
}
|
||||
}
|
||||
|
||||
$sql = implode(" {$rule['condition']} ", $group_rules);
|
||||
return "($sql)";
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a rule
|
||||
*
|
||||
* @param $rule
|
||||
* @param bool $expand Expand macros?
|
||||
* @return string
|
||||
*/
|
||||
private function parseRule($rule, $expand = false)
|
||||
{
|
||||
$op = self::$operators[$rule['operator']];
|
||||
$value = $rule['value'];
|
||||
|
||||
if (starts_with($value, '`') && ends_with($value, '`')) {
|
||||
// pass through value such as field
|
||||
$value = trim($value, '`');
|
||||
$value = $this->expandMacro($value); // check for macros
|
||||
} elseif ($rule['type'] != 'integer') {
|
||||
$value = "\"$value\"";
|
||||
}
|
||||
|
||||
$field = $rule['field'];
|
||||
if ($expand) {
|
||||
$field = $this->expandMacro($field);
|
||||
}
|
||||
|
||||
if (str_contains($op, '?')) {
|
||||
// op with value inside it aka IN and NOT IN
|
||||
$sql = "$field " . str_replace('?', $value, $op);
|
||||
} else {
|
||||
$sql = "$field $op $value";
|
||||
}
|
||||
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand macro to sql
|
||||
*
|
||||
* @param $subject
|
||||
* @param bool $tables_only Used when finding tables in query returns an array instead of sql string
|
||||
* @param int $depth_limit
|
||||
* @return string|array
|
||||
*/
|
||||
private function expandMacro($subject, $tables_only = false, $depth_limit = 20)
|
||||
{
|
||||
if (!str_contains($subject, 'macros.')) {
|
||||
return $subject;
|
||||
}
|
||||
|
||||
$macros = Config::get('alert.macros.rule');
|
||||
|
||||
$count = 0;
|
||||
while ($count++ < $depth_limit && str_contains($subject, 'macros.')) {
|
||||
$subject = preg_replace_callback('/%?macros.([^ =()]+)/', function ($matches) use ($macros) {
|
||||
$name = $matches[1];
|
||||
if (isset($macros[$name])) {
|
||||
return $macros[$name];
|
||||
} else {
|
||||
return $matches[0]; // this isn't a macro, don't replace
|
||||
}
|
||||
}, $subject);
|
||||
}
|
||||
|
||||
if ($tables_only) {
|
||||
preg_match_all('/%([^%.]+)\./', $subject, $matches);
|
||||
return array_unique($matches[1]);
|
||||
}
|
||||
|
||||
// clean leading %
|
||||
$subject = preg_replace('/%([^%.]+)\./', '$1.', $subject);
|
||||
|
||||
// wrap entire macro result in parenthesis if needed
|
||||
if (!(starts_with($subject, '(') && ends_with($subject, ')'))) {
|
||||
$subject = "($subject)";
|
||||
}
|
||||
|
||||
return $subject;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
private function generateGlue($target = 'devices')
|
||||
{
|
||||
$tables = $this->getTables(); // get all tables in query
|
||||
|
||||
// always add the anchor to the target table
|
||||
$anchor = $target . '.' . $this->schema->getPrimaryKey($target) . ' = ?';
|
||||
$glue = [$anchor];
|
||||
|
||||
foreach ($tables as $table) {
|
||||
$path = $this->schema->findRelationshipPath($table, $target);
|
||||
if ($path) {
|
||||
foreach (array_pairs($path) as $pair) {
|
||||
list($left, $right) = $pair;
|
||||
$glue[] = $this->getGlue($left, $right);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove duplicates
|
||||
$glue = array_unique($glue);
|
||||
|
||||
return '(' . implode(' AND ', $glue) . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get glue sql between tables. Resolve fields to use.
|
||||
*
|
||||
* @param string $parent
|
||||
* @param string $child
|
||||
* @return string
|
||||
*/
|
||||
public function getGlue($parent, $child)
|
||||
{
|
||||
// first check to see if there is a single shared column name ending with _id
|
||||
$shared_keys = array_filter(array_intersect(
|
||||
$this->schema->getColumns($parent),
|
||||
$this->schema->getColumns($child)
|
||||
), function ($table) {
|
||||
return ends_with($table, '_id');
|
||||
});
|
||||
|
||||
if (count($shared_keys) === 1) {
|
||||
$shared_key = reset($shared_keys);
|
||||
return "$parent.$shared_key = $child.$shared_key";
|
||||
}
|
||||
|
||||
$parent_key = $this->schema->getPrimaryKey($parent);
|
||||
$flipped = empty($parent_key);
|
||||
if ($flipped) {
|
||||
// if the "parent" table doesn't have a primary key, flip them
|
||||
list($parent, $child) = [$child, $parent];
|
||||
$parent_key = $this->schema->getPrimaryKey($parent);
|
||||
}
|
||||
$child_key = $parent_key; // assume the column names match
|
||||
|
||||
if (!$this->schema->columnExists($child, $child_key)) {
|
||||
// if they don't match, guess the column name from the parent
|
||||
if (ends_with($parent, 'xes')) {
|
||||
$child_key = substr($parent, 0, -2) . '_id';
|
||||
} else {
|
||||
$child_key = preg_replace('/s$/', '_id', $parent);
|
||||
}
|
||||
|
||||
if (!$this->schema->columnExists($child, $child_key)) {
|
||||
echo"FIXME: Could not make glue from $child to $parent\n";
|
||||
}
|
||||
}
|
||||
|
||||
if ($flipped) {
|
||||
return "$child.$child_key = $parent.$parent_key";
|
||||
}
|
||||
|
||||
return "$parent.$parent_key = $child.$child_key";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of this rule ready for jQuery QueryBuilder
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify data which should be serialized to JSON
|
||||
* @link http://php.net/manual/en/jsonserializable.jsonserialize.php
|
||||
* @return mixed data which can be serialized by <b>json_encode</b>,
|
||||
* which is a value of any type other than a resource.
|
||||
* @since 5.4.0
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->builder;
|
||||
}
|
||||
}
|
@ -47,6 +47,10 @@ class Config
|
||||
require $install_dir . '/includes/defaults.inc.php';
|
||||
require $install_dir . '/includes/definitions.inc.php';
|
||||
|
||||
// import standard settings
|
||||
$macros = json_decode(file_get_contents($install_dir . '/misc/macros.json'), true);
|
||||
self::set('alert.macros.rule', $macros);
|
||||
|
||||
// variable definitions (remove me)
|
||||
require $install_dir . '/includes/vmware_guestid.inc.php';
|
||||
|
||||
|
254
LibreNMS/DB/Schema.php
Normal file
254
LibreNMS/DB/Schema.php
Normal file
@ -0,0 +1,254 @@
|
||||
<?php
|
||||
/**
|
||||
* Schema.php
|
||||
*
|
||||
* Class for querying the schema
|
||||
*
|
||||
* 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 2018 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace LibreNMS\DB;
|
||||
|
||||
use LibreNMS\Config;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class Schema
|
||||
{
|
||||
private static $relationship_blacklist = [
|
||||
'devices_perms',
|
||||
'bill_perms',
|
||||
'ports_perms',
|
||||
];
|
||||
|
||||
private $relationships;
|
||||
private $schema;
|
||||
|
||||
/**
|
||||
* Get the primary key column(s) for a table
|
||||
*
|
||||
* @param string $table
|
||||
* @return string|array if a single column just the name is returned, otherwise an array of column names
|
||||
*/
|
||||
public function getPrimaryKey($table)
|
||||
{
|
||||
$schema = $this->getSchema();
|
||||
|
||||
$columns = $schema[$table]['Indexes']['PRIMARY']['Columns'];
|
||||
|
||||
if (count($columns) == 1) {
|
||||
return reset($columns);
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
public function getSchema()
|
||||
{
|
||||
if (!isset($this->schema)) {
|
||||
$file = Config::get('install_dir') . '/misc/db_schema.yaml';
|
||||
$this->schema = Yaml::parse(file_get_contents($file));
|
||||
}
|
||||
|
||||
return $this->schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all tables.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getTables()
|
||||
{
|
||||
return array_keys($this->getSchema());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all columns for the given table
|
||||
*
|
||||
* @param $table
|
||||
* @return array
|
||||
*/
|
||||
public function getColumns($table)
|
||||
{
|
||||
$schema = $this->getSchema();
|
||||
return array_column($schema[$table]['Columns'], 'Field');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all relationship paths.
|
||||
* Caches the data after the first call as long as the schema hasn't changed
|
||||
*
|
||||
* @param string $base
|
||||
* @return mixed
|
||||
*/
|
||||
public function getAllRelationshipPaths($base = 'devices')
|
||||
{
|
||||
$update_cache = true;
|
||||
$cache_file = Config::get('install_dir') . "/cache/{$base}_relationships.cache";
|
||||
|
||||
if (is_file($cache_file)) {
|
||||
$cache = unserialize(file_get_contents($cache_file));
|
||||
if ($cache['version'] == get_db_schema()) {
|
||||
$update_cache = false; // cache is valid skip update
|
||||
}
|
||||
}
|
||||
|
||||
if ($update_cache) {
|
||||
$paths = [];
|
||||
foreach ($this->getTables() as $table) {
|
||||
$path = $this->findPathRecursive([$table], $base);
|
||||
if ($path) {
|
||||
$paths[$table] = $path;
|
||||
}
|
||||
}
|
||||
|
||||
$cache = [
|
||||
'version' => get_db_schema(),
|
||||
$base => $paths
|
||||
];
|
||||
|
||||
file_put_contents($cache_file, serialize($cache));
|
||||
}
|
||||
|
||||
return $cache[$base];
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the relationship path from $start to $target
|
||||
*
|
||||
* @param string $target
|
||||
* @param string $start Default: devices
|
||||
* @return array|false list of tables in path order, or false if no path is found
|
||||
*/
|
||||
public function findRelationshipPath($target, $start = 'devices')
|
||||
{
|
||||
d_echo("Searching for target: $start, starting with $target\n");
|
||||
|
||||
if ($target == $start) {
|
||||
// um, yeah, we found it...
|
||||
return [$start];
|
||||
}
|
||||
|
||||
$all = $this->getAllRelationshipPaths($start);
|
||||
|
||||
return isset($all[$target]) ? $all[$target] : false;
|
||||
}
|
||||
|
||||
private function findPathRecursive(array $tables, $target, $history = [])
|
||||
{
|
||||
$relationships = $this->getTableRelationships();
|
||||
|
||||
d_echo("Starting Tables: " . json_encode($tables) . PHP_EOL);
|
||||
if (!empty($history)) {
|
||||
$tables = array_diff($tables, $history);
|
||||
d_echo("Filtered Tables: " . json_encode($tables) . PHP_EOL);
|
||||
}
|
||||
|
||||
foreach ($tables as $table) {
|
||||
$table_relations = $relationships[$table];
|
||||
d_echo("Searching $table: " . json_encode($table_relations) . PHP_EOL);
|
||||
|
||||
if (!empty($table_relations)) {
|
||||
if (in_array($target, $relationships[$table])) {
|
||||
d_echo("Found in $table\n");
|
||||
return [$target, $table]; // found it
|
||||
} else {
|
||||
$recurse = $this->findPathRecursive($relationships[$table], $target, array_merge($history, $tables));
|
||||
if ($recurse) {
|
||||
return array_merge($recurse, [$table]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$relations = array_keys(array_filter($relationships, function ($related) use ($table) {
|
||||
return in_array($table, $related);
|
||||
}));
|
||||
|
||||
d_echo("Dead end at $table, searching for relationships " . json_encode($relations) . PHP_EOL);
|
||||
$recurse = $this->findPathRecursive($relations, $target, array_merge($history, $tables));
|
||||
if ($recurse) {
|
||||
return array_merge($recurse, [$table]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getTableRelationships()
|
||||
{
|
||||
if (!isset($this->relationships)) {
|
||||
$schema = $this->getSchema();
|
||||
|
||||
$relations = array_column(array_map(function ($table, $data) {
|
||||
$columns = array_column($data['Columns'], 'Field');
|
||||
|
||||
$related = array_filter(array_map(function ($column) use ($table) {
|
||||
$guess = $this->getTableFromKey($column);
|
||||
if ($guess != $table) {
|
||||
return $guess;
|
||||
}
|
||||
|
||||
return null;
|
||||
}, $columns));
|
||||
|
||||
// renumber $related array
|
||||
$related = array_values($related);
|
||||
|
||||
return [$table, $related];
|
||||
}, array_keys($schema), $schema), 1, 0);
|
||||
|
||||
// filter out blacklisted tables
|
||||
$this->relationships = array_diff_key($relations, array_flip(self::$relationship_blacklist));
|
||||
}
|
||||
|
||||
return $this->relationships;
|
||||
}
|
||||
|
||||
public function getTableFromKey($key)
|
||||
{
|
||||
if (ends_with($key, '_id')) {
|
||||
// hardcoded
|
||||
if ($key == 'app_id') {
|
||||
return 'applications';
|
||||
}
|
||||
|
||||
// try to guess assuming key_id = keys table
|
||||
$guessed_table = substr($key, 0, -3);
|
||||
|
||||
if (!ends_with($guessed_table, 's')) {
|
||||
if (ends_with($guessed_table, 'x')) {
|
||||
$guessed_table .= 'es';
|
||||
} else {
|
||||
$guessed_table .= 's';
|
||||
}
|
||||
}
|
||||
|
||||
if (array_key_exists($guessed_table, $this->getSchema())) {
|
||||
return $guessed_table;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function columnExists($table, $column)
|
||||
{
|
||||
return in_array($column, $this->getColumns($table));
|
||||
}
|
||||
}
|
@ -234,9 +234,9 @@ if ($options['f'] === 'refresh_alert_rules') {
|
||||
}
|
||||
|
||||
echo 'Refreshing alert rules queries' . PHP_EOL;
|
||||
$rules = dbFetchRows('SELECT `id`, `rule` FROM `alert_rules`');
|
||||
$rules = dbFetchRows('SELECT `id`, `rule`, `builder` FROM `alert_rules`');
|
||||
foreach ($rules as $rule) {
|
||||
$data['query'] = GenSQL($rule['rule']);
|
||||
$data['query'] = GenSQL($rule['rule'], $rule['builder']);
|
||||
if (!empty($data['query'])) {
|
||||
dbUpdate($data, 'alert_rules', 'id=?', array($rule['id']));
|
||||
unset($data);
|
||||
|
@ -4,6 +4,8 @@ source: Alerting/Entities.md
|
||||
|
||||
Entities as described earlier are based on the table and column names within the database, if you are unsure of what the entity is you want then have a browse around inside MySQL using `show tables` and `desc <tablename>`.
|
||||
|
||||
Below are some common entities that you can use within the alerting system. This list is not exhaustive and you should look at the MySQL database schema for the full list.
|
||||
|
||||
## Devices
|
||||
Entity | Description
|
||||
---|---
|
||||
@ -54,7 +56,7 @@ Entity | Description
|
||||
`ports.ifOperStatus` | The operational status of the port (up or down)
|
||||
`ports.ifAdminStatus` | The administrative status of the port (up or down)
|
||||
`ports.ifDuplex` | Duplex setting of the port
|
||||
`ports.ifMtu` | The MTU setting of the port.
|
||||
`ports.ifMtu` | The MTU setting of the port.`
|
||||
|
||||
## Processors
|
||||
Entity | Description
|
||||
|
@ -8,92 +8,86 @@ You can define your own macros in your `config.php`.
|
||||
|
||||
Example macro-implementation of Debian-Devices
|
||||
```php
|
||||
$config['alert']['macros']['rule']['is_debian'] = '%devices.features ~ "@debian@"';
|
||||
$config['alert']['macros']['rule']['is_debian'] = 'devices.features ~ "@debian@"';
|
||||
```
|
||||
And in the Rule:
|
||||
```
|
||||
... && %macros.is_debian = "1" && ...
|
||||
... macros.is_debian = 1 ...
|
||||
```
|
||||
|
||||
This Example-macro is a Boolean-macro, it applies a form of filter to the set of results defined by the rule.
|
||||
|
||||
All macros that are not unary should return Boolean.
|
||||
|
||||
You can only apply _Equal_ or _Not-Equal_ Operations on Boolean-macros where `True` is represented by `"1"` and `False` by `"0"`.
|
||||
|
||||
Example
|
||||
```php
|
||||
((%ports.ifInOctets_rate*8) / %ports.ifSpeed)*100
|
||||
```
|
||||
|
||||
## Device (Boolean)
|
||||
|
||||
Entity: `%macros.device`
|
||||
Entity: `macros.device`
|
||||
|
||||
Description: Only select devices that aren't deleted, ignored or disabled.
|
||||
|
||||
Source: `(%devices.disabled = "0" && %devices.ignore = "0")`
|
||||
Source: `(devices.disabled = 0 AND devices.ignore = 0)`
|
||||
|
||||
### Device is up (Boolean)
|
||||
|
||||
Entity: `%macros.device_up`
|
||||
Entity: `macros.device_up`
|
||||
|
||||
Description: Only select devices that are up.
|
||||
|
||||
Implies: %macros.device
|
||||
Implies: macros.device
|
||||
|
||||
Source: `(%devices.status = "1" && %macros.device)`
|
||||
Source: `(devices.status = 1 AND macros.device)`
|
||||
|
||||
### Device is down (Boolean)
|
||||
|
||||
Entity: `%macros.device_down`
|
||||
Entity: `macros.device_down`
|
||||
|
||||
Description: Only select devices that are down.
|
||||
|
||||
Implies: %macros.device
|
||||
Implies: macros.device
|
||||
|
||||
Source: `(%devices.status = "0" && %macros.device)`
|
||||
Source: `(devices.status = 0 AND macros.device)`
|
||||
|
||||
## Port (Boolean)
|
||||
|
||||
Entity: `%macros.port`
|
||||
Entity: `macros.port`
|
||||
|
||||
Description: Only select ports that aren't deleted, ignored or disabled.
|
||||
|
||||
Source: `(%ports.deleted = "0" && %ports.ignore = "0" && %ports.disabled = "0")`
|
||||
Source: `(ports.deleted = 0 AND ports.ignore = 0 AND ports.disabled = 0)`
|
||||
|
||||
### Port is up (Boolean)
|
||||
|
||||
Entity: `%macros.port_up`
|
||||
Entity: `macros.port_up`
|
||||
|
||||
Description: Only select ports that are up and also should be up.
|
||||
|
||||
Implies: %macros.port
|
||||
Implies: macros.port
|
||||
|
||||
Source: `(%ports.ifOperStatus = "up" && %ports.ifAdminStatus = "up" && %macros.port)`
|
||||
Source: `(ports.ifOperStatus = up AND ports.ifAdminStatus = up AND macros.port)`
|
||||
|
||||
### Port is down (Boolean)
|
||||
|
||||
Entity: `%macros.port_down`
|
||||
Entity: `macros.port_down`
|
||||
|
||||
Description: Only select ports that are down.
|
||||
|
||||
Implies: %macros.port
|
||||
Implies: macros.port
|
||||
|
||||
Source: `(%ports.ifOperStatus = "down" && %ports.ifAdminStatus != "down" && %macros.port)`
|
||||
Source: `(ports.ifOperStatus = "down" AND ports.ifAdminStatus != "down" AND macros.port)`
|
||||
|
||||
### Port-Usage in Percent (Decimal)
|
||||
|
||||
Entity: `%macros.port_usage_perc`
|
||||
Entity: `macros.port_usage_perc`
|
||||
|
||||
Description: Return port-usage in percent.
|
||||
|
||||
Source: `((%ports.ifInOctets_rate*8) / %ports.ifSpeed)*100`
|
||||
Source: `((ports.ifInOctets_rate*8) / ports.ifSpeed)*100`
|
||||
|
||||
## Time
|
||||
|
||||
### Now (Datetime)
|
||||
|
||||
Entity: `%macros.now`
|
||||
Entity: `macros.now`
|
||||
|
||||
Description: Alias of MySQL's NOW()
|
||||
|
||||
@ -101,11 +95,11 @@ Source: `NOW()`
|
||||
|
||||
### Past N Minutes (Datetime)
|
||||
|
||||
Entity: `%macros.past_$m`
|
||||
Entity: `macros.past_$m`
|
||||
|
||||
Description: Returns a MySQL Timestamp dated `$` Minutes in the past. `$` can only be a supported Resolution.
|
||||
|
||||
Example: `%macros.past_5m` is Last 5 Minutes.
|
||||
Example: `macros.past_5m` is Last 5 Minutes.
|
||||
|
||||
Resolution: 5,10,15,30,60
|
||||
|
||||
@ -113,86 +107,86 @@ Source: `DATE_SUB(NOW(),INTERVAL $ MINUTE)`
|
||||
|
||||
## Sensors (Boolean)
|
||||
|
||||
Entity: `%macros.sensor`
|
||||
Entity: `macros.sensor`
|
||||
|
||||
Description: Only select sensors that aren't ignored.
|
||||
|
||||
Source: `(%sensors.sensor_alert = 1)`
|
||||
Source: `(sensors.sensor_alert = 1)`
|
||||
|
||||
Entity: `%macros.sensor_port_link`
|
||||
Entity: `macros.sensor_port_link = 1`
|
||||
|
||||
Description: Only selects sensors that have a port linked to them, the port is up and the device is up.
|
||||
|
||||
Source: `(%sensors.entity_link_type = 'port' && %sensors.entity_link_index = %ports.ifIndex && %macros.port_up && %macros.device_up))`
|
||||
Source: `(sensors.entity_link_type = "port" AND sensors.entity_link_index = ports.ifIndex AND macros.port_up AND macros.device_up))`
|
||||
|
||||
## State Sensors (Boolean)
|
||||
|
||||
Entity: `%macros.state_sensor_ok`, `%macros.state_sensor_warning`, `%macros.state_sensor_critical`, `%macros.state_sensor_unknown`
|
||||
Entity: `macros.state_sensor_ok`, `macros.state_sensor_warning`, `macros.state_sensor_critical`, `macros.state_sensor_unknown`
|
||||
|
||||
Description: Select state sensors by their generic status ok (0), warning (1), critical (2), unknown (3)
|
||||
|
||||
Source: `(%sensors.sensor_current = %state_translations.state_value && %state_translations.state_generic_value = 2)`
|
||||
Source: `(sensors.sensor_current = state_translations.state_value AND state_translations.state_generic_value = 2)`
|
||||
|
||||
## Misc (Boolean)
|
||||
|
||||
### Packet Loss
|
||||
|
||||
Entity: `(%macros.packet_loss_5m)`
|
||||
Entity: `(macros.packet_loss_5m)`
|
||||
|
||||
Description: Packet loss % value for the device within the last 5 minutes.
|
||||
|
||||
Example: `%macros.packet_loss_5m` > 50
|
||||
Example: `macros.packet_loss_5m` > 50
|
||||
|
||||
Entity: `(%macros.packet_loss_15m)`
|
||||
Entity: `(macros.packet_loss_15m)`
|
||||
|
||||
Description: Packet loss % value for the device within the last 15 minutes.
|
||||
|
||||
Example: `%macros.packet_loss_15m` > 50
|
||||
Example: `macros.packet_loss_15m` > 50
|
||||
|
||||
### Ports in usage perc (Int)
|
||||
|
||||
Entity: `((%ports.ifInOctets_rate*8)/%ports.ifSpeed)*100`
|
||||
Entity: `((ports.ifInOctets_rate*8)/ports.ifSpeed)*100`
|
||||
|
||||
Description: Port in used more than 50%
|
||||
|
||||
Example: `%macros.port_in_usage_perc > 50
|
||||
Example: `macros.port_in_usage_perc > 50
|
||||
|
||||
### Ports out usage perc (Int)
|
||||
|
||||
Entity: `((%ports.ifOutOctets_rate*8)/%ports.ifSpeed)*100`
|
||||
Entity: `((ports.ifOutOctets_rate*8)/ports.ifSpeed)*100`
|
||||
|
||||
Description: Port out used more than 50%
|
||||
|
||||
Example: `%macros.port_out_usage_perc > 50
|
||||
Example: `macros.port_out_usage_perc > 50
|
||||
|
||||
### Ports now down (Boolean)
|
||||
|
||||
Entity: `%ports.ifOperStatus != %ports.ifOperStatus_prev && %ports.ifOperStatus_prev = "up" && %ports.ifAdminStatus = "up"`
|
||||
Entity: `ports.ifOperStatus != ports.ifOperStatus_prev AND ports.ifOperStatus_prev = "up" AND ports.ifAdminStatus = "up"`
|
||||
|
||||
Description: Ports that were previously up and have now gone down.
|
||||
|
||||
Example: `%macros.port_now_down = "1"`
|
||||
Example: `macros.port_now_down = 1`
|
||||
|
||||
### Device component down [JunOS]
|
||||
|
||||
Entity: `%sensors.sensor_class = "state" && %sensors.sensor_current != "6" && %sensors.sensor_type = "jnxFruState" && %sensors.sensor_current != "2"`
|
||||
Entity: `sensors.sensor_class = "state" AND sensors.sensor_current != "6" AND sensors.sensor_type = "jnxFruState" AND sensors.sensor_current != "2"`
|
||||
|
||||
Description: Device component is down such as Fan, PSU, etc for JunOS devices.
|
||||
|
||||
Example: `%macros.device_component_down_junos = "1"`
|
||||
Example: `macros.device_component_down_junos = 1`
|
||||
|
||||
### Device component down [Cisco]
|
||||
|
||||
Entity: `%sensors.sensor_current != "1" && %sensors.sensor_current != "5" && %sensors.sensor_type ~ "^cisco.*State$"`
|
||||
Entity: `sensors.sensor_current != 1 AND sensors.sensor_current != 5 AND sensors.sensor_type ~ "^cisco.*State$"`
|
||||
|
||||
Description: Device component is down such as Fan, PSU, etc for Cisco devices.
|
||||
|
||||
Example: `%macros.device_component_down_cisco = "1"`
|
||||
Example: `macros.device_component_down_cisco = 1`
|
||||
|
||||
### PDU over amperage [APC]
|
||||
|
||||
Entity: `%sensors.sensor_class = "current" && %sensors.sensor_descr = "Bank Total" && %sensors.sensor_current > %sensors.sensor_limit && %devices.os = "apc"`
|
||||
Entity: `sensors.sensor_class = "current" AND sensors.sensor_descr = "Bank Total" AND sensors.sensor_current > sensors.sensor_limit AND devices.os = "apc"`
|
||||
|
||||
Description: APC PDU over amperage
|
||||
|
||||
Example: `%macros.pdu_over_amperage_apc = "1"`
|
||||
Example: `macros.pdu_over_amperage_apc = 1`
|
||||
|
@ -1,20 +0,0 @@
|
||||
source: Alerting/Rule-Mapping.md
|
||||
|
||||
# Alert Rule Mapping
|
||||
Alert Rule Mapping can be found in the WebUI. In the Nav bar click on Alerts then Rule Mapping.
|
||||
In LibreNMS you can create alert rules and map those alert rules to devices or you can create device groups and map alert rules to those groups.
|
||||
This could be useful for alerts rules that you don't want to be checked against devices that may not match with your alert rule or may give you false positive etc.
|
||||
|
||||
Example: Alert Rule Mapping
|
||||

|
||||
|
||||
|
||||
In this example we have an alert rule that checks HPE iLo Power Supply Failure. You probably don't want this alert rule being checked on none HPE iLo devices. So in the picture below you can see the Alert rule is mapped to a Device group that was created just for HPE iLo devices.
|
||||
|
||||
Example: HPE iLo Rule map
|
||||

|
||||
|
||||
|
||||
### You have two options when mapping the alert rule.
|
||||
* First option: you can map alert rule to one device.
|
||||
* Second option: you create a device group and group all your devices together. [Link to Device Groups](../Extensions/Device-Groups.md)
|
@ -2,9 +2,10 @@ source: Alerting/Rules.md
|
||||
|
||||
# Rules
|
||||
Rules are defined using a logical language.
|
||||
The GUI provides a simple way of creating basic rules.
|
||||
Creating more complicated rules which may include maths calculations and MySQL queries can be
|
||||
done using [macros](Macros.md)
|
||||
|
||||
The GUI provides a simple way of creating rules.
|
||||
|
||||
Creating more complicated rules which may include maths calculations and MySQL queries can be done using [macros](Macros.md)
|
||||
|
||||
#### Video on how the alert rules work in LibreNMS
|
||||
[Alert Rules](https://youtu.be/ryv0j8GEkhM)
|
||||
@ -14,25 +15,35 @@ done using [macros](Macros.md)
|
||||
## Syntax
|
||||
Rules must consist of at least 3 elements: An __Entity__, a __Condition__ and a __Value__.
|
||||
Rules can contain braces and __Glues__.
|
||||
__Entities__ are provided from Table and Field from the database. For Example: `%ports.ifOperStatus`.
|
||||
> Please note that % is not required when adding alert rules via the WebUI.
|
||||
__Entities__ are provided from Table and Field from the database. For Example: `ports.ifOperStatus`.
|
||||
|
||||
__Conditions__ can be any of:
|
||||
|
||||
- Equals `=`
|
||||
- Not Equals `!=`
|
||||
- Like `~`
|
||||
- Not Like `!~`
|
||||
- In `IN`
|
||||
- Not In `NOT IN`
|
||||
- Begins with `LIKE ('%...')`
|
||||
- Doesn't begin with `NOT LIKE ('%...')`
|
||||
- Contains `LIKE ('%...%')`
|
||||
- Doesn't Contain `NOT LIKE ('%...%')`
|
||||
- Ends with `LIKE ('...%')`
|
||||
- Doesn't end with `NOT LIKE ('...%')`
|
||||
- Between `BETWEEN`
|
||||
- Not Between `NOT BETWEEN`
|
||||
- Is Empty `= ''`
|
||||
- Is Not Empty `!= '''`
|
||||
- Is Null `IS NULL`
|
||||
- Is Not Null `IS NOT NULL`
|
||||
- Greater `>`
|
||||
- Greater or Equal `>=`
|
||||
- Smaller `<`
|
||||
- Smaller or Equal `<=`
|
||||
- Less `<`
|
||||
- Less or Equal `<=`
|
||||
- Regex `REGEXP`
|
||||
|
||||
__Values__ can be an entity or any single-quoted data.
|
||||
__Glues__ can be `&&` for `AND`.
|
||||
__Values__ can be an entity or any data.
|
||||
|
||||
**Note** if you need to use `OR` `||` please use a [macros](Macros.md)
|
||||
|
||||
__Note__: The difference between `Equals` and `Like` (and its negation) is that `Equals` does a strict comparison and `Like` allows the usage of MySQL RegExp.
|
||||
__Note__: Regex supports MySQL Regular expressions
|
||||
|
||||
Arithmetics are allowed as well.
|
||||
|
||||
@ -55,17 +66,17 @@ You can associate a rule to a procedure by giving the URL of the procedure when
|
||||
|
||||
Alert when:
|
||||
|
||||
- Device goes down: `%devices.status != '1'`
|
||||
- Any port changes: `%ports.ifOperStatus != 'up'`
|
||||
- Root-directory gets too full: `%storage.storage_descr = '/' && %storage.storage_perc >= '75'`
|
||||
- Any storage gets fuller than the 'warning': `%storage.storage_perc >= %storage_perc_warn`
|
||||
- If device is a server and the used storage is above the warning level, but ignore /boot partitions: `%storage.storage_perc > %storage.storage_perc_warn && %devices.type = "server" && %storage.storage_descr !~ "/boot"`
|
||||
- VMware LAG is not using "Source ip address hash" load balancing: `%devices.os = "vmware" && %ports.ifType = "ieee8023adLag" && %ports.ifDescr !~ "Link Aggregation @, load balancing algorithm: Source ip address hash"`
|
||||
- Syslog, authentication failure during the last 5m: `%syslog.timestamp >= %macros.past_5m && %syslog.msg ~ "@authentication failure@"`
|
||||
- High memory usage: `%macros.device_up = "1" && %mempools.mempool_perc >= "90" && %mempools.mempool_descr = "Virtual@"`
|
||||
- High CPU usage(per core usage, not overall): `%macros.device_up = "1" && %processors.processor_usage >= "90"`
|
||||
- High port usage, where description is not client & ifType is not softwareLoopback: `%macros.port_usage_perc >= "80" && %port.port_descr_type != "client" && %ports.ifType != "softwareLoopback"`
|
||||
- Alert when mac address is located on your network `%ipv4_mac.mac_address = "2c233a756912"`
|
||||
- Device goes down: `devices.status = 1`
|
||||
- Any port changes: `ports.ifOperStatus != 'up'`
|
||||
- Root-directory gets too full: `storage.storage_descr = '/' AND storage.storage_perc >= '75'`
|
||||
- Any storage gets fuller than the 'warning': `storage.storage_perc >= storage_perc_warn`
|
||||
- If device is a server and the used storage is above the warning level, but ignore /boot partitions: `storage.storage_perc > storage.storage_perc_warn AND devices.type = "server" AND storage.storage_descr != "/boot"`
|
||||
- VMware LAG is not using "Source ip address hash" load balancing: `devices.os = "vmware" AND ports.ifType = "ieee8023adLag" AND ports.ifDescr REGEXP "Link Aggregation .*, load balancing algorithm: Source ip address hash"`
|
||||
- Syslog, authentication failure during the last 5m: `syslog.timestamp >= macros.past_5m AND syslog.msg REGEXP ".*authentication failure.*"`
|
||||
- High memory usage: `macros.device_up = 1 AND mempools.mempool_perc >= 90 AND mempools.mempool_descr REGEXP "Virtual.*"`
|
||||
- High CPU usage(per core usage, not overall): `macros.device_up = 1 AND processors.processor_usage >= 90`
|
||||
- High port usage, where description is not client & ifType is not softwareLoopback: `macros.port_usage_perc >= 80 AND port.port_descr_type != "client" AND ports.ifType != "softwareLoopback"`
|
||||
- Alert when mac address is located on your network `ipv4_mac.mac_address = "2c233a756912"`
|
||||
|
||||
### Alert Rules Collection
|
||||
You can also select Alert Rule from the Alerts Collection. These Alert Rules are submitted by users in the community :)
|
||||
|
@ -40,7 +40,10 @@ We list below what we make use of including the license compliance.
|
||||
- [Graylog SSO Authentication Plugin](https://github.com/Graylog2/graylog-plugin-auth-sso)
|
||||
- [Select2](https://select2.org): MIT License
|
||||
- [JustGage](http://justgage.com): MIT
|
||||
|
||||
- [jQuery.extendext](https://github.com/mistic100/jQuery.extendext): MIT
|
||||
- [doT](https://github.com/olado/doT): MIT
|
||||
- [jQuery-queryBuilder](https://github.com/mistic100/jQuery-QueryBuilder/): MIT
|
||||
- [sql-parser](https://github.com/mistic100/sql-parser/): MIT (Currently a custom build is used)
|
||||
|
||||
## 3rd Party GPLv3 Non-compliant
|
||||
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 18 KiB |
Binary file not shown.
Before Width: | Height: | Size: 3.3 KiB |
@ -23,11 +23,15 @@ if (!$_SESSION['authenticated']) {
|
||||
|
||||
set_debug($_REQUEST['debug']);
|
||||
|
||||
$id = mres($_REQUEST['id']);
|
||||
$type = mres($_REQUEST['type']);
|
||||
|
||||
if (isset($id)) {
|
||||
if (file_exists("includes/list/$id.inc.php")) {
|
||||
if (isset($type) && file_exists("includes/list/$type.inc.php")) {
|
||||
header('Content-type: application/json');
|
||||
include_once "includes/list/$id.inc.php";
|
||||
}
|
||||
|
||||
list($results, $more) = include "includes/list/$type.inc.php";
|
||||
|
||||
die(json_encode([
|
||||
'results' => $results,
|
||||
'pagination' => ['more' => $more]
|
||||
]));
|
||||
}
|
||||
|
6
html/css/query-builder.default.min.css
vendored
Normal file
6
html/css/query-builder.default.min.css
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
/*!
|
||||
* jQuery QueryBuilder 2.4.5
|
||||
* Copyright 2014-2017 Damien "Mistic" Sorel (http://www.strangeplanet.fr)
|
||||
* Licensed under MIT (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
.query-builder .rule-container,.query-builder .rule-placeholder,.query-builder .rules-group-container{position:relative;margin:4px 0;border-radius:5px;padding:5px;border:1px solid #EEE;background:rgba(255,255,255,.9)}.query-builder .drag-handle,.query-builder .error-container,.query-builder .rule-container .rule-filter-container,.query-builder .rule-container .rule-operator-container,.query-builder .rule-container .rule-value-container{display:inline-block;margin:0 5px 0 0;vertical-align:middle}.query-builder .rules-group-container{padding:10px 10px 6px;border:1px solid #DCC896;background:rgba(250,240,210,.5)}.query-builder .rules-group-header{margin-bottom:10px}.query-builder .rules-group-header .group-conditions .btn.readonly:not(.active),.query-builder .rules-group-header .group-conditions input[name$='_cond']{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;white-space:nowrap}.query-builder .rules-group-header .group-conditions .btn.readonly{border-radius:3px}.query-builder .rules-list{list-style:none;padding:0 0 0 15px;margin:0}.query-builder .rule-value-container{border-left:1px solid #DDD;padding-left:5px}.query-builder .rule-value-container label{margin-bottom:0;font-weight:400}.query-builder .rule-value-container label.block{display:block}.query-builder .rule-value-container input[type=text],.query-builder .rule-value-container input[type=number],.query-builder .rule-value-container select{padding:1px}.query-builder .error-container{display:none;cursor:help;color:red}.query-builder .has-error{background-color:#FDD;border-color:#F99}.query-builder .has-error .error-container{display:inline-block!important}.query-builder .dragging::after,.query-builder .dragging::before,.query-builder .rules-list>:last-child::after{display:none}.query-builder .rules-list>::after,.query-builder .rules-list>::before{content:'';position:absolute;left:-10px;width:10px;height:calc(50% + 4px);border-color:#CCC;border-style:solid}.query-builder .rules-list>::before{top:-4px;border-width:0 0 2px 2px}.query-builder .rules-list>::after{top:50%;border-width:0 0 0 2px}.query-builder .rules-list>:first-child::before{top:-12px;height:calc(50% + 14px)}.query-builder .rules-list>:last-child::before{border-radius:0 0 0 4px}.query-builder.bt-checkbox-glyphicons .checkbox input[type=checkbox]:checked+label::after{font-family:'Glyphicons Halflings';content:'\e013'}.query-builder.bt-checkbox-glyphicons .checkbox label::after{padding-left:4px;padding-top:2px;font-size:9px}.query-builder .error-container+.tooltip .tooltip-inner{color:#F99!important}.query-builder p.filter-description{margin:5px 0 0;background:#D9EDF7;border:1px solid #BCE8F1;color:#31708F;border-radius:5px;padding:2.5px 5px;font-size:.8em}.query-builder .rules-group-header [data-invert]{margin-left:5px}.query-builder .drag-handle{cursor:move;vertical-align:middle;margin-left:5px}.query-builder .dragging{position:fixed;opacity:.5;z-index:100}.query-builder .rule-placeholder{border:1px dashed #BBB;opacity:.7}
|
@ -2194,3 +2194,8 @@ label {
|
||||
margin-right: 8px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
/* Workaround for https://github.com/select2/select2/issues/291 */
|
||||
.select2-selection--multiple .select2-search--inline .select2-search__field {
|
||||
width: auto !important;
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ if (!is_numeric($alert_id)) {
|
||||
exit;
|
||||
} else {
|
||||
if ($state == 2) {
|
||||
$state = dbFetchCell('SELECT alerted FROM alerts WHERE id = ?', array($alert_id));
|
||||
$state = 1;
|
||||
$open = 1;
|
||||
} elseif ($state >= 1) {
|
||||
$state = 2;
|
||||
|
148
html/includes/forms/alert-rules.inc.php
Normal file
148
html/includes/forms/alert-rules.inc.php
Normal file
@ -0,0 +1,148 @@
|
||||
<?php
|
||||
/**
|
||||
* alert-rules.inc.php
|
||||
*
|
||||
* LibreNMS alert-rules.inc.php for processor
|
||||
*
|
||||
* 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 2018 Neil Lathwood
|
||||
* @author Neil Lathwood <gh+n@laf.io>
|
||||
*/
|
||||
|
||||
use LibreNMS\Alerting\QueryBuilderParser;
|
||||
|
||||
header('Content-type: application/json');
|
||||
|
||||
if (is_admin() === false) {
|
||||
die(json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'ERROR: You need to be admin',
|
||||
]));
|
||||
}
|
||||
|
||||
$status = 'ok';
|
||||
$message = '';
|
||||
|
||||
$builder_json = $_POST['builder_json'];
|
||||
$query = QueryBuilderParser::fromJson($builder_json)->toSql();
|
||||
$rule_id = $_POST['rule_id'];
|
||||
$count = mres($_POST['count']);
|
||||
$delay = mres($_POST['delay']);
|
||||
$interval = mres($_POST['interval']);
|
||||
$mute = mres($_POST['mute']);
|
||||
$invert = mres($_POST['invert']);
|
||||
$name = mres($_POST['name']);
|
||||
$proc = mres($_POST['proc']);
|
||||
$severity = mres($_POST['severity']);
|
||||
|
||||
if (!is_numeric($count)) {
|
||||
$count = '-1';
|
||||
}
|
||||
|
||||
$delay_sec = convert_delay($delay);
|
||||
$interval_sec = convert_delay($interval);
|
||||
|
||||
if ($mute == 'on') {
|
||||
$mute = true;
|
||||
} else {
|
||||
$mute = false;
|
||||
}
|
||||
|
||||
if ($invert == 'on') {
|
||||
$invert = true;
|
||||
} else {
|
||||
$invert = false;
|
||||
}
|
||||
|
||||
$extra = array(
|
||||
'mute' => $mute,
|
||||
'count' => $count,
|
||||
'delay' => $delay_sec,
|
||||
'invert' => $invert,
|
||||
'interval' => $interval_sec,
|
||||
);
|
||||
|
||||
$extra_json = json_encode($extra);
|
||||
|
||||
if (is_numeric($rule_id) && $rule_id > 0) {
|
||||
if (dbUpdate(
|
||||
array(
|
||||
'severity' => $severity,
|
||||
'extra' => $extra_json,
|
||||
'name' => $name,
|
||||
'proc' => $proc,
|
||||
'query' => $query,
|
||||
'builder' => $builder_json
|
||||
),
|
||||
'alert_rules',
|
||||
'id=?',
|
||||
array($rule_id)
|
||||
) > 0) {
|
||||
$message = "Edited Rule: <i>$name</i>";
|
||||
} else {
|
||||
$message = "Failed to edit Rule <i>$name</i>";
|
||||
$status = 'error';
|
||||
}
|
||||
} else {
|
||||
if (empty($name)) {
|
||||
$status = 'error';
|
||||
$message = 'No rule name provided';
|
||||
} elseif (empty($builder_json) || empty($query)) {
|
||||
$status = 'error';
|
||||
$message = 'No rules provided';
|
||||
} else {
|
||||
$rule_id = dbInsert(array(
|
||||
'rule' => '',
|
||||
'severity' => $severity,
|
||||
'extra' => $extra_json,
|
||||
'disabled' => 0,
|
||||
'name' => $name,
|
||||
'proc' => $proc,
|
||||
'query' => $query,
|
||||
'builder' => $builder_json
|
||||
), 'alert_rules');
|
||||
|
||||
if ($rule_id) {
|
||||
$message = "Added Rule: <i>$name</i>";
|
||||
} else {
|
||||
$message = 'Failed to add Rule: <i>' . $name . '</i>';
|
||||
$status = 'error';
|
||||
}
|
||||
}
|
||||
}//end if
|
||||
|
||||
// update maps
|
||||
if (is_numeric($rule_id) && $rule_id > 0) {
|
||||
$devices = [];
|
||||
$groups = [];
|
||||
foreach ((array)$_POST['maps'] as $item) {
|
||||
if (starts_with($item, 'g')) {
|
||||
$groups[] = (int)substr($item, 1);
|
||||
} else {
|
||||
$devices[] = (int)$item;
|
||||
}
|
||||
}
|
||||
|
||||
dbSyncRelationship('alert_device_map', 'rule_id', $rule_id, 'device_id', $devices);
|
||||
dbSyncRelationship('alert_group_map', 'rule_id', $rule_id, 'group_id', $groups);
|
||||
}
|
||||
|
||||
|
||||
die(json_encode([
|
||||
'status' => $status,
|
||||
'message' => $message,
|
||||
]));
|
@ -1,68 +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.
|
||||
*/
|
||||
|
||||
if (is_admin() === false) {
|
||||
die('ERROR: You need to be admin');
|
||||
}
|
||||
|
||||
$rule = mres($_POST['rule']);
|
||||
$target = mres($_POST['target']);
|
||||
$map_id = mres($_POST['map_id']);
|
||||
$ret = array();
|
||||
|
||||
if (empty($rule) || empty($target)) {
|
||||
$ret[] = 'ERROR: No map was generated';
|
||||
} else {
|
||||
$raw = $rule;
|
||||
$rule = dbFetchCell('SELECT id FROM alert_rules WHERE name = ?', array($rule));
|
||||
if (!is_numeric($rule)) {
|
||||
array_unshift($ret, "ERROR: Could not find rule for '".$raw."'");
|
||||
} else {
|
||||
$raw = $target;
|
||||
if ($target[0].$target[1] == 'g:') {
|
||||
$target = 'g'.dbFetchCell('SELECT id FROM device_groups WHERE name = ?', array(substr($target, 2)));
|
||||
} else {
|
||||
$target = dbFetchCell('SELECT device_id FROM devices WHERE hostname = ?', array($target));
|
||||
}
|
||||
|
||||
if (!is_numeric(str_replace('g', '', $target))) {
|
||||
array_unshift($ret, "ERROR: Could not find entry for '".$raw."'");
|
||||
} else {
|
||||
if (is_numeric($map_id) && $map_id > 0) {
|
||||
if (dbUpdate(array('rule' => $rule, 'target' => $target), 'alert_map', 'id=?', array($map_id)) >= 0) {
|
||||
$ret[] = 'Edited Map: <i>'.$map_id.': '.$rule.' = '.$target.'</i>';
|
||||
} else {
|
||||
array_unshift($ret, 'ERROR: Failed to edit Map: <i>'.$map_id.': '.$rule.' = '.$target.'</i>');
|
||||
}
|
||||
} else {
|
||||
if (dbInsert(array('rule' => $rule, 'target' => $target), 'alert_map')) {
|
||||
$ret[] = 'Added Map: <i>'.$rule.' = '.$target.'</i>';
|
||||
} else {
|
||||
array_unshift($ret, 'ERROR: Failed to add Map: <i>'.$rule.' = '.$target.'</i>');
|
||||
}
|
||||
}
|
||||
|
||||
if (($tmp = dbFetchCell('SELECT device_id FROM alert_rules WHERE id = ?', array($rule))) && $tmp[0] != ':') {
|
||||
if (dbUpdate(array('device_id' => ':'.$tmp), 'alert_rules', 'id=?', array($rule)) >= 0) {
|
||||
$ret[] = 'Edited Rule: <i>'.$rule." device_id = ':".$tmp."'</i>";
|
||||
} else {
|
||||
array_unshift($ret, 'ERROR: Failed to edit Rule: <i>'.$rule.": device_id = ':".$tmp."'</i>");
|
||||
}
|
||||
}
|
||||
}//end if
|
||||
}//end if
|
||||
}//end if
|
||||
foreach ($ret as $msg) {
|
||||
echo $msg.'<br/>';
|
||||
}
|
@ -1,44 +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.
|
||||
*/
|
||||
|
||||
if (is_admin() === false) {
|
||||
die('ERROR: You need to be admin');
|
||||
}
|
||||
|
||||
$ret = array();
|
||||
$brk = false;
|
||||
if (!is_numeric($_POST['map_id'])) {
|
||||
array_unshift($ret, 'ERROR: No map selected');
|
||||
} else {
|
||||
if (dbFetchCell('SELECT COUNT(B.id) FROM alert_map,alert_map AS B WHERE alert_map.rule=B.rule && alert_map.id = ?', array($_POST['map_id'])) <= 1) {
|
||||
$rule = dbFetchRow('SELECT alert_rules.id,alert_rules.device_id FROM alert_map,alert_rules WHERE alert_map.rule=alert_rules.id && alert_map.id = ?', array($_POST['map_id']));
|
||||
$rule['device_id'] = str_replace(':', '', $rule['device_id']);
|
||||
if (dbUpdate(array('device_id' => $rule['device_id']), 'alert_rules', 'id = ?', array($rule['id'])) >= 0) {
|
||||
$ret[] = 'Restored Rule: <i>'.$rule['id'].": device_id = '".$rule['device_id']."'</i>";
|
||||
} else {
|
||||
array_unshift($ret, 'ERROR: Rule '.$rule['id'].' has not been restored.');
|
||||
$brk = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($brk === false && dbDelete('alert_map', '`id` = ?', array($_POST['map_id']))) {
|
||||
$ret[] = 'Map has been deleted.';
|
||||
} else {
|
||||
array_unshift($ret, 'ERROR: Map has not been deleted.');
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($ret as $msg) {
|
||||
echo $msg.'<br/>';
|
||||
}
|
@ -22,11 +22,8 @@ if (!is_numeric($_POST['alert_id'])) {
|
||||
exit;
|
||||
} else {
|
||||
if (dbDelete('alert_rules', '`id` = ?', array($_POST['alert_id']))) {
|
||||
if (dbDelete('alert_map', 'rule = ?', array($_POST['alert_id'])) || dbFetchCell('SELECT COUNT(*) FROM alert_map WHERE rule = ?', array($_POST['alert_id'])) == 0) {
|
||||
echo 'Maps has been deleted.';
|
||||
} else {
|
||||
echo 'WARNING: Maps could not be deleted.';
|
||||
}
|
||||
dbDelete('alert_device_map', 'rule_id=?', [$_POST['alert_id']]);
|
||||
dbDelete('alert_group_map', 'rule_id=?', [$_POST['alert_id']]);
|
||||
|
||||
echo 'Alert rule has been deleted.';
|
||||
exit;
|
||||
|
@ -22,12 +22,7 @@ if (!is_numeric($_POST['group_id'])) {
|
||||
exit;
|
||||
} else {
|
||||
if (dbDelete('device_groups', '`id` = ?', array($_POST['group_id']))) {
|
||||
if (dbFetchCell('SELECT COUNT(id) FROM alert_map WHERE target = ?', array('g'.$_POST['group_id'])) >= 1) {
|
||||
foreach (dbFetchRows('SELECT id FROM alert_map WHERE target = ?', array('g'.$_POST['group_id'])) as $map) {
|
||||
$_POST['map_id'] = $map['id'];
|
||||
include 'forms/delete-alert-map.inc.php';
|
||||
}
|
||||
}
|
||||
dbDelete('alert_group_map', 'group_id=?', [$_POST['group_id']]);
|
||||
|
||||
echo 'Group has been deleted.';
|
||||
exit;
|
||||
|
@ -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.
|
||||
*/
|
||||
|
||||
if (is_admin() === false) {
|
||||
header('Content-type: text/plain');
|
||||
die('ERROR: You need to be admin');
|
||||
}
|
||||
|
||||
$map_id = $_POST['map_id'];
|
||||
|
||||
if (is_numeric($map_id) && $map_id > 0) {
|
||||
$map = dbFetchRow('SELECT alert_rules.name,alert_map.target FROM alert_map,alert_rules WHERE alert_map.rule=alert_rules.id && alert_map.id = ?', array($map_id));
|
||||
if ($map['target'][0] == 'g') {
|
||||
$map['target'] = 'g:'.dbFetchCell('SELECT name FROM device_groups WHERE id = ?', array(substr($map['target'], 1)));
|
||||
} else {
|
||||
$map['target'] = dbFetchCell('SELECT hostname FROM devices WHERE device_id = ?', array($map['target']));
|
||||
}
|
||||
|
||||
$output = array(
|
||||
'rule' => $map['name'],
|
||||
'target' => $map['target'],
|
||||
);
|
||||
header('Content-type: application/json');
|
||||
echo _json_encode($output);
|
||||
}
|
@ -12,6 +12,8 @@
|
||||
* the source code distribution for details.
|
||||
*/
|
||||
|
||||
use LibreNMS\Alerting\QueryBuilderParser;
|
||||
|
||||
if (is_admin() === false) {
|
||||
header('Content-type: text/plain');
|
||||
die('ERROR: You need to be admin');
|
||||
@ -21,22 +23,40 @@ $alert_id = $_POST['alert_id'];
|
||||
$template_id = $_POST['template_id'];
|
||||
|
||||
if (is_numeric($alert_id) && $alert_id > 0) {
|
||||
$rule = dbFetchRow('SELECT * FROM `alert_rules` WHERE `id` = ? LIMIT 1', array($alert_id));
|
||||
$rule = dbFetchRow('SELECT * FROM `alert_rules` WHERE `id` = ? LIMIT 1', [$alert_id]);
|
||||
|
||||
$maps = [];
|
||||
|
||||
$devices = dbFetchRows('SELECT `device_id`, `hostname`, `sysName` FROM `alert_device_map` LEFT JOIN `devices` USING (`device_id`) WHERE `rule_id`=?', [$alert_id]);
|
||||
foreach ($devices as $device) {
|
||||
$maps[] = ['id' => $device['device_id'], 'text' => format_hostname($device)];
|
||||
}
|
||||
|
||||
$groups = dbFetchRows('SELECT `group_id`, `name` FROM `alert_group_map` LEFT JOIN `device_groups` ON `device_groups`.`id`=`alert_group_map`.`group_id` WHERE `rule_id`=?', [$alert_id]);
|
||||
foreach ($groups as $group) {
|
||||
$maps[] = ['id' => 'g' . $group['group_id'], 'text' => $group['name']];
|
||||
}
|
||||
} elseif (is_numeric($template_id) && $template_id >= 0) {
|
||||
$tmp_rules = get_rules_from_json();
|
||||
$rule = $tmp_rules[$template_id];
|
||||
$maps = [];
|
||||
}
|
||||
|
||||
if (is_array($rule)) {
|
||||
$rule_split = preg_split('/([a-zA-Z0-9_\-\.\=\%\<\>\ \"\'\!\~\(\)\*\/\@\|]+[&&|\|\|]{2})/', $rule['rule'], -1, (PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY));
|
||||
$count = (count($rule_split) - 1);
|
||||
$rule_split[$count] = $rule_split[$count].' &&';
|
||||
$output = array(
|
||||
'severity' => $rule['severity'],
|
||||
'extra' => $rule['extra'],
|
||||
if (empty($rule['builder'])) {
|
||||
// convert old rules when editing
|
||||
$builder = QueryBuilderParser::fromOld($rule['rule'])->toArray();
|
||||
} else {
|
||||
$builder = json_decode($rule['builder']);
|
||||
}
|
||||
|
||||
header('Content-type: application/json');
|
||||
echo json_encode([
|
||||
'extra' => isset($rule['extra']) ? json_decode($rule['extra']) : null,
|
||||
'maps' => $maps,
|
||||
'name' => $rule['name'],
|
||||
'proc' => $rule['proc'],
|
||||
'rules' => $rule_split,
|
||||
);
|
||||
header('Content-type: application/json');
|
||||
echo _json_encode($output);
|
||||
'builder' => $builder,
|
||||
'severity' => $rule['severity'],
|
||||
]);
|
||||
}
|
||||
|
55
html/includes/forms/sql-from-alert-collection.inc.php
Normal file
55
html/includes/forms/sql-from-alert-collection.inc.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/**
|
||||
* alert-rules.inc.php
|
||||
*
|
||||
* LibreNMS alert-rules.inc.php for processor
|
||||
*
|
||||
* 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 2018 Neil Lathwood
|
||||
* @author Neil Lathwood <gh+n@laf.io>
|
||||
*/
|
||||
|
||||
use LibreNMS\Alerting\QueryBuilderParser;
|
||||
|
||||
header('Content-type: application/json');
|
||||
|
||||
if (is_admin() === false) {
|
||||
die(json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'ERROR: You need to be admin',
|
||||
]));
|
||||
}
|
||||
|
||||
$template_id = $vars['template_id'];
|
||||
|
||||
if (is_numeric($template_id)) {
|
||||
$rules = get_rules_from_json();
|
||||
|
||||
$output = [
|
||||
'status' => 'ok',
|
||||
'name' => $rules[$template_id]['name'],
|
||||
'builder' => QueryBuilderParser::fromOld($rules[$template_id]['rule'])->toArray(),
|
||||
'extra' => $rules[$template_id]['extra']
|
||||
];
|
||||
} else {
|
||||
$output = [
|
||||
'status' => 'error',
|
||||
'message' => 'Invalid template'
|
||||
];
|
||||
}
|
||||
|
||||
die(json_encode($output));
|
@ -1644,30 +1644,6 @@ function get_dashboards($user_id = null)
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate javascript to fill in a select box from an ajax list
|
||||
*
|
||||
* @param string $list_type type of list look in html/includes/list/
|
||||
* @param string $selector jquery selector for the target select element
|
||||
* @param int $selected the id of the item to mark as selected
|
||||
* @return string the javascript (not including <script> tags)
|
||||
*/
|
||||
function generate_fill_select_js($list_type, $selector, $selected = null)
|
||||
{
|
||||
return '$(document).ready(function() {
|
||||
$select = $("' . $selector . '")
|
||||
$.getJSON(\'ajax_list.php?id=' . $list_type . '\', function(data){
|
||||
$.each(data, function(index,item) {
|
||||
if (item.id == "' . $selected . '") {
|
||||
$select.append("<option value=" + item.id + " selected>" + item.value + "</option>");
|
||||
} else {
|
||||
$select.append("<option value=" + item.id + ">" + item.value + "</option>");
|
||||
}
|
||||
});
|
||||
});
|
||||
});';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return stacked graphs information
|
||||
*
|
||||
|
71
html/includes/list/devices.inc.php
Normal file
71
html/includes/list/devices.inc.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
/**
|
||||
* devices.inc.php
|
||||
*
|
||||
* List devices
|
||||
*
|
||||
* 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 2018 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
$query = '';
|
||||
$where = [];
|
||||
$params = [];
|
||||
|
||||
if (!is_admin() && !is_read()) {
|
||||
$query .= ' LEFT JOIN `devices_perms` USING (`device_id`)';
|
||||
$where = '`devices_perms`.`user_id`=?';
|
||||
$params[] = $_SESSION['user_id'];
|
||||
}
|
||||
|
||||
if (!empty($_REQUEST['search'])) {
|
||||
$where[] = '(`hostname` LIKE ? OR `sysName` LIKE ?)';
|
||||
$search = '%' . mres($_REQUEST['search']) . '%';
|
||||
$params[] = $search;
|
||||
$params[] = $search;
|
||||
}
|
||||
|
||||
if (!empty($where)) {
|
||||
$query .= ' WHERE ';
|
||||
$query .= implode(' AND ', $where);
|
||||
}
|
||||
|
||||
$total = dbFetchCell("SELECT COUNT(*) FROM `devices` $query", $params);
|
||||
$more = false;
|
||||
|
||||
if (!empty($_REQUEST['limit'])) {
|
||||
$limit = (int) $_REQUEST['limit'];
|
||||
$page = isset($_REQUEST['page']) ? (int) $_REQUEST['page'] : 1;
|
||||
$offset = ($page - 1) * $limit;
|
||||
|
||||
$query .= " LIMIT $offset, $limit";
|
||||
} else {
|
||||
$offset = 0;
|
||||
}
|
||||
|
||||
$sql = "SELECT `device_id`, `hostname`, `sysName` FROM `devices` $query";
|
||||
$devices = array_map(function ($device) {
|
||||
return [
|
||||
'id' => $device['device_id'],
|
||||
'text' => format_hostname($device),
|
||||
];
|
||||
}, dbFetchRows($sql, $params));
|
||||
|
||||
$more = ($offset + count($devices)) < $total;
|
||||
|
||||
return [$devices, $more];
|
@ -1,8 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* hostnames.inc.php
|
||||
* devices_groups.inc.php
|
||||
*
|
||||
* -Description-
|
||||
* List devices and groups in one
|
||||
*
|
||||
* 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
|
||||
@ -19,16 +19,21 @@
|
||||
*
|
||||
* @package LibreNMS
|
||||
* @link http://librenms.org
|
||||
* @copyright 2017 Tony Murray
|
||||
* @copyright 2018 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
if (is_admin() || is_read()) {
|
||||
echo json_encode(dbFetchRows('SELECT `device_id` AS id, `hostname` AS value FROM `devices`'));
|
||||
} else {
|
||||
$sql = 'SELECT `devices`.`device_id` AS id, `hostname` AS value FROM `devices`
|
||||
LEFT JOIN `devices_perms` ON `devices`.`device_id` = `devices_perms`.`device_id`
|
||||
WHERE `devices_perms`.`user_id` = ?';
|
||||
list($devices, $d_more) = include 'devices.inc.php';
|
||||
list($groups, $g_more) = include 'groups.inc.php';
|
||||
|
||||
echo json_encode(dbFetchRows($sql, $_SESSION['user_id']));
|
||||
}
|
||||
$groups = array_map(function ($group) {
|
||||
$group['id'] = 'g' . $group['id'];
|
||||
return $group;
|
||||
}, $groups);
|
||||
|
||||
$data = [
|
||||
['text' => 'Devices', 'children' => $devices],
|
||||
['text' => 'Groups', 'children' => $groups]
|
||||
];
|
||||
|
||||
return [$data, $d_more || $g_more];
|
58
html/includes/list/groups.inc.php
Normal file
58
html/includes/list/groups.inc.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/**
|
||||
* groups.inc.php
|
||||
*
|
||||
* List groups
|
||||
*
|
||||
* 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 2018 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
if (!is_admin() && !is_read()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$query = '';
|
||||
$params = [];
|
||||
|
||||
if (!empty($_REQUEST['search'])) {
|
||||
$query .= ' WHERE `name` LIKE ?';
|
||||
$params[] = '%' . mres($_REQUEST['search']) . '%';
|
||||
}
|
||||
|
||||
|
||||
$total = dbFetchCell("SELECT COUNT(*) FROM `device_groups` $query", $params);
|
||||
$more = false;
|
||||
|
||||
if (!empty($_REQUEST['limit'])) {
|
||||
$limit = (int) $_REQUEST['limit'];
|
||||
$page = isset($_REQUEST['page']) ? (int) $_REQUEST['page'] : 1;
|
||||
$offset = ($page - 1) * $limit;
|
||||
|
||||
$query .= " LIMIT $offset, $limit";
|
||||
} else {
|
||||
$offset = 0;
|
||||
}
|
||||
|
||||
|
||||
$sql = "SELECT `id`, `name` AS `text` FROM `device_groups` $query";
|
||||
$groups = dbFetchRows($sql, $params);
|
||||
|
||||
$more = ($offset + count($groups)) < $total;
|
||||
|
||||
return [$groups, $more];
|
@ -30,7 +30,7 @@ if (is_admin() === false) {
|
||||
?>
|
||||
|
||||
<div class="modal fade" id="search_rule_modal" tabindex="-1" role="dialog" aria-labelledby="search_rule" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
@ -68,15 +68,32 @@ if (is_admin() === false) {
|
||||
"action": function (column, row) {
|
||||
return "<button type=\"button\" id=\"rule_from_collection\" name=\"rule_from_collection\" data-rule_id=\"" + row.action + "\" class=\"btn btn-sm btn-primary rule_from_collection\">Select</button";
|
||||
}
|
||||
},
|
||||
templates: {
|
||||
footer: "<div id=\"{{ctx.id}}\" class=\"{{css.footer}}\"><div class=\"row\"><div class=\"col-sm-12\"><p class=\"{{css.pagination}}\"></p></div></div></div>"
|
||||
}
|
||||
}).on("loaded.rs.jquery.bootgrid", function()
|
||||
{
|
||||
grid.find(".rule_from_collection").on("click", function(e)
|
||||
{
|
||||
grid.find(".rule_from_collection").on("click", function(e) {
|
||||
var template_rule_id = $(this).data("rule_id");
|
||||
$("#template_id").val(template_rule_id);
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "ajax_form.php",
|
||||
data: {type: 'sql-from-alert-collection', template_id: template_rule_id},
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
if (data.status == 'ok') {
|
||||
$("#search_rule_modal").modal('hide');
|
||||
$("#create-alert").modal('show');
|
||||
loadRule(data);
|
||||
$('#create-alert').modal('show');
|
||||
} else {
|
||||
toastr.error(data.message);
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
toastr.error('Failed to process template');
|
||||
}
|
||||
});
|
||||
}).end();
|
||||
});
|
||||
</script>
|
||||
|
@ -1,69 +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.
|
||||
*/
|
||||
|
||||
if (is_admin() === false) {
|
||||
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 alert map 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="alert-map-removal" data-target="alert-map-removal">Delete</button>
|
||||
<input type="hidden" name="map_id" id="map_id" value="">
|
||||
<input type="hidden" name="confirm" id="confirm" value="yes">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$('#confirm-delete').on('show.bs.modal', function(event) {
|
||||
map_id = $(event.relatedTarget).data('map_id');
|
||||
$("#map_id").val(map_id);
|
||||
});
|
||||
|
||||
$('#alert-map-removal').click('', function(event) {
|
||||
event.preventDefault();
|
||||
var map_id = $("#map_id").val();
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: 'ajax_form.php',
|
||||
data: { type: "delete-alert-map", map_id: map_id },
|
||||
dataType: "html",
|
||||
success: function(msg) {
|
||||
if(msg.indexOf("ERROR:") <= -1) {
|
||||
$("#row_"+map_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 alert map could not be deleted.</div>');
|
||||
$("#confirm-delete").modal('hide');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
@ -1,191 +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.
|
||||
*/
|
||||
|
||||
if (is_admin() !== false) {
|
||||
?>
|
||||
|
||||
<div class="modal fade bs-example-modal-sm" id="create-map" 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">Alert Maps</h5>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form method="post" role="form" id="maps" class="form-horizontal maps-form">
|
||||
<input type="hidden" name="map_id" id="map_id" value="">
|
||||
<input type="hidden" name="type" id="type" value="create-map-item">
|
||||
<div class='form-group'>
|
||||
<label for='name' class='col-sm-3 control-label'>Rule: </label>
|
||||
<div class='col-sm-9'>
|
||||
<input type='text' id='rule' name='rule' class='form-control' maxlength='200'>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for='target' class='col-sm-3 control-label'>Target: </label>
|
||||
<div class="col-sm-9">
|
||||
<input type='text' id='target' name='target' class='form-control' placeholder='Group or Hostname'/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-3 col-sm-3">
|
||||
<button class="btn btn-default btn-sm" type="submit" name="map-submit" id="map-submit" value="save">Save map</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$('#create-map').on('show.bs.modal', function (event) {
|
||||
var button = $(event.relatedTarget);
|
||||
var map_id = button.data('map_id');
|
||||
var modal = $(this)
|
||||
$('#map_id').val(map_id);
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "ajax_form.php",
|
||||
data: { type: "parse-alert-map", map_id: map_id },
|
||||
dataType: "json",
|
||||
success: function(output) {
|
||||
$('#rule').val(output['rule']);
|
||||
$('#target').val(output['target']);
|
||||
}
|
||||
});
|
||||
});
|
||||
var cache = {};
|
||||
var alert_rules = new Bloodhound({
|
||||
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'),
|
||||
queryTokenizer: Bloodhound.tokenizers.whitespace,
|
||||
remote: {
|
||||
url: "ajax_search.php?search=%QUERY&type=alert-rules",
|
||||
filter: function (output) {
|
||||
return $.map(output, function (item) {
|
||||
return {
|
||||
name: item.name,
|
||||
};
|
||||
});
|
||||
},
|
||||
wildcard: "%QUERY"
|
||||
}
|
||||
});
|
||||
alert_rules.initialize();
|
||||
$('#rule').typeahead({
|
||||
hint: true,
|
||||
highlight: true,
|
||||
minLength: 1,
|
||||
classNames: {
|
||||
menu: 'typeahead-left'
|
||||
}
|
||||
},
|
||||
{
|
||||
source: alert_rules.ttAdapter(),
|
||||
async: true,
|
||||
displayKey: 'name',
|
||||
valueKey: name,
|
||||
templates: {
|
||||
alert_rules: Handlebars.compile('<p> {{name}}</p>')
|
||||
}
|
||||
});
|
||||
|
||||
var map_devices = new Bloodhound({
|
||||
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'),
|
||||
queryTokenizer: Bloodhound.tokenizers.whitespace,
|
||||
remote: {
|
||||
url: "ajax_search.php?search=%QUERY&type=device&map=1",
|
||||
filter: function (output) {
|
||||
return $.map(output, function (item) {
|
||||
return {
|
||||
name: item.name,
|
||||
};
|
||||
});
|
||||
},
|
||||
wildcard: "%QUERY"
|
||||
}
|
||||
});
|
||||
var map_groups = new Bloodhound({
|
||||
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'),
|
||||
queryTokenizer: Bloodhound.tokenizers.whitespace,
|
||||
remote: {
|
||||
url: "ajax_search.php?search=%QUERY&type=group&map=1",
|
||||
filter: function (output) {
|
||||
return $.map(output, function (item) {
|
||||
return {
|
||||
name: item.name,
|
||||
};
|
||||
});
|
||||
},
|
||||
wildcard: "%QUERY"
|
||||
}
|
||||
});
|
||||
map_devices.initialize();
|
||||
map_groups.initialize();
|
||||
$('#target').typeahead({
|
||||
hint: true,
|
||||
highlight: true,
|
||||
minLength: 1,
|
||||
classNames: {
|
||||
menu: 'typeahead-left'
|
||||
}
|
||||
},
|
||||
{
|
||||
source: map_devices.ttAdapter(),
|
||||
async: true,
|
||||
displayKey: 'name',
|
||||
valueKey: name,
|
||||
templates: {
|
||||
header: '<h5><strong> Devices</strong></h5>',
|
||||
suggestion: Handlebars.compile('<p> {{name}}</p>')
|
||||
}
|
||||
},
|
||||
{
|
||||
source: map_groups.ttAdapter(),
|
||||
async: true,
|
||||
displayKey: 'name',
|
||||
valueKey: name,
|
||||
templates: {
|
||||
header: '<h5><strong> Groups</strong></h5>',
|
||||
suggestion: Handlebars.compile('<p> {{name}}</p>')
|
||||
}
|
||||
});
|
||||
|
||||
$('#map-submit').click('', function(e) {
|
||||
e.preventDefault();
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "ajax_form.php",
|
||||
data: $('form.maps-form').serialize(),
|
||||
success: function(msg){
|
||||
$("#message").html('<div class="alert alert-info">'+msg+'</div>');
|
||||
$("#create-map").modal('hide');
|
||||
if(msg.indexOf("ERROR:") <= -1) {
|
||||
setTimeout(function() {
|
||||
location.reload(1);
|
||||
}, 1000);
|
||||
}
|
||||
},
|
||||
error: function(){
|
||||
$("#message").html('<div class="alert alert-info">An error occurred creating this map.</div>');
|
||||
$("#create-map").modal('hide');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<?php
|
||||
}
|
||||
|
||||
?>
|
@ -2,7 +2,8 @@
|
||||
/*
|
||||
* LibreNMS
|
||||
*
|
||||
* Copyright (c) 2014 Neil Lathwood <https://github.com/laf/ http://www.lathwood.co.uk/fa>
|
||||
* Copyright (c) 2018 Neil Lathwood <https://github.com/laf/ http://www.lathwood.co.uk/fa>
|
||||
* Copyright (c) 2018 Tony Murray <murraytony@gmail.com>
|
||||
*
|
||||
* 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
|
||||
@ -11,93 +12,84 @@
|
||||
* the source code distribution for details.
|
||||
*/
|
||||
|
||||
if (is_admin() !== false) {
|
||||
?>
|
||||
use LibreNMS\Alerting\QueryBuilderFilter;
|
||||
|
||||
<div class="modal fade bs-example-modal-sm" id="create-alert" tabindex="-1" role="dialog" aria-labelledby="Create" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
if (is_admin()) {
|
||||
$filters = json_encode(new QueryBuilderFilter('alert'));
|
||||
|
||||
?>
|
||||
|
||||
<div class="modal fade" id="create-alert" tabindex="-1" role="dialog"
|
||||
aria-labelledby="Create" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<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">Alert Rules</h5>
|
||||
<h5 class="modal-title" id="Create">Alert Rules :: <a href="https://docs.librenms.org/Alerting/">Docs <i class="fa fa-book fa-1x"></i></a> </h5>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form method="post" role="form" id="rules" class="form-horizontal alerts-form">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<span id="response"></span>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="device_id" id="device_id" value="">
|
||||
<input type="hidden" name="alert_id" id="alert_id" value="">
|
||||
<input type="hidden" name="type" id="type" value="create-alert-item">
|
||||
<input type="hidden" name="template_id" id="template_id" value="">
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<span id="ajax_response"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for='entity' class='col-sm-3 control-label'>Entity: </label>
|
||||
<div class="col-sm-5">
|
||||
<input type='text' id='suggest' name='entity' 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'>
|
||||
<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'/> <span id="next-step-value"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="post" role="form" id="rules" class="form-horizontal alerts-form">
|
||||
<input type="hidden" name="device_id" id="device_id" value="<?php echo isset($device['device_id']) ? $device['device_id'] : -1; ?>">
|
||||
<input type="hidden" name="device_name" id="device_name" value="<?php echo format_hostname($device); ?>">
|
||||
<input type="hidden" name="rule_id" id="rule_id" value="">
|
||||
<input type="hidden" name="type" id="type" value="alert-rules">
|
||||
<input type="hidden" name="template_id" id="template_id" value="">
|
||||
<input type="hidden" name="builder_json" id="builder_json" value="">
|
||||
<div class='form-group'>
|
||||
<label for='name' class='col-sm-3 control-label'>Rule name: </label>
|
||||
<div class='col-sm-9'>
|
||||
<input type='text' id='name' name='name' class='form-control validation' maxlength='200' required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-3 col-sm-5">
|
||||
<button class="btn btn-default btn-sm btn-warning" type="submit" name="rule-glue" value="&&" id="and" name="and">Add</button>
|
||||
<span id="next-step-and"></span>
|
||||
<div class="col-sm-3">
|
||||
<div class="pull-right">
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-default dropdown-toggle" type="button"
|
||||
id="import-from" data-toggle="dropdown" aria-haspopup="true"
|
||||
aria-expanded="true">
|
||||
Import from
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="import-from"
|
||||
id="import-dropdown">
|
||||
<li><a href="#" name="import-query" id="import-query">SQL Query</a>
|
||||
</li>
|
||||
<li><a href="#" name="import-old-format" id="import-old-format">Old
|
||||
Format</a></li>
|
||||
<li><a href="#" name="import-collection" id="import-collection">Collection</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-9">
|
||||
<div id="builder"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for='severity' class='col-sm-3 control-label'>Severity: </label>
|
||||
<div class="col-sm-5">
|
||||
<select name='severity' id='severity' placeholder='Severity' class='form-control'>
|
||||
<div class="col-sm-2">
|
||||
<select name='severity' id='severity' class='form-control'>
|
||||
<option value='ok'>OK</option>
|
||||
<option value='warning'>Warning</option>
|
||||
<option value='critical' selected>Critical</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-group form-inline">
|
||||
<label for='count' class='col-sm-3 control-label'>Max alerts: </label>
|
||||
<div class='col-sm-2'>
|
||||
<input type='text' id='count' name='count' class='form-control'>
|
||||
<div class="col-sm-2">
|
||||
<input type='text' id='count' name='count' class='form-control' size="4" value="123">
|
||||
</div>
|
||||
<label for='delay' class='col-sm-1 control-label'>Delay: </label>
|
||||
<div class='col-sm-2'>
|
||||
<input type='text' id='delay' name='delay' class='form-control'>
|
||||
<div class="col-sm-3">
|
||||
<label for='delay' class='control-label' style="vertical-align: top;">Delay: </label>
|
||||
<input type='text' id='delay' name='delay' class='form-control' size="4">
|
||||
</div>
|
||||
<label for='interval' class='col-sm-2 control-label'>Interval: </label>
|
||||
<div class='col-sm-2'>
|
||||
<input type='text' id='interval' name='interval' class='form-control'>
|
||||
<div class="col-sm-4">
|
||||
<label for='interval' class='control-label align-top' style="vertical-align: top;">Interval: </label>
|
||||
<input type='text' id='interval' name='interval' class='form-control' size="4">
|
||||
</div>
|
||||
</div>
|
||||
<div class='form-group'>
|
||||
@ -110,307 +102,246 @@ if (is_admin() !== false) {
|
||||
<input type='checkbox' name='invert' id='invert'>
|
||||
</div>
|
||||
</div>
|
||||
<div class='form-group'>
|
||||
<label for='name' class='col-sm-3 control-label'>Rule name: </label>
|
||||
<div class='col-sm-9'>
|
||||
<input type='text' id='name' name='name' class='form-control' maxlength='200'>
|
||||
</div>
|
||||
</div>
|
||||
<div id="preseed-maps">
|
||||
<div class="form-group">
|
||||
<label for='map-stub' class='col-sm-3 control-label'>Map To: </label>
|
||||
<div class="col-sm-5">
|
||||
<input type='text' id='map-stub' name='map-stub' class='form-control'/>
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<button class="btn btn-primary btn-sm" type="button" name="add-map" id="add-map" value="Add">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<span id="map-tags"></span>
|
||||
</div>
|
||||
<label for='maps' class='col-sm-3 control-label'data-toggle="tooltip" title="Restricts this alert rule to the selected devices or groups.">Map To: </label>
|
||||
<div class="col-sm-9">
|
||||
<select id="maps" name="maps[]" class="form-control" multiple="multiple"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class='form-group'>
|
||||
<label for='proc' class='col-sm-3 control-label'>Procedure URL: </label>
|
||||
<div class='col-sm-9'>
|
||||
<input type='text' id='proc' name='proc' class='form-control' maxlength='80'>
|
||||
<div class='col-sm-5'>
|
||||
<input type='text' id='proc' name='proc' class='form-control validation' pattern='(http|https)://.*' maxlength='80'>
|
||||
</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="rule-submit" id="rule-submit" value="save">Save Rule</button>
|
||||
<button type="button" class="btn btn-success" id="btn-save" name="save-alert">
|
||||
Save Rule
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
$("[name='mute']").bootstrapSwitch('offColor','danger');
|
||||
$("[name='invert']").bootstrapSwitch('offColor','danger');
|
||||
<script src="js/sql-parser.min.js"></script>
|
||||
<script src="js/query-builder.standalone.min.js"></script>
|
||||
<script>
|
||||
$('#builder').on('afterApplyRuleFlags.queryBuilder afterCreateRuleFilters.queryBuilder', function () {
|
||||
$("[name$='_filter']").each(function () {
|
||||
$(this).select2({
|
||||
dropdownParent: $("#create-alert"),
|
||||
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'
|
||||
],
|
||||
|
||||
$('#create-alert').on('hide.bs.modal', function (event) {
|
||||
$('#response').data('tagmanager').empty();
|
||||
$('#map-tags').data('tagmanager').empty();
|
||||
});
|
||||
|
||||
$('#add-map').click('',function (event) {
|
||||
$('#map-tags').data('tagmanager').populate([ $('#map-stub').val() ]);
|
||||
$('#map-stub').val('');
|
||||
});
|
||||
|
||||
$('#create-alert').on('show.bs.modal', function (event) {
|
||||
var button = $(event.relatedTarget);
|
||||
var device_id = button.data('device_id');
|
||||
var alert_id = button.data('alert_id');
|
||||
var modal = $(this)
|
||||
var template_id = $('#template_id').val();
|
||||
$('#template_id').val('');
|
||||
var arr = [];
|
||||
if (template_id >= 0) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "ajax_form.php",
|
||||
data: { type: "parse-alert-rule", alert_id: null, template_id: template_id },
|
||||
dataType: "json",
|
||||
success: function(output) {
|
||||
$.each ( output['rules'], function( key, value ) {
|
||||
arr.push(value);
|
||||
});
|
||||
$('#response').data('tagmanager').populate(arr);
|
||||
$('#name').val(output['name']);
|
||||
$('#device_id').val("-1");
|
||||
filters: <?php echo $filters; ?>,
|
||||
operators: [
|
||||
'equal', 'not_equal', 'in', 'not_in', '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',
|
||||
{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: 'regexp', nb_inputs: 1, multiple: false, apply_to: ['string']}
|
||||
],
|
||||
lang: {
|
||||
operators: {
|
||||
regexp: 'regex'
|
||||
}
|
||||
});
|
||||
}
|
||||
$('#device_id').val(device_id);
|
||||
$('#alert_id').val(alert_id);
|
||||
$('#tagmanager').tagmanager();
|
||||
$('#response').tagmanager({
|
||||
strategy: 'array',
|
||||
tagFieldName: 'rules[]'
|
||||
});
|
||||
$('#map-tags').tagmanager({
|
||||
strategy: 'array',
|
||||
tagFieldName: 'maps[]',
|
||||
initialCap: false
|
||||
});
|
||||
if( $('#alert_id').val() == '' || template_id == '') {
|
||||
$('#preseed-maps').show();
|
||||
} else {
|
||||
$('#preseed-maps').hide();
|
||||
}
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "ajax_form.php",
|
||||
data: { type: "parse-alert-rule", alert_id: alert_id },
|
||||
dataType: "json",
|
||||
success: function(output) {
|
||||
$.each ( output['rules'], function( key, value ) {
|
||||
arr.push(value);
|
||||
});
|
||||
$('#response').data('tagmanager').populate(arr);
|
||||
$('#severity').val(output['severity']).change;
|
||||
var extra = $.parseJSON(output['extra']);
|
||||
$('#count').val(extra['count']);
|
||||
if((extra['delay'] / 86400) >= 1) {
|
||||
var delay = extra['delay'] / 86400 + ' d';
|
||||
} else if((extra['delay'] / 3600) >= 1) {
|
||||
var delay = extra['delay'] / 3600 + ' h';
|
||||
} else if((extra['delay'] / 60) >= 1) {
|
||||
var delay = extra['delay'] / 60 + ' m';
|
||||
} else {
|
||||
var delay = extra['delay'];
|
||||
}
|
||||
$('#delay').val(delay);
|
||||
if((extra['interval'] / 86400) >= 1) {
|
||||
var interval = extra['interval'] / 86400 + ' d';
|
||||
} else if((extra['interval'] / 3600) >= 1) {
|
||||
var interval = extra['interval'] / 3600 + ' h';
|
||||
} else if((extra['interval'] / 60) >= 1) {
|
||||
var interval = extra['interval'] / 60 + ' m';
|
||||
} else {
|
||||
var interval = extra['interval'];
|
||||
}
|
||||
$('#interval').val(interval);
|
||||
$("[name='mute']").bootstrapSwitch('state',extra['mute']);
|
||||
$("[name='invert']").bootstrapSwitch('state',extra['invert']);
|
||||
$('#name').val(output['name']);
|
||||
$('#proc').val(output['proc']);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<script>
|
||||
var cache = {};
|
||||
var suggestions = new Bloodhound({
|
||||
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'),
|
||||
queryTokenizer: Bloodhound.tokenizers.whitespace,
|
||||
remote: {
|
||||
url: "ajax_rulesuggest.php?device_id=<?php echo $device['device_id'];?>&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>')
|
||||
sqlOperators: {
|
||||
regexp: {op: 'REGEXP'}
|
||||
},
|
||||
limit: 20
|
||||
});
|
||||
|
||||
var map_devices = new Bloodhound({
|
||||
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'),
|
||||
queryTokenizer: Bloodhound.tokenizers.whitespace,
|
||||
remote: {
|
||||
url: "ajax_search.php?search=%QUERY&type=device&map=1",
|
||||
filter: function (output) {
|
||||
return $.map(output, function (item) {
|
||||
return {
|
||||
name: item.name,
|
||||
};
|
||||
sqlRuleOperator: {
|
||||
'REGEXP': function (v) {
|
||||
return {val: v, op: 'regexp'};
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
wildcard: "%QUERY"
|
||||
}
|
||||
});
|
||||
var map_groups = new Bloodhound({
|
||||
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'),
|
||||
queryTokenizer: Bloodhound.tokenizers.whitespace,
|
||||
remote: {
|
||||
url: "ajax_search.php?search=%QUERY&type=group&map=1",
|
||||
filter: function (output) {
|
||||
return $.map(output, function (item) {
|
||||
return {
|
||||
name: item.name,
|
||||
};
|
||||
});
|
||||
},
|
||||
wildcard: "%QUERY"
|
||||
}
|
||||
});
|
||||
map_devices.initialize();
|
||||
map_groups.initialize();
|
||||
$('#map-stub').typeahead({
|
||||
hint: true,
|
||||
highlight: true,
|
||||
minLength: 1,
|
||||
classNames: {
|
||||
menu: 'typeahead-left'
|
||||
}
|
||||
},
|
||||
{
|
||||
source: map_devices.ttAdapter(),
|
||||
async: true,
|
||||
displayKey: 'name',
|
||||
valueKey: name,
|
||||
templates: {
|
||||
header: '<h5><strong> Devices</strong></h5>',
|
||||
suggestion: Handlebars.compile('<p> {{name}}</p>')
|
||||
}
|
||||
},
|
||||
{
|
||||
source: map_groups.ttAdapter(),
|
||||
async: true,
|
||||
displayKey: 'name',
|
||||
valueKey: name,
|
||||
templates: {
|
||||
header: '<h5><strong> Groups</strong></h5>',
|
||||
suggestion: Handlebars.compile('<p> {{name}}</p>')
|
||||
}
|
||||
});
|
||||
|
||||
$('#and').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: 'rules[]'
|
||||
});
|
||||
if(value.indexOf("%") < 0 && isNaN(value)) {
|
||||
value = '"'+value+'"';
|
||||
}
|
||||
if(entity.indexOf("%") < 0) {
|
||||
entity = '%'+entity;
|
||||
}
|
||||
$('#response').data('tagmanager').populate([ entity+' '+condition+' '+value+' '+glue ]);
|
||||
}
|
||||
});
|
||||
|
||||
$('#rule-submit').click('', function(e) {
|
||||
$('#btn-save').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
var result_json = $('#builder').queryBuilder('getRules');
|
||||
if (result_json !== null && result_json.valid) {
|
||||
$('#builder_json').val(JSON.stringify(result_json));
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "ajax_form.php",
|
||||
data: $('form.alerts-form').serialize(),
|
||||
success: function(msg){
|
||||
if(msg.indexOf("ERROR:") <= -1) {
|
||||
$("#message").html('<div class="alert alert-info">'+msg+'</div>');
|
||||
$("#create-alert").modal('hide');
|
||||
$('#response').data('tagmanager').empty();
|
||||
setTimeout(function() {
|
||||
location.reload(1);
|
||||
}, 1000);
|
||||
data: $('form.alerts-form').serializeArray(),
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
if (data.status == 'ok') {
|
||||
toastr.success(data.message);
|
||||
$('#create-alert').modal('hide');
|
||||
window.location.reload(); // FIXME: reload table
|
||||
} 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>');
|
||||
toastr.error(data.message);
|
||||
}
|
||||
},
|
||||
error: function(){
|
||||
$("#ajax_response").html('<div class="alert alert-info">An error occurred creating this alert.</div>');
|
||||
error: function () {
|
||||
toastr.error('Failed to process rule');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$( "#suggest, #value" ).blur(function() {
|
||||
var $this = $(this);
|
||||
var suggest = $('#suggest').val();
|
||||
var value = $('#value').val();
|
||||
if (suggest == "") {
|
||||
$("#next-step-and").html("");
|
||||
$("#value").closest('.form-group').removeClass('has-error');
|
||||
$("#suggest").closest('.form-group').addClass('has-error');
|
||||
} else if (value == "") {
|
||||
$("#next-step-and").html("");
|
||||
$("#value").closest('.form-group').addClass('has-error');
|
||||
$("#suggest").closest('.form-group').removeClass('has-error');
|
||||
} else {
|
||||
$("#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 Add');
|
||||
}
|
||||
});
|
||||
});
|
||||
$('#import-query').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
var sql_import = window.prompt("Enter your SQL query:");
|
||||
if (sql_import) {
|
||||
try {
|
||||
$("#builder").queryBuilder("setRulesFromSQL", sql_import);
|
||||
} catch (e) {
|
||||
alert('Your query could not be parsed');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
$('#import-old-format').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
var old_import = window.prompt("Enter your old alert rule:");
|
||||
if (old_import) {
|
||||
try {
|
||||
old_import = old_import.replace(/&&/g, 'AND');
|
||||
old_import = old_import.replace(/\|\|/g, 'OR');
|
||||
old_import = old_import.replace(/%/g, '');
|
||||
old_import = old_import.replace(/"/g, "'");
|
||||
old_import = old_import.replace(/~/g, "REGEXP");
|
||||
old_import = old_import.replace(/@/g, ".*");
|
||||
$("#builder").queryBuilder("setRulesFromSQL", old_import);
|
||||
} catch (e) {
|
||||
alert('Your query could not be parsed');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
<?php
|
||||
$('#import-collection').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
$("#search_rule_modal").modal('show');
|
||||
});
|
||||
|
||||
$('#create-alert').on('show.bs.modal', function(e) {
|
||||
//get data-id attribute of the clicked element
|
||||
var rule_id = $(e.relatedTarget).data('rule_id');
|
||||
$('#rule_id').val(rule_id);
|
||||
|
||||
if (rule_id >= 0) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "ajax_form.php",
|
||||
data: { type: "parse-alert-rule", alert_id: rule_id },
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
loadRule(data);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// new, reset form
|
||||
$("#builder").queryBuilder("reset");
|
||||
var $severity = $('#severity');
|
||||
$severity.val($severity.find("option[selected]").val());
|
||||
$("#mute").bootstrapSwitch('state', false);
|
||||
$("#invert").bootstrapSwitch('state', false);
|
||||
$(this).find("input[type=text]").val("");
|
||||
$('#count').val('-1');
|
||||
$('#delay').val('5 m');
|
||||
$('#interval').val('5 m');
|
||||
|
||||
var $maps = $('#maps');
|
||||
$maps.empty();
|
||||
$maps.val(null).trigger('change');
|
||||
setRuleDevice() // pre-populate device in the maps if this is a per-device rule
|
||||
}
|
||||
});
|
||||
|
||||
function loadRule(rule) {
|
||||
$('#name').val(rule.name);
|
||||
$('#proc').val(rule.proc);
|
||||
$('#builder').queryBuilder("setRules", rule.builder);
|
||||
$('#severity').val(rule.severity).trigger('change');
|
||||
|
||||
var $maps = $('#maps');
|
||||
$maps.empty();
|
||||
$maps.val(null).trigger('change'); // clear
|
||||
if (rule.maps == null) {
|
||||
// collection rule
|
||||
setRuleDevice()
|
||||
} else {
|
||||
$.each(rule.maps, function(index, value) {
|
||||
var option = new Option(value.text, value.id, true, true);
|
||||
$maps.append(option).trigger('change')
|
||||
});
|
||||
}
|
||||
|
||||
if (rule.extra != null) {
|
||||
var extra = rule.extra;
|
||||
$('#count').val(extra.count);
|
||||
if ((extra.delay / 86400) >= 1) {
|
||||
$('#delay').val(extra.delay / 86400 + ' d');
|
||||
} else if ((extra.delay / 3600) >= 1) {
|
||||
$('#delay').val( extra.delay / 3600 + ' h');
|
||||
} else if ((extra.delay / 60) >= 1) {
|
||||
$('#delay').val( extra.delay / 60 + ' m');
|
||||
} else {
|
||||
$('#delay').val(extra.delay);
|
||||
}
|
||||
|
||||
if ((extra.interval / 86400) >= 1) {
|
||||
$('#interval').val(extra.interval / 86400 + ' d');
|
||||
} else if ((extra.interval / 3600) >= 1) {
|
||||
$('#interval').val(extra.interval / 3600 + ' h');
|
||||
} else if ((extra.interval / 60) >= 1) {
|
||||
$('#interval').val(extra.interval / 60 + ' m');
|
||||
} else {
|
||||
$('#interval').val(extra.interval);
|
||||
}
|
||||
|
||||
$("[name='mute']").bootstrapSwitch('state', extra.mute);
|
||||
$("[name='invert']").bootstrapSwitch('state', extra.invert);
|
||||
} else {
|
||||
$('#count').val('-1');
|
||||
}
|
||||
}
|
||||
|
||||
function setRuleDevice() {
|
||||
// pre-populate device in the maps if this is a per-device rule
|
||||
var device_id = $('#device_id').val();
|
||||
if (device_id > 0) {
|
||||
var device_name = $('#device_name').val();
|
||||
var option = new Option(device_name, device_id, true, true);
|
||||
$('#maps').append(option).trigger('change')
|
||||
}
|
||||
}
|
||||
|
||||
$("#maps").select2({
|
||||
width: '100%',
|
||||
placeholder: "Devices or Groups",
|
||||
ajax: {
|
||||
url: 'ajax_list.php',
|
||||
delay: 250,
|
||||
data: function (params) {
|
||||
return {
|
||||
type: 'devices_groups',
|
||||
search: params.term
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
|
@ -23,6 +23,8 @@
|
||||
* @author Neil Lathwood <neil@lathwood.co.uk>
|
||||
*/
|
||||
|
||||
use LibreNMS\Alerting\QueryBuilderParser;
|
||||
|
||||
if (!is_admin()) {
|
||||
echo("Insufficient Privileges");
|
||||
exit();
|
||||
@ -41,7 +43,7 @@ switch ($type) {
|
||||
$results = array();
|
||||
foreach ($rules as $rule) {
|
||||
if (empty($rule['query'])) {
|
||||
$rule['query'] = GenSQL($rule['rule']);
|
||||
$rule['query'] = GenSQL($rule['rule'], $rule['builder']);
|
||||
}
|
||||
$sql = $rule['query'];
|
||||
$qry = dbFetchRow($sql, array($device_id));
|
||||
@ -51,8 +53,15 @@ switch ($type) {
|
||||
} else {
|
||||
$response = 'no match';
|
||||
}
|
||||
|
||||
if ($rule['builder']) {
|
||||
$qb = QueryBuilderParser::fromJson($rule['builder']);
|
||||
} else {
|
||||
$qb = QueryBuilderParser::fromOld($rule['rule']);
|
||||
}
|
||||
|
||||
$output .= 'Rule name: ' . $rule['name'] . PHP_EOL;
|
||||
$output .= 'Alert rule: ' . $rule['rule'] . PHP_EOL;
|
||||
$output .= 'Alert rule: ' . $qb->toSql(false) . PHP_EOL;
|
||||
$output .= 'Alert query: ' . $rule['query'] . PHP_EOL;
|
||||
$output .= 'Rule match: ' . $response . PHP_EOL . PHP_EOL;
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
use LibreNMS\Alerting\QueryBuilderParser;
|
||||
|
||||
$no_refresh = true;
|
||||
|
||||
?>
|
||||
@ -23,18 +25,17 @@ if (isset($_POST['create-default'])) {
|
||||
'interval' => 300,
|
||||
);
|
||||
|
||||
require_once '../includes/alerts.inc.php';
|
||||
|
||||
foreach ($default_rules as $add_rule) {
|
||||
$extra = $default_extra;
|
||||
if (isset($add_rule['extra'])) {
|
||||
$extra = array_replace($extra, json_decode($add_rule['extra'], true));
|
||||
}
|
||||
|
||||
$qb = QueryBuilderParser::fromOld($add_rule['rule']);
|
||||
$insert = array(
|
||||
'device_id' => -1,
|
||||
'rule' => $add_rule['rule'],
|
||||
'query' => GenSQL($add_rule['rule']),
|
||||
'rule' => '',
|
||||
'builder' => json_encode($qb),
|
||||
'query' => $qb->toSql(),
|
||||
'severity' => 'critical',
|
||||
'extra' => json_encode($extra),
|
||||
'disabled' => 0,
|
||||
@ -43,7 +44,8 @@ if (isset($_POST['create-default'])) {
|
||||
|
||||
dbInsert($insert, 'alert_rules');
|
||||
}
|
||||
}//end if
|
||||
unset($qb);
|
||||
}
|
||||
|
||||
require_once 'includes/modal/new_alert_rule.inc.php';
|
||||
require_once 'includes/modal/delete_alert_rule.inc.php';
|
||||
@ -67,11 +69,11 @@ echo '<div class="table-responsive">
|
||||
<th>Status</th>
|
||||
<th>Extra</th>
|
||||
<th>Enabled</th>
|
||||
<th>Action</th>
|
||||
<th style="width:86px;">Action</th>
|
||||
</tr>';
|
||||
|
||||
echo '<td colspan="7">';
|
||||
if ($_SESSION['userlevel'] >= '10') {
|
||||
if (is_admin()) {
|
||||
echo '<button type="button" class="btn btn-primary btn-sm" data-toggle="modal" data-target="#create-alert" data-device_id="'.$device['device_id'].'"><i class="fa fa-plus"></i> Create new alert rule</button>';
|
||||
echo '<i> - OR - </i>';
|
||||
echo '<button type="button" class="btn btn-primary btn-sm" data-toggle="modal" data-target="#search_rule_modal" data-device_id="'.$device['device_id'].'"><i class="fa fa-plus"></i> Create rule from collection</button>';
|
||||
@ -98,26 +100,24 @@ foreach ($result_options as $option) {
|
||||
|
||||
echo '</select></td>';
|
||||
|
||||
$count_query = 'SELECT COUNT(*)';
|
||||
$full_query = 'SELECT *';
|
||||
$sql = '';
|
||||
$param = array();
|
||||
$query = 'FROM alert_rules';
|
||||
$where = '';
|
||||
$param = [];
|
||||
if (isset($device['device_id']) && $device['device_id'] > 0) {
|
||||
$sql = 'WHERE (device_id=? OR device_id="-1")';
|
||||
$param = array($device['device_id']);
|
||||
$query .= ' LEFT JOIN alert_device_map ON alert_rules.id=alert_device_map.rule_id';
|
||||
$where = 'WHERE (device_id=? OR device_id IS NULL)';
|
||||
$param[] = $device['device_id'];
|
||||
}
|
||||
|
||||
$query = " FROM alert_rules $sql";
|
||||
$count_query = $count_query.$query;
|
||||
$count = dbFetchCell($count_query, $param);
|
||||
if (!isset($_POST['page_number']) && $_POST['page_number'] < 1) {
|
||||
$page_number = 1;
|
||||
} else {
|
||||
$count = dbFetchCell("SELECT COUNT(*) $query $where", $param);
|
||||
if (isset($_POST['page_number']) && $_POST['page_number'] > 0 && $_POST['page_number'] <= $count) {
|
||||
$page_number = $_POST['page_number'];
|
||||
} else {
|
||||
$page_number = 1;
|
||||
}
|
||||
|
||||
$start = (($page_number - 1) * $results);
|
||||
$full_query = $full_query.$query." ORDER BY id ASC LIMIT $start,$results";
|
||||
$full_query = "SELECT alert_rules.* $query $where ORDER BY alert_rules.id ASC LIMIT $start,$results";
|
||||
|
||||
foreach (dbFetchRows($full_query, $param) as $rule) {
|
||||
$sub = dbFetchRows('SELECT * FROM alerts WHERE rule_id = ? ORDER BY `state` DESC, `id` DESC LIMIT 1', array($rule['id']));
|
||||
@ -149,20 +149,38 @@ foreach (dbFetchRows($full_query, $param) as $rule) {
|
||||
}
|
||||
|
||||
$rule_extra = json_decode($rule['extra'], true);
|
||||
if ($rule['device_id'] == ':-1' || $rule['device_id'] == '-1') {
|
||||
$popover_msg = 'Global alert rule';
|
||||
|
||||
$device_count = dbFetchCell('SELECT COUNT(*) FROM alert_device_map WHERE rule_id=?', [$rule['id']]);
|
||||
$group_count = dbFetchCell('SELECT COUNT(*) FROM alert_group_map WHERE rule_id=?', [$rule['id']]);
|
||||
if ($device_count && $group_count) {
|
||||
$popover_msg = 'Restricted rule';
|
||||
$icon_indicator = 'fa fa-connectdevelop fa-fw text-primary';
|
||||
} elseif ($device_count) {
|
||||
$popover_msg = 'Device restricted rule';
|
||||
$icon_indicator = 'fa fa-server fa-fw text-primary';
|
||||
} elseif ($group_count) {
|
||||
$popover_msg = 'Group restricted rule';
|
||||
$icon_indicator = 'fa fa-th fa-fw text-primary';
|
||||
} else {
|
||||
$popover_msg = 'Device specific rule';
|
||||
$popover_msg = 'Global alert rule';
|
||||
$icon_indicator = 'fa fa-globe fa-fw text-success';
|
||||
}
|
||||
|
||||
echo "<tr class='".$extra."' id='row_".$rule['id']."'>";
|
||||
echo '<td><i>#'.((int) $rule['id']).'</i></td>';
|
||||
echo "<td><i>#".((int) $rule['id'])."</i><br /><i class=\"$icon_indicator\"></i></td>";
|
||||
echo '<td>'.$rule['name'].'</td>';
|
||||
echo "<td class='col-sm-4'>";
|
||||
if ($rule_extra['invert'] === true) {
|
||||
echo '<strong><em>Inverted</em></strong> ';
|
||||
}
|
||||
|
||||
echo '<i>'.htmlentities($rule['rule']).'</i></td>';
|
||||
if (empty($rule['builder'])) {
|
||||
$rule_display = $rule['rule'];
|
||||
} else {
|
||||
$rule_display = QueryBuilderParser::fromJson($rule['builder'])->toSql(false);
|
||||
}
|
||||
echo '<i>'.htmlentities($rule_display).'</i></td>';
|
||||
|
||||
echo '<td>'.$rule['severity'].'</td>';
|
||||
echo "<td><span id='alert-rule-".$rule['id']."' class='fa fa-fw fa-2x fa-".$ico.' text-'.$col."'></span> ";
|
||||
if ($rule_extra['mute'] === true) {
|
||||
@ -171,15 +189,15 @@ foreach (dbFetchRows($full_query, $param) as $rule) {
|
||||
|
||||
echo '<td><small>Max: '.$rule_extra['count'].'<br />Delay: '.$rule_extra['delay'].'<br />Interval: '.$rule_extra['interval'].'</small></td>';
|
||||
echo '<td>';
|
||||
if ($_SESSION['userlevel'] >= '10') {
|
||||
if (is_admin()) {
|
||||
echo "<input id='".$rule['id']."' type='checkbox' name='alert-rule' data-orig_class='".$orig_class."' data-orig_colour='".$orig_col."' data-orig_state='".$orig_ico."' data-alert_id='".$rule['id']."' ".$alert_checked." data-size='small' data-content='".$popover_msg."' data-toggle='modal'>";
|
||||
}
|
||||
|
||||
echo '</td>';
|
||||
echo '<td>';
|
||||
if ($_SESSION['userlevel'] >= '10') {
|
||||
if (is_admin()) {
|
||||
echo "<div class='btn-group btn-group-sm' role='group'>";
|
||||
echo "<button type='button' class='btn btn-primary' data-toggle='modal' data-target='#create-alert' data-device_id='".$rule['device_id']."' data-alert_id='".$rule['id']."' name='edit-alert-rule' data-content='".$popover_msg."' data-container='body'><i class='fa fa-lg fa-pencil' aria-hidden='true'></i></button> ";
|
||||
echo "<button type='button' class='btn btn-primary' data-toggle='modal' data-target='#create-alert' data-rule_id='".$rule['id']."' name='edit-alert-rule' data-content='".$popover_msg."' data-container='body'><i class='fa fa-lg fa-pencil' aria-hidden='true'></i></button> ";
|
||||
echo "<button type='button' class='btn btn-danger' aria-label='Delete' data-toggle='modal' data-target='#confirm-delete' data-alert_id='".$rule['id']."' name='delete-alert-rule' data-content='".$popover_msg."' data-container='body'><i class='fa fa-lg fa-trash' aria-hidden='true'></i></button>";
|
||||
}
|
||||
|
||||
@ -295,11 +313,4 @@ function changePage(page,e) {
|
||||
$('#result_form').submit();
|
||||
}
|
||||
|
||||
function newRule(data, e) {
|
||||
$('#template_id').val(data.value);
|
||||
$('#create-alert').modal({
|
||||
show: true
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
|
@ -605,7 +605,6 @@ if ($alerts['active_count'] > 0) {
|
||||
?>
|
||||
<li><a href="<?php echo(generate_url(array('page'=>'alert-rules'))); ?>"><i class="fa fa-list fa-fw fa-lg" aria-hidden="true"></i> Alert Rules</a></li>
|
||||
<li><a href="<?php echo(generate_url(array('page'=>'alert-schedule'))); ?>"><i class="fa fa-calendar fa-fw fa-lg" aria-hidden="true"></i> Scheduled Maintenance</a></li>
|
||||
<li><a href="<?php echo(generate_url(array('page'=>'alert-map'))); ?>"><i class="fa fa-connectdevelop fa-fw fa-lg" aria-hidden="true"></i> Rule Mapping</a></li>
|
||||
<li><a href="<?php echo(generate_url(array('page'=>'templates'))); ?>"><i class="fa fa-file fa-fw fa-lg" aria-hidden="true"></i> Alert Templates</a></li>
|
||||
<?php
|
||||
}
|
||||
|
@ -128,8 +128,9 @@ if (empty($config['favicon'])) {
|
||||
<link href="css/MarkerCluster.Default.css" rel="stylesheet" type="text/css" />
|
||||
<link href="css/leaflet.awesome-markers.css" rel="stylesheet" type="text/css" />
|
||||
<link href="css/select2.min.css" rel="stylesheet" type="text/css" />
|
||||
<link href="<?php echo($config['stylesheet']); ?>?ver=253427421" rel="stylesheet" type="text/css" />
|
||||
<link href="css/<?php echo $config['site_style']; ?>.css?ver=632417639" rel="stylesheet" type="text/css" />
|
||||
<link href="css/query-builder.default.min.css" rel="stylesheet" type="text/css" />
|
||||
<link href="<?php echo($config['stylesheet']); ?>?ver=291727421" rel="stylesheet" type="text/css" />
|
||||
<link href="css/<?php echo $config['site_style']; ?>.css?ver=632417642" rel="stylesheet" type="text/css" />
|
||||
<?php
|
||||
|
||||
foreach ((array)$config['webui']['custom_css'] as $custom_css) {
|
||||
|
8
html/js/query-builder.standalone.min.js
vendored
Normal file
8
html/js/query-builder.standalone.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
7
html/js/sql-parser.min.js
vendored
Normal file
7
html/js/sql-parser.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -29,6 +29,14 @@ echo '<div class="panel panel-default panel-condensed">
|
||||
</div>
|
||||
</div>
|
||||
';
|
||||
|
||||
if (isset($_POST['device_id'])) {
|
||||
$default_option = '<option value="' . (int)$_POST['device_id'] . '" selected="selected">';
|
||||
$default_option .= htmlentities($_POST['hostname']) . '</option>';
|
||||
} else {
|
||||
$default_option = '';
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<div class="table-responsive">
|
||||
@ -55,12 +63,13 @@ echo '<div class="panel panel-default panel-condensed">
|
||||
header: '<div id="{{ctx.id}}" class="{{css.header}}"><div class="row"> \
|
||||
<div class="col-sm-8 actionBar"><span class="pull-left"> \
|
||||
<form method="post" action="" class="form-inline" role="form" id="result_form"> \
|
||||
<input type=hidden name="hostname" id="hostname"> \
|
||||
<div class="form-group"> \
|
||||
<label> \
|
||||
<strong>Device </strong> \
|
||||
</label> \
|
||||
<select name="device_id" id="device_id" class="form-control input-sm" style="min-width: 175px;"> \
|
||||
<option value="">All Devices</option> \
|
||||
<?php echo $default_option; ?> \
|
||||
</select> \
|
||||
</div> \
|
||||
<div class="form-group"> \
|
||||
@ -118,5 +127,22 @@ echo '<div class="panel panel-default panel-condensed">
|
||||
});
|
||||
});
|
||||
|
||||
<?php echo generate_fill_select_js('hostnames', '#device_id', $_POST['device_id']); ?>
|
||||
$("#device_id").select2({
|
||||
allowClear: true,
|
||||
placeholder: "All Devices",
|
||||
ajax: {
|
||||
url: 'ajax_list.php',
|
||||
delay: 250,
|
||||
data: function (params) {
|
||||
return {
|
||||
type: 'devices',
|
||||
search: params.term,
|
||||
limit: 8,
|
||||
page: params.page || 1
|
||||
};
|
||||
}
|
||||
}
|
||||
}).on('select2:select', function (e) {
|
||||
$('#hostname').val(e.params.data.text);
|
||||
});
|
||||
</script>
|
||||
|
@ -1,31 +0,0 @@
|
||||
<?php
|
||||
require_once 'includes/modal/new_alert_map.inc.php';
|
||||
require_once 'includes/modal/delete_alert_map.inc.php';
|
||||
|
||||
$no_refresh = true;
|
||||
|
||||
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>Rule</th><th>Target</th><th>Actions</th>';
|
||||
echo '</tr></thead><tbody>';
|
||||
foreach (dbFetchRows('SELECT alert_map.target,alert_map.id,alert_rules.name FROM alert_map,alert_rules WHERE alert_map.rule=alert_rules.id ORDER BY alert_map.rule ASC') as $link) {
|
||||
if ($link['target'][0] == 'g') {
|
||||
$link['target'] = substr($link['target'], 1);
|
||||
$link['target'] = '<a href="'.generate_url(array('page' => 'devices', 'group' => $link['target'])).'">'.ucfirst(dbFetchCell('SELECT name FROM device_groups WHERE id = ?', array($link['target']))).'</a>';
|
||||
} elseif (is_numeric($link['target'])) {
|
||||
$link['target'] = '<a href="'.generate_url(array('page' => 'device', 'device' => $link['target'])).'">'.dbFetchCell('SELECT hostname FROM devices WHERE device_id = ?', array($link['target'])).'</a>';
|
||||
}
|
||||
|
||||
echo '<tr id="row_'.$link['id'].'">';
|
||||
echo '<td>'.$link['name'].'</td>';
|
||||
echo '<td>'.$link['target'].'</td>';
|
||||
echo '<td>';
|
||||
echo "<button type='button' class='btn btn-primary btn-sm' aria-label='Edit' data-toggle='modal' data-target='#create-map' data-map_id='".$link['id']."' name='edit-alert-map'><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-map_id='".$link['id']."' name='delete-alert-map'><span class='fa fa-trash' aria-hidden='true'></i></button>";
|
||||
echo '</td>';
|
||||
echo '</tr>';
|
||||
}
|
||||
|
||||
echo '</tbody></table></div>';
|
||||
echo "<button type='button' class='btn btn-primary btn-sm' aria-label='Add' data-toggle='modal' data-target='#create-map' data-map_id='' name='create-alert-map'>Create new Map</button> ";
|
@ -12,6 +12,4 @@
|
||||
* the source code distribution for details.
|
||||
*/
|
||||
|
||||
$device['device_id'] = '-1';
|
||||
require_once 'includes/print-alert-rules.php';
|
||||
unset($device['device_id']);
|
||||
|
@ -22,14 +22,29 @@
|
||||
* @subpackage Alerts
|
||||
*/
|
||||
|
||||
use LibreNMS\Alerting\QueryBuilderParser;
|
||||
use LibreNMS\Authentication\Auth;
|
||||
|
||||
/**
|
||||
* @param $rule
|
||||
* @param $query_builder
|
||||
* @return bool|string
|
||||
*/
|
||||
function GenSQL($rule, $query_builder = false)
|
||||
{
|
||||
if ($query_builder) {
|
||||
return QueryBuilderParser::fromJson($query_builder);
|
||||
} else {
|
||||
return GenSQLOld($rule);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate SQL from Rule
|
||||
* @param string $rule Rule to generate SQL for
|
||||
* @return string|boolean
|
||||
*/
|
||||
function GenSQL($rule)
|
||||
function GenSQLOld($rule)
|
||||
{
|
||||
$rule = RunMacros($rule);
|
||||
if (empty($rule)) {
|
||||
@ -99,10 +114,11 @@ function RunMacros($rule, $x = 1)
|
||||
krsort($config['alert']['macros']['rule']);
|
||||
foreach ($config['alert']['macros']['rule'] as $macro => $value) {
|
||||
if (!strstr($macro, " ")) {
|
||||
$rule = str_replace('%macros.'.$macro, '('.$value.')', $rule);
|
||||
$value = str_replace('%', '', $value);
|
||||
$rule = str_replace('macros.'.$macro, '('.$value.')', $rule);
|
||||
}
|
||||
}
|
||||
if (strstr($rule, "%macros")) {
|
||||
if (strstr($rule, "macros.")) {
|
||||
if (++$x < 30) {
|
||||
$rule = RunMacros($rule, $x);
|
||||
} else {
|
||||
@ -120,13 +136,15 @@ function RunMacros($rule, $x = 1)
|
||||
function GetRules($device)
|
||||
{
|
||||
$groups = GetGroupsFromDevice($device);
|
||||
$params = array($device,$device);
|
||||
$params = array($device, $device);
|
||||
$where = "";
|
||||
foreach ($groups as $group) {
|
||||
$where .= " || alert_map.target = ?";
|
||||
$params[] = 'g'.$group;
|
||||
$where .= " || group_id = ?";
|
||||
$params[] = $group;
|
||||
}
|
||||
return dbFetchRows('SELECT alert_rules.* FROM alert_rules LEFT JOIN alert_map ON alert_rules.id=alert_map.rule WHERE alert_rules.disabled = 0 && ( (alert_rules.device_id = -1 || alert_rules.device_id = ? ) || alert_map.target = ? '.$where.' )', $params);
|
||||
|
||||
$query = "SELECT DISTINCT a.* FROM alert_rules a LEFT JOIN alert_device_map d ON a.id=d.rule_id LEFT JOIN alert_group_map g ON a.id=d.rule_id WHERE a.disabled = 0 AND ((device_id=? OR device_id IS NULL) $where)";
|
||||
return dbFetchRows($query, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -167,7 +185,7 @@ function RunRules($device)
|
||||
d_echo(PHP_EOL);
|
||||
$chk = dbFetchRow("SELECT state FROM alerts WHERE rule_id = ? && device_id = ? ORDER BY id DESC LIMIT 1", array($rule['id'], $device));
|
||||
if (empty($rule['query'])) {
|
||||
$rule['query'] = GenSQL($rule['rule']);
|
||||
$rule['query'] = GenSQL($rule['rule'], $rule['builder']);
|
||||
}
|
||||
$sql = $rule['query'];
|
||||
$qry = dbFetchRows($sql, array($device));
|
||||
@ -630,7 +648,7 @@ function IssueAlert($alert)
|
||||
|
||||
if ($config['alert']['fixed-contacts'] == false) {
|
||||
if (empty($alert['query'])) {
|
||||
$alert['query'] = GenSQL($alert['rule']);
|
||||
$alert['query'] = GenSQL($alert['rule'], $alert['builder']);
|
||||
}
|
||||
$sql = $alert['query'];
|
||||
$qry = dbFetchRows($sql, array($alert['device_id']));
|
||||
@ -657,13 +675,13 @@ function IssueAlert($alert)
|
||||
*/
|
||||
function RunAcks()
|
||||
{
|
||||
|
||||
foreach (loadAlerts('alerts.state = 2 && alerts.open = 1') as $alert) {
|
||||
IssueAlert($alert);
|
||||
dbUpdate(array('open' => 0), 'alerts', 'rule_id = ? && device_id = ?', array($alert['rule_id'], $alert['device_id']));
|
||||
}
|
||||
}//end RunAcks()
|
||||
|
||||
|
||||
/**
|
||||
* Run Follow-Up alerts
|
||||
* @return void
|
||||
@ -677,7 +695,7 @@ function RunFollowUp()
|
||||
}
|
||||
|
||||
if (empty($alert['query'])) {
|
||||
$alert['query'] = GenSQL($alert['rule']);
|
||||
$alert['query'] = GenSQL($alert['rule'], $alert['builder']);
|
||||
}
|
||||
$chk = dbFetchRows($alert['query'], array($alert['device_id']));
|
||||
//make sure we can json_encode all the datas later
|
||||
@ -749,6 +767,7 @@ function RunAlerts()
|
||||
$updet = false;
|
||||
$rextra = json_decode($alert['extra'], true);
|
||||
$chk = dbFetchRow('SELECT alerts.alerted,devices.ignore,devices.disabled FROM alerts,devices WHERE alerts.device_id = ? && devices.device_id = alerts.device_id && alerts.rule_id = ?', array($alert['device_id'], $alert['rule_id']));
|
||||
|
||||
if ($chk['alerted'] == $alert['state']) {
|
||||
$noiss = true;
|
||||
}
|
||||
|
@ -1863,3 +1863,21 @@ 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;
|
||||
}
|
||||
|
@ -675,3 +675,60 @@ function recordDbStatistic($stat, $start_time)
|
||||
|
||||
return $runtime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronize a relationship to a list of related ids
|
||||
*
|
||||
* @param string $table
|
||||
* @param string $target_column column name for the target
|
||||
* @param int $target column target id
|
||||
* @param string $list_column related column names
|
||||
* @param array $list list of related ids
|
||||
* @return array [$inserted, $deleted]
|
||||
*/
|
||||
function dbSyncRelationship($table, $target_column = null, $target = null, $list_column = null, $list = null)
|
||||
{
|
||||
$inserted = 0;
|
||||
|
||||
$delete_query = "`$target_column`=? AND `$list_column`";
|
||||
$delete_params = [$target];
|
||||
if (!empty($list)) {
|
||||
$delete_query .= ' NOT IN ' . dbGenPlaceholders(count($list));
|
||||
$delete_params = array_merge($delete_params, $list);
|
||||
}
|
||||
$deleted = (int)dbDelete($table, $delete_query, $delete_params);
|
||||
|
||||
$db_list = dbFetchColumn("SELECT `$list_column` FROM `$table` WHERE `$target_column`=?", [$target]);
|
||||
foreach ($list as $item) {
|
||||
if (!in_array($item, $db_list)) {
|
||||
dbInsert([$target_column => $target, $list_column => $item], $table);
|
||||
$inserted++;
|
||||
}
|
||||
}
|
||||
|
||||
return [$inserted, $deleted];
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronize a relationship to a list of relations
|
||||
*
|
||||
* @param string $table
|
||||
* @param array $relationships array of relationship pairs with columns as keys and ids as values
|
||||
* @return array [$inserted, $deleted]
|
||||
*/
|
||||
function dbSyncRelationships($table, $relationships = array())
|
||||
{
|
||||
$changed = [[0, 0]];
|
||||
list($target_column, $list_column) = array_keys(reset($relationships));
|
||||
|
||||
$grouped = [];
|
||||
foreach ($relationships as $relationship) {
|
||||
$grouped[$relationship[$target_column]][] = $relationship[$list_column];
|
||||
}
|
||||
|
||||
foreach ($grouped as $target => $list) {
|
||||
$changed[] = dbSyncRelationship($table, $target_column, $target, $list_column, $list);
|
||||
}
|
||||
|
||||
return [array_sum(array_column($changed, 0)), array_sum(array_column($changed, 1))];
|
||||
}
|
||||
|
@ -33,6 +33,22 @@ alerts:
|
||||
unique_alert: { Name: unique_alert, Columns: [device_id, rule_id], Unique: true, Type: BTREE }
|
||||
rule_id: { Name: rule_id, Columns: [rule_id], Unique: false, Type: BTREE }
|
||||
device_id: { Name: device_id, Columns: [device_id], Unique: false, Type: BTREE }
|
||||
alert_device_map:
|
||||
Columns:
|
||||
- { Field: id, Type: int(11), 'Null': false, Extra: auto_increment }
|
||||
- { Field: rule_id, Type: int(11), 'Null': false, Extra: '' }
|
||||
- { Field: device_id, Type: int(11), 'Null': false, Extra: '' }
|
||||
Indexes:
|
||||
PRIMARY: { Name: PRIMARY, Columns: [id], Unique: true, Type: BTREE }
|
||||
alert_device_map_rule_id_device_id_uindex: { Name: alert_device_map_rule_id_device_id_uindex, Columns: [rule_id, device_id], Unique: true, Type: BTREE }
|
||||
alert_group_map:
|
||||
Columns:
|
||||
- { Field: id, Type: int(11), 'Null': false, Extra: auto_increment }
|
||||
- { Field: rule_id, Type: int(11), 'Null': false, Extra: '' }
|
||||
- { Field: group_id, Type: int(11), 'Null': false, Extra: '' }
|
||||
Indexes:
|
||||
PRIMARY: { Name: PRIMARY, Columns: [id], Unique: true, Type: BTREE }
|
||||
alert_group_map_rule_id_group_id_uindex: { Name: alert_group_map_rule_id_group_id_uindex, Columns: [rule_id, group_id], Unique: true, Type: BTREE }
|
||||
alert_log:
|
||||
Columns:
|
||||
- { Field: id, Type: int(11), 'Null': false, Extra: auto_increment }
|
||||
@ -46,28 +62,20 @@ alert_log:
|
||||
rule_id: { Name: rule_id, Columns: [rule_id], Unique: false, Type: BTREE }
|
||||
device_id: { Name: device_id, Columns: [device_id], Unique: false, Type: BTREE }
|
||||
time_logged: { Name: time_logged, Columns: [time_logged], Unique: false, Type: BTREE }
|
||||
alert_map:
|
||||
Columns:
|
||||
- { Field: id, Type: int(11), 'Null': false, Extra: auto_increment }
|
||||
- { Field: rule, Type: int(11), 'Null': false, Extra: '', Default: '0' }
|
||||
- { Field: target, Type: varchar(255), 'Null': false, Extra: '', Default: '' }
|
||||
Indexes:
|
||||
PRIMARY: { Name: PRIMARY, Columns: [id], Unique: true, Type: BTREE }
|
||||
alert_rules:
|
||||
Columns:
|
||||
- { Field: id, Type: int(11), 'Null': false, Extra: auto_increment }
|
||||
- { Field: device_id, Type: varchar(255), 'Null': false, Extra: '', Default: '' }
|
||||
- { Field: rule, Type: text, 'Null': false, Extra: '' }
|
||||
- { Field: severity, Type: 'enum(''ok'',''warning'',''critical'')', 'Null': false, Extra: '' }
|
||||
- { Field: extra, Type: varchar(255), 'Null': false, Extra: '' }
|
||||
- { Field: disabled, Type: tinyint(1), 'Null': false, Extra: '' }
|
||||
- { Field: name, Type: varchar(255), 'Null': false, Extra: '' }
|
||||
- { Field: query, Type: text, 'Null': false, Extra: '' }
|
||||
- { Field: builder, Type: text, 'Null': false, Extra: '' }
|
||||
- { Field: proc, Type: varchar(80), 'Null': true, Extra: '' }
|
||||
Indexes:
|
||||
PRIMARY: { Name: PRIMARY, Columns: [id], Unique: true, Type: BTREE }
|
||||
name: { Name: name, Columns: [name], Unique: true, Type: BTREE }
|
||||
device_id: { Name: device_id, Columns: [device_id], Unique: false, Type: BTREE }
|
||||
alert_schedule:
|
||||
Columns:
|
||||
- { Field: schedule_id, Type: int(11), 'Null': false, Extra: auto_increment }
|
||||
@ -223,7 +231,7 @@ bills:
|
||||
- { Field: bill_notes, Type: varchar(256), 'Null': false, Extra: '' }
|
||||
- { Field: bill_autoadded, Type: tinyint(1), 'Null': false, Extra: '' }
|
||||
Indexes:
|
||||
bill_id: { Name: bill_id, Columns: [bill_id], Unique: true, Type: BTREE }
|
||||
PRIMARY: { Name: PRIMARY, Columns: [bill_id], Unique: true, Type: BTREE }
|
||||
bill_data:
|
||||
Columns:
|
||||
- { Field: bill_id, Type: int(11), 'Null': false, Extra: '' }
|
||||
|
35
misc/macros.json
Normal file
35
misc/macros.json
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
"now": "NOW()",
|
||||
"past_5m": "DATE_SUB(NOW(),INTERVAL 5 MINUTE)",
|
||||
"past_10m": "DATE_SUB(NOW(),INTERVAL 10 MINUTE)",
|
||||
"past_15m": "DATE_SUB(NOW(),INTERVAL 15 MINUTE)",
|
||||
"past_30m": "DATE_SUB(NOW(),INTERVAL 30 MINUTE)",
|
||||
"device": "(%devices.disabled = 0 && %devices.ignore = 0)",
|
||||
"device_up": "(%devices.status = 1 && %macros.device)",
|
||||
"device_down": "(%devices.status = 0 && %macros.device)",
|
||||
"port": "(%ports.deleted = 0 && %ports.ignore = 0 && %ports.disabled = 0)",
|
||||
"port_up": "(%ports.ifOperStatus = \"up\" && %ports.ifAdminStatus = \"up\" && %macros.port)",
|
||||
"past_60m": "DATE_SUB(NOW(),INTERVAL 60 MINUTE)",
|
||||
"port_usage_perc": "((%ports.ifInOctets_rate*8) \/ %ports.ifSpeed)*100",
|
||||
"port_down": "(%ports.ifOperStatus = \"down\" && %ports.ifAdminStatus != \"down\" && %macros.port)",
|
||||
"sensor": "(%sensors.sensor_alert = 1)",
|
||||
"packet_loss_15m": "(%macros.past_15m && %device_perf.loss)",
|
||||
"packet_loss_5m": "(%macros.past_5m && %device_perf.loss)",
|
||||
"component": "(%component.disabled = 0 && %component.ignore = 0)",
|
||||
"component_warning": "(%component.status = 1 && %macros.component)",
|
||||
"component_normal": "(%component.status = 0 && %macros.component)",
|
||||
"port_in_usage_perc": "((%ports.ifInOctets_rate*8) \/ %ports.ifSpeed)*100",
|
||||
"port_out_usage_perc": "((%ports.ifOutOctets_rate*8) \/ %ports.ifSpeed)*100",
|
||||
"port_now_down": "%ports.ifOperStatus != %ports.ifOperStatus_prev && %ports.ifOperStatus_prev = \"up\" && %ports.ifAdminStatus = \"up\"",
|
||||
"device_component_down_junos": "%sensors.sensor_class = \"state\" && %sensors.sensor_current != \"6\" && %sensors.sensor_type = \"jnxFruState\" && %sensors.sensor_current != \"2\" && %sensors.sensor_alert = \"1\"",
|
||||
"device_component_down_cisco": "%sensors.sensor_current != \"1\" && %sensors.sensor_current != \"5\" && %sensors.sensor_type ~ \"^cisco.*State$\" && %sensors.sensor_alert = \"1\"",
|
||||
"pdu_over_amperage_apc": "%sensors.sensor_class = \"current\" && %sensors.sensor_descr = \"Bank Total\" && %sensors.sensor_current > %sensors.sensor_limit && %devices.os = \"apc\"",
|
||||
"component_critical": "(%component.status = 2 && %macros.component)",
|
||||
"bill_quota_over_quota": "((%bills.total_data \/ %bills.bill_quota)*100) && %bills.bill_type = \"quota\"",
|
||||
"bill_cdr_over_quota": "((%bills.rate_95th \/ %bills.bill_cdr)*100) && %bills.bill_type = \"cdr\"",
|
||||
"sensor_port_link": "(%sensors.entPhysicalIndex_measured = 'ports' && %sensors.entPhysicalIndex = %ports.ifIndex && %macros.port_up)",
|
||||
"state_sensor_ok": "%sensors.sensor_current = %state_translations.state_value && %state_translations.state_generic_value = 0",
|
||||
"state_sensor_warning": "%sensors.sensor_current = %state_translations.state_value && %state_translations.state_generic_value = 1",
|
||||
"state_sensor_critical": "%sensors.sensor_current = %state_translations.state_value && %state_translations.state_generic_value = 2",
|
||||
"state_sensor_unknown": "%sensors.sensor_current = %state_translations.state_value && %state_translations.state_generic_value = 3"
|
||||
}
|
@ -105,7 +105,6 @@ pages:
|
||||
- Alerting/Entities.md
|
||||
- Alerting/Macros.md
|
||||
- Alerting/Testing.md
|
||||
- Alerting/Rule-Mapping.md
|
||||
- Alerting/Device-Dependencies.md
|
||||
- 9. Getting help:
|
||||
- How to get help: Support/index.md
|
||||
|
@ -8,10 +8,8 @@ if [ "$EXECUTE_BUILD_DOCS" != "true" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
pip install --user --quiet 'jinja2<2.9'
|
||||
pip install --user --quiet mkdocs
|
||||
pip install --user --quiet pymdown-extensions
|
||||
pip install --user --quiet git+git://github.com/aleray/mdx_del_ins.git
|
||||
pip install --user 'jinja2<2.9' mkdocs pymdown-extensions
|
||||
pip install --user git+git://github.com/aleray/mdx_del_ins.git
|
||||
|
||||
mkdir -p out
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
||||
insert into `config` (`config_name`,`config_value`,`config_default`,`config_descr`,`config_group`,`config_group_order`,`config_sub_group`,`config_sub_group_order`,`config_hidden`,`config_disabled`) values ('alert.macros.rule.sensor','(%sensors.sensor_alert = 1)','(%sensors.sensor_alert = 1)','Sensors of interest','alerting',0,'macros',0,'1','0');
|
||||
|
@ -1,5 +1,3 @@
|
||||
CREATE TABLE IF NOT EXISTS `device_perf` ( `id` int(11) NOT NULL AUTO_INCREMENT, `device_id` int(11) NOT NULL, `timestamp` datetime NOT NULL, `xmt` float NOT NULL, `rcv` float NOT NULL, `loss` float NOT NULL, `min` float NOT NULL, `max` float NOT NULL, `avg` float NOT NULL, KEY `id` (`id`,`device_id`)) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
|
||||
insert into config (`config_name`,`config_value`,`config_default`,`config_descr`,`config_group`,`config_group_order`,`config_sub_group`,`config_sub_group_order`,`config_hidden`,`config_disabled`) values ('alert.macros.rule.packet_loss_15m','(%macros.past_15m && %device_perf.loss)','(%macros.past_15m && %device_perf.loss)','Packet loss over the last 15 minutes','alerting',0,'macros',0,'1','0');
|
||||
insert into config (`config_name`,`config_value`,`config_default`,`config_descr`,`config_group`,`config_group_order`,`config_sub_group`,`config_sub_group_order`,`config_hidden`,`config_disabled`) values ('alert.macros.rule.packet_loss_5m','(%macros.past_5m && %device_perf.loss)','(%macros.past_5m && %device_perf.loss)','Packet loss over the last 5 minutes','alerting',0,'macros',0,'1','0');
|
||||
ALTER TABLE `devices` ADD `status_reason` VARCHAR( 50 ) NOT NULL AFTER `status` ;
|
||||
UPDATE `devices` SET `status_reason`='down' WHERE `status`=0;
|
||||
|
@ -2,4 +2,3 @@ DROP TABLE IF EXISTS `component`;
|
||||
CREATE TABLE `component` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID for each component, unique index', `device_id` int(11) unsigned NOT NULL COMMENT 'device_id from the devices table', `type` varchar(50) COLLATE utf8_unicode_ci NOT NULL COMMENT 'name from the component_type table', `label` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT 'Display label for the component', `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'The status of the component, retreived from the device', `disabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Should this component be polled', `ignore` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Should this component be alerted on', `error` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT 'Error message if in Alert state', PRIMARY KEY (`id`), KEY `device` (`device_id`), KEY `type` (`type`) ) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='components attached to a device.';
|
||||
DROP TABLE IF EXISTS `component_prefs`;
|
||||
CREATE TABLE `component_prefs` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID for each entry', `component` int(11) unsigned NOT NULL COMMENT 'id from the component table', `attribute` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT 'Attribute for the Component', `value` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT 'Value for the Component', PRIMARY KEY (`id`), KEY `component` (`component`), CONSTRAINT `component_prefs_ibfk_1` FOREIGN KEY (`component`) REFERENCES `component` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='AV Pairs for each component';
|
||||
INSERT INTO `config` (`config_name`,`config_value`,`config_default`,`config_descr`,`config_group`,`config_group_order`,`config_sub_group`,`config_sub_group_order`,`config_hidden`,`config_disabled`) VALUES ('alert.macros.rule.component','(%component.disabled = 0 && %component.ignore = 0)','(%component.disabled = 0 && %component.ignore = 0)','Component that isn\'t disabled or ignored','alerting',0,'macros',0,'1','0'),('alert.macros.rule.component_normal','(%component.status = 1 && %macros.component)','(%component.status = 1 && %macros.component)','Component that is in a normal state','alerting',0,'macros',0,'1','0'),('alert.macros.rule.component_alert','(%component.status = 0 && %macros.component)','(%component.status = 0 && %macros.component)','Component that alerting','alerting',0,'macros',0,'1','0');
|
||||
|
@ -1,9 +1,3 @@
|
||||
INSERT INTO `config` (`config_name`,`config_value`,`config_default`,`config_descr`,`config_group`,`config_group_order`,`config_sub_group`,`config_sub_group_order`,`config_hidden`,`config_disabled`) VALUES ('alert.macros.rule.port_in_usage_perc','((%ports.ifInOctets_rate*8)/%ports.ifSpeed)*100','((%ports.ifInOctets_rate*8)/%ports.ifSpeed)*100','Ports using more than X perc of capacity IN','alerting',0,'macros',0,'1','0');
|
||||
INSERT INTO `config` (`config_name`,`config_value`,`config_default`,`config_descr`,`config_group`,`config_group_order`,`config_sub_group`,`config_sub_group_order`,`config_hidden`,`config_disabled`) VALUES ('alert.macros.rule.port_out_usage_perc','((%ports.ifOutOctets_rate*8)/%ports.ifSpeed)*100','((%ports.ifOutOctets_rate*8)/%ports.ifSpeed)*100','Ports using more than X perc of capacity OUT','alerting',0,'macros',0,'1','0');
|
||||
INSERT INTO `config` (`config_name`,`config_value`,`config_default`,`config_descr`,`config_group`,`config_group_order`,`config_sub_group`,`config_sub_group_order`,`config_hidden`,`config_disabled`) VALUES ('alert.macros.rule.port_now_down','%ports.ifOperStatus != %ports.ifOperStatus_prev && %ports.ifOperStatus_prev = "up" && %ports.ifAdminStatus = "up"','%ports.ifOperStatus != %ports.ifOperStatus_prev && %ports.ifOperStatus_prev = "up" && %ports.ifAdminStatus = "up"','Port has gone down','alerting',0,'macros',0,'1','0');
|
||||
INSERT INTO `config` (`config_name`,`config_value`,`config_default`,`config_descr`,`config_group`,`config_group_order`,`config_sub_group`,`config_sub_group_order`,`config_hidden`,`config_disabled`) VALUES ('alert.macros.rule.device_component_down_junos','%sensors.sensor_class = "state" && %sensors.sensor_current != "6" && %sensors.sensor_type = "jnxFruState" && %sensors.sensor_current != "2"','%sensors.sensor_class = "state" && %sensors.sensor_current != "6" && %sensors.sensor_type = "jnxFruState" && %sensors.sensor_current != "2"','Device Component down [JunOS]','alerting',0,'macros',0,'1','0');
|
||||
INSERT INTO `config` (`config_name`,`config_value`,`config_default`,`config_descr`,`config_group`,`config_group_order`,`config_sub_group`,`config_sub_group_order`,`config_hidden`,`config_disabled`) VALUES ('alert.macros.rule.device_component_down_cisco','%sensors.sensor_current != "1" && %sensors.sensor_current != "5" && %sensors.sensor_type ~ "^cisco.*State$"','%sensors.sensor_current != "1" && %sensors.sensor_current != "5" && %sensors.sensor_type ~ "^cisco.*State$"','Device Component down [Cisco]','alerting',0,'macros',0,'1','0');
|
||||
INSERT INTO `config` (`config_name`,`config_value`,`config_default`,`config_descr`,`config_group`,`config_group_order`,`config_sub_group`,`config_sub_group_order`,`config_hidden`,`config_disabled`) VALUES ('alert.macros.rule.pdu_over_amperage_apc','%sensors.sensor_class = "current" && %sensors.sensor_descr = "Bank Total" && %sensors.sensor_current > %sensors.sensor_limit && %devices.os = "apc"','%sensors.sensor_class = "current" && %sensors.sensor_descr = "Bank Total" && %sensors.sensor_current > %sensors.sensor_limit && %devices.os = "apc"','PDU Over Amperage [APC]','alerting',0,'macros',0,'1','0');
|
||||
INSERT INTO `alert_templates` (`rule_id`, `name`, `template`, `title`, `title_rec`) VALUES (',','BGP Sessions.','%title\\r\\n\nSeverity: %severity\\r\\n\n{if %state == 0}Time elapsed: %elapsed\\r\\n{/if}\nTimestamp: %timestamp\\r\\n\nUnique-ID: %uid\\r\\n\nRule: {if %name}%name{else}%rule{/if}\\r\\n\n{if %faults}Faults:\\r\\n\n{foreach %faults}\n#%key: %value.string\\r\\n\nPeer: %value.astext\\r\\n\nPeer IP: %value.bgpPeerIdentifier\\r\\n\nPeer AS: %value.bgpPeerRemoteAs\\r\\n\nPeer EstTime: %value.bgpPeerFsmEstablishedTime\\r\\n\nPeer State: %value.bgpPeerState\\r\\n\n{/foreach}\n{/if}','','');
|
||||
INSERT INTO `alert_templates` (`rule_id`, `name`, `template`, `title`, `title_rec`) VALUES (',','Ports','%title\\r\\n\nSeverity: %severity\\r\\n\n{if %state == 0}Time elapsed: %elapsed{/if}\nTimestamp: %timestamp\nUnique-ID: %uid\nRule: {if %name}%name{else}%rule{/if}\\r\\n\n{if %faults}Faults:\\r\\n\n{foreach %faults}\\r\\n\n#%key: %value.string\\r\\n\nPort: %value.ifName\\r\\n\nPort Name: %value.ifAlias\\r\\n\nPort Status: %value.message\\r\\n\n{/foreach}\\r\\n\n{/if}\n','','');
|
||||
INSERT INTO `alert_templates` (`rule_id`, `name`, `template`, `title`, `title_rec`) VALUES (',','Temperature','%title\\r\\n\nSeverity: %severity\\r\\n\n{if %state == 0}Time elapsed: %elapsed{/if}\\r\\n\nTimestamp: %timestamp\\r\\n\nUnique-ID: %uid\\r\\n\nRule: {if %name}%name{else}%rule{/if}\\r\\n\n{if %faults}Faults:\\r\\n\n{foreach %faults}\\r\\n\n#%key: %value.string\\r\\n\nTemperature: %value.sensor_current\\r\\n\nPrevious Measurement: %value.sensor_prev\\r\\n\n{/foreach}\\r\\n\n{/if}','','');
|
||||
|
@ -3,7 +3,6 @@ CREATE TABLE `component_statuslog` ( `id` int(11) unsigned NOT NULL AUTO_INCREME
|
||||
ALTER TABLE `component` CHANGE `status` `status` TINYINT(1) NOT NULL DEFAULT '0' COMMENT 'The status of the component, retreived from the device';
|
||||
UPDATE `config` SET `config_name`='alert.macros.rule.component_warning',`config_descr`='Component status Warning' WHERE `config_name`='alert.macros.rule.component_normal';
|
||||
UPDATE `config` SET `config_name`='alert.macros.rule.component_normal',`config_descr`='Component status Normal' WHERE `config_name`='alert.macros.rule.component_alert';
|
||||
INSERT INTO `config` (`config_name`,`config_value`,`config_default`,`config_descr`,`config_group`,`config_group_order`,`config_sub_group`,`config_sub_group_order`,`config_hidden`,`config_disabled`) VALUES ('alert.macros.rule.component_critical','(%component.status = 2 && %macros.component)','(%component.status = 2 && %macros.component)','Component status Critical','alerting',0,'macros',0,'1','0');
|
||||
UPDATE component SET status=2 WHERE status=0;
|
||||
UPDATE component SET status=0 WHERE status=1;
|
||||
INSERT INTO `widgets` (`widget_title`,`widget`,`base_dimensions`) VALUES ('Component Status','component-status','3,2');
|
||||
|
@ -1,2 +0,0 @@
|
||||
INSERT INTO `config` (`config_name`,`config_value`,`config_default`,`config_descr`,`config_group`,`config_group_order`,`config_sub_group`,`config_sub_group_order`,`config_hidden`,`config_disabled`) VALUES ('alert.macros.rule.bill_quota_over_quota','((%bills.total_data/%bills.bill_quota) * 100) && %bills.bill_type = "quota"','((%bills.total_data/%bills.bill_quota) * 100) && %bills.bill_type = "quota"','Quota bills over X perc of quota','alerting',0,'macros',0,'1','0');
|
||||
INSERT INTO `config` (`config_name`,`config_value`,`config_default`,`config_descr`,`config_group`,`config_group_order`,`config_sub_group`,`config_sub_group_order`,`config_hidden`,`config_disabled`) VALUES ('alert.macros.rule.bill_cdr_over_quota','((%bills.rate_95th/%bills.bill_cdr) * 100) && %bills.bill_type = "cdr"','((%bills.rate_95th/%bills.bill_cdr) * 100) && %bills.bill_type = "cdr"','CDR bills over X perc of quota','alerting',0,'macros',0,'1','0');
|
||||
|
@ -1,2 +1 @@
|
||||
ALTER TABLE `sensors` ADD `entity_link_type` VARCHAR(32) NULL, ADD `entity_link_index` INT(11) UNSIGNED NOT NULL DEFAULT 0;
|
||||
INSERT INTO `config` (`config_name`,`config_value`,`config_default`,`config_descr`,`config_group`,`config_group_order`,`config_sub_group`,`config_sub_group_order`,`config_hidden`,`config_disabled`) VALUES ('alert.macros.rule.sensor_port_link',"(%sensors.entity_link_type = 'port' && %sensors.entity_link_index = %ports.ifIndex && %macros.port_up)","(%sensors.entity_link_type = 'port' && %sensors.entity_link_index = %ports.ifIndex && %macros.port_up)",'Sensors linked to port','alerting',0,'macros',0,'1','0');
|
||||
|
@ -1,4 +0,0 @@
|
||||
INSERT INTO `config` (`config_name`,`config_value`,`config_default`,`config_descr`,`config_group`,`config_group_order`,`config_sub_group`,`config_sub_group_order`,`config_hidden`,`config_disabled`) VALUES ('alert.macros.rule.state_sensor_ok','%sensors.sensor_current = %state_translations.state_value && %state_translations.state_generic_value = 0','%sensors.sensor_current = %state_translations.state_value && %state_translations.state_generic_value = 0','Ok state sensors','alerting',0,'macros',0,'1','0');
|
||||
INSERT INTO `config` (`config_name`,`config_value`,`config_default`,`config_descr`,`config_group`,`config_group_order`,`config_sub_group`,`config_sub_group_order`,`config_hidden`,`config_disabled`) VALUES ('alert.macros.rule.state_sensor_warning','%sensors.sensor_current = %state_translations.state_value && %state_translations.state_generic_value = 1','%sensors.sensor_current = %state_translations.state_value && %state_translations.state_generic_value = 1','Warning state sensors','alerting',0,'macros',0,'1','0');
|
||||
INSERT INTO `config` (`config_name`,`config_value`,`config_default`,`config_descr`,`config_group`,`config_group_order`,`config_sub_group`,`config_sub_group_order`,`config_hidden`,`config_disabled`) VALUES ('alert.macros.rule.state_sensor_critical','%sensors.sensor_current = %state_translations.state_value && %state_translations.state_generic_value = 2','%sensors.sensor_current = %state_translations.state_value && %state_translations.state_generic_value = 2','Critical state sensors','alerting',0,'macros',0,'1','0');
|
||||
INSERT INTO `config` (`config_name`,`config_value`,`config_default`,`config_descr`,`config_group`,`config_group_order`,`config_sub_group`,`config_sub_group_order`,`config_hidden`,`config_disabled`) VALUES ('alert.macros.rule.state_sensor_unknown','%sensors.sensor_current = %state_translations.state_value && %state_translations.state_generic_value = 3','%sensors.sensor_current = %state_translations.state_value && %state_translations.state_generic_value = 3','Unknown state sensors','alerting',0,'macros',0,'1','0');
|
||||
|
@ -1,4 +1,3 @@
|
||||
INSERT INTO `config` (`config_name`,`config_value`,`config_default`,`config_descr`,`config_group`,`config_group_order`,`config_sub_group`,`config_sub_group_order`,`config_hidden`,`config_disabled`) VALUES('alert.transports.hue.bridge','','','Philips Hue Bridge IP','alerting','0','transports','0','0','0');
|
||||
INSERT INTO `config` (`config_name`,`config_value`,`config_default`,`config_descr`,`config_group`,`config_group_order`,`config_sub_group`,`config_sub_group_order`,`config_hidden`,`config_disabled`) VALUES('alert.transports.hue.user','','','Philips Hue User','alerting','0','transports','0','0','0');
|
||||
INSERT INTO `config` (`config_name`,`config_value`,`config_default`,`config_descr`,`config_group`,`config_group_order`,`config_sub_group`,`config_sub_group_order`,`config_hidden`,`config_disabled`) VALUES('alert.transports.hue.duration','','lselect','Philips Hue Alert Duration','alerting','0','transports','0','0','0');
|
||||
|
||||
|
1
sql-schema/241.sql
Normal file
1
sql-schema/241.sql
Normal file
@ -0,0 +1 @@
|
||||
ALTER TABLE `services` MODIFY `service_ds` TEXT NOT NULL;
|
11
sql-schema/242.sql
Normal file
11
sql-schema/242.sql
Normal file
@ -0,0 +1,11 @@
|
||||
ALTER TABLE `alert_rules` ADD `query_builder` TEXT NOT NULL AFTER `query`;
|
||||
CREATE TABLE `alert_group_map` (`id` INT PRIMARY KEY AUTO_INCREMENT, `rule_id` INT NOT NULL, `group_id` INT NOT NULL);
|
||||
CREATE UNIQUE INDEX `alert_group_map_rule_id_group_id_uindex` ON `alert_group_map` (`rule_id`, `group_id`);
|
||||
INSERT INTO `alert_group_map` (`rule_id`, `group_id`) SELECT `rule`, SUBSTRING(`target`, 2) as `group_id` FROM `alert_map` WHERE `target` LIKE 'g%';
|
||||
DELETE FROM `alert_map` WHERE `target` LIKE 'g%';
|
||||
ALTER TABLE `alert_map` CHANGE `rule` `rule_id` INT(11) NOT NULL;
|
||||
ALTER TABLE `alert_map` CHANGE `target` `device_id` INT(11) NOT NULL;
|
||||
ALTER TABLE `alert_map` RENAME TO `alert_device_map`;
|
||||
CREATE UNIQUE INDEX `alert_device_map_rule_id_device_id_uindex` ON `alert_device_map` (`rule_id`, `device_id`);
|
||||
INSERT INTO `alert_device_map` (`rule_id`, `device_id`) SELECT `id`, `device_id` FROM `alert_rules` WHERE `device_id` != -1;
|
||||
ALTER TABLE `alert_rules` DROP COLUMN `device_id`;
|
2
sql-schema/243.sql
Normal file
2
sql-schema/243.sql
Normal file
@ -0,0 +1,2 @@
|
||||
ALTER TABLE alert_rules DROP query_builder;
|
||||
ALTER TABLE alert_rules ADD builder TEXT NOT NULL AFTER query;
|
1
sql-schema/244.sql
Normal file
1
sql-schema/244.sql
Normal file
@ -0,0 +1 @@
|
||||
ALTER TABLE bills DROP INDEX `bill_id`, ADD PRIMARY KEY (bill_id);
|
67
tests/QueryBuilderTest.php
Normal file
67
tests/QueryBuilderTest.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
/**
|
||||
* QueryBuilderTest.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 2018 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace LibreNMS\Tests;
|
||||
|
||||
use LibreNMS\Alerting\QueryBuilderParser;
|
||||
use LibreNMS\Config;
|
||||
|
||||
class QueryBuilderTest extends TestCase
|
||||
{
|
||||
private $data_file = 'tests/data/misc/querybuilder.json';
|
||||
|
||||
public function testHasQueryData()
|
||||
{
|
||||
$this->assertNotEmpty(
|
||||
$this->loadQueryData(),
|
||||
"Could not load query builder test data from $this->data_file"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @dataProvider loadQueryData
|
||||
* @param string $legacy
|
||||
* @param array $builder
|
||||
* @param string $display
|
||||
* @param string $sql
|
||||
*/
|
||||
public function testQueryConversion($legacy, $builder, $display, $sql)
|
||||
{
|
||||
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());
|
||||
}
|
||||
|
||||
public function loadQueryData()
|
||||
{
|
||||
$base = Config::get('install_dir');
|
||||
$data = file_get_contents("$base/$this->data_file");
|
||||
return json_decode($data, true);
|
||||
}
|
||||
}
|
150
tests/SchemaTest.php
Normal file
150
tests/SchemaTest.php
Normal file
@ -0,0 +1,150 @@
|
||||
<?php
|
||||
/**
|
||||
* SchemaTest.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 2018 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace LibreNMS\Tests;
|
||||
|
||||
use LibreNMS\DB\Schema;
|
||||
|
||||
class SchemaTest extends TestCase
|
||||
{
|
||||
private $mock_schema = [
|
||||
"bills" => [
|
||||
"Columns" => [
|
||||
["Field" => "bill_id", "Type" => "int(11)", "Null" => false, "Extra" => "auto_increment"],
|
||||
],
|
||||
"Indexes" => ["bill_id" => ["Name" => "bill_id", "Columns" => ["bill_id"], "Unique" => true, "Type" => "BTREE"]]
|
||||
],
|
||||
"bill_ports" => [
|
||||
"Columns" => [
|
||||
["Field" => "bill_id", "Type" => "int(11)", "Null" => false, "Extra" => ""],
|
||||
["Field" => "port_id", "Type" => "int(11)", "Null" => false, "Extra" => ""],
|
||||
]
|
||||
],
|
||||
"devices" => [
|
||||
"Columns" => [
|
||||
["Field" => "device_id", "Type" => "int(11) unsigned", "Null" => false, "Extra" => "auto_increment"],
|
||||
],
|
||||
"Indexes" => [
|
||||
"PRIMARY" => ["Name" => "PRIMARY", "Columns" => ["device_id"], "Unique" => true, "Type" => "BTREE"],
|
||||
]
|
||||
],
|
||||
"ports" => [
|
||||
"Columns" => [
|
||||
["Field" => "port_id", "Type" => "int(11)", "Null" => false, "Extra" => "auto_increment"],
|
||||
["Field" => "device_id", "Type" => "int(11)", "Null" => false, "Extra" => "", "Default" => "0"],
|
||||
],
|
||||
"Indexes" => [
|
||||
"PRIMARY" => ["Name" => "PRIMARY", "Columns" => ["port_id"], "Unique" => true, "Type" => "BTREE"],
|
||||
]
|
||||
],
|
||||
"sensors" => [
|
||||
"Columns" => [
|
||||
["Field" => "sensor_id", "Type" => "int(11)", "Null" => false, "Extra" => "auto_increment"],
|
||||
["Field" => "device_id", "Type" => "int(11) unsigned", "Null" => false, "Extra" => "", "Default" => "0"],
|
||||
],
|
||||
"Indexes" => [
|
||||
"PRIMARY" => ["Name" => "PRIMARY", "Columns" => ["sensor_id"], "Unique" => true, "Type" => "BTREE"],
|
||||
]
|
||||
],
|
||||
"sensors_to_state_indexes" => [
|
||||
"Columns" => [
|
||||
["Field" => "sensors_to_state_translations_id", "Type" => "int(11)", "Null" => false, "Extra" => "auto_increment"],
|
||||
["Field" => "sensor_id", "Type" => "int(11)", "Null" => false, "Extra" => ""],
|
||||
["Field" => "state_index_id", "Type" => "int(11)", "Null" => false, "Extra" => ""]
|
||||
],
|
||||
"Indexes" => [
|
||||
"PRIMARY" => ["Name" => "PRIMARY", "Columns" => ["sensors_to_state_translations_id"], "Unique" => true, "Type" => "BTREE"],
|
||||
]
|
||||
],
|
||||
"state_indexes" => [
|
||||
"Columns" => [
|
||||
["Field" => "state_index_id", "Type" => "int(11)", "Null" => false, "Extra" => "auto_increment"],
|
||||
],
|
||||
"Indexes" => [
|
||||
"PRIMARY" => ["Name" => "PRIMARY", "Columns" => ["state_index_id"], "Unique" => true, "Type" => "BTREE"],
|
||||
]
|
||||
],
|
||||
"state_translations" => [
|
||||
"Columns" => [
|
||||
["Field" => "state_translation_id", "Type" => "int(11)", "Null" => false, "Extra" => "auto_increment"],
|
||||
["Field" => "state_index_id", "Type" => "int(11)", "Null" => false, "Extra" => ""],
|
||||
],
|
||||
"Indexes" => [
|
||||
"PRIMARY" => ["Name" => "PRIMARY", "Columns" => ["state_translation_id"], "Unique" => true, "Type" => "BTREE"],
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
/**
|
||||
* @return Schema
|
||||
*/
|
||||
private function getSchemaMock()
|
||||
{
|
||||
// use a Mock so we don't have to rely on the schema being stable.
|
||||
|
||||
$schema = $this->getMockBuilder(Schema::class)
|
||||
->setMethods(['getSchema'])
|
||||
->getMock();
|
||||
|
||||
$schema->method('getSchema')->willReturn($this->mock_schema);
|
||||
|
||||
/** @var $schema Schema Mock of Schema */
|
||||
return $schema;
|
||||
}
|
||||
|
||||
|
||||
public function testTableRelationships()
|
||||
{
|
||||
// mock getSchema
|
||||
$schema = $this->getSchemaMock();
|
||||
|
||||
$expected = [
|
||||
'bills' => [],
|
||||
'bill_ports' => ['bills', 'ports'],
|
||||
'devices' => [],
|
||||
'ports' => ['devices'],
|
||||
'sensors' => ['devices'],
|
||||
'sensors_to_state_indexes' => ['sensors', 'state_indexes'],
|
||||
'state_indexes' => [],
|
||||
'state_translations' => ['state_indexes'],
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $schema->getTableRelationships());
|
||||
}
|
||||
|
||||
public function testFindRelationshipPath()
|
||||
{
|
||||
$schema = $this->getSchemaMock();
|
||||
|
||||
$this->assertEquals(['devices'], $schema->findRelationshipPath('devices'));
|
||||
$this->assertEquals(['devices', 'ports'], $schema->findRelationshipPath('ports'));
|
||||
$this->assertEquals(['devices', 'ports', 'bill_ports'], $schema->findRelationshipPath('bill_ports'));
|
||||
$this->assertEquals(['devices', 'ports', 'bill_ports', 'bills'], $schema->findRelationshipPath('bills'));
|
||||
$this->assertEquals(
|
||||
['devices', 'sensors', 'sensors_to_state_indexes', 'state_indexes', 'state_translations'],
|
||||
$schema->findRelationshipPath('state_translations')
|
||||
);
|
||||
}
|
||||
}
|
38
tests/data/misc/querybuilder.json
Normal file
38
tests/data/misc/querybuilder.json
Normal file
@ -0,0 +1,38 @@
|
||||
[
|
||||
[
|
||||
"%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"
|
||||
],
|
||||
[
|
||||
"%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"
|
||||
],
|
||||
[
|
||||
"%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.*\""
|
||||
],
|
||||
[
|
||||
"%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"
|
||||
],
|
||||
[
|
||||
"",
|
||||
{"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"
|
||||
],
|
||||
[
|
||||
"%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\""
|
||||
]
|
||||
]
|
Reference in New Issue
Block a user