mirror of
https://github.com/librenms/librenms.git
synced 2024-10-07 16:52:45 +00:00
Added ability to set a custom SQL query for alert rules. (#9094)
* Added support for AVG in rules * More updates * Final work to have advanced sql queries * Added missing use * Fix exception when invalid json is passed * Fixed api for alert rules * updated docs
This commit is contained in:
committed by
Tony Murray
parent
53a1730fc7
commit
466b5a35a8
@@ -146,7 +146,7 @@ class QueryBuilderParser implements \JsonSerializable
|
||||
public static function fromJson($json)
|
||||
{
|
||||
if (!is_array($json)) {
|
||||
$json = json_decode($json, true);
|
||||
$json = json_decode($json, true) ?: [];
|
||||
}
|
||||
|
||||
return new static($json);
|
||||
|
@@ -84,7 +84,6 @@ Output:
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"message": "",
|
||||
"message": "Alert has been unmuted"
|
||||
}
|
||||
```
|
||||
@@ -243,8 +242,8 @@ Route: `/api/v0/rules`
|
||||
|
||||
Input (JSON):
|
||||
|
||||
- device_id: This is either the device id or -1 for a global rule
|
||||
- rule: The rule which should be in the format %entity $condition $value (i.e %devices.status != 0 for devices marked as down).
|
||||
- devices: This is either an array of device ids or -1 for a global rule
|
||||
- builder: The rule which should be in the format entity.condition value (i.e devices.status != 0 for devices marked as down). It must be json encoded in the format rules are currently stored.
|
||||
- severity: The severity level the alert will be raised against, Ok, Warning, Critical.
|
||||
- disabled: Whether the rule will be disabled or not, 0 = enabled, 1 = disabled
|
||||
- count: This is how many polling runs before an alert will trigger and the frequency.
|
||||
@@ -255,7 +254,7 @@ Input (JSON):
|
||||
|
||||
Example:
|
||||
```curl
|
||||
curl -X POST -d '{"device_id":"-1", "rule":"%devices.os != \"Cisco\"","severity": "critical","count":15,"delay":"5 m","mute":false}' -H 'X-Auth-Token: YOURAPITOKENHERE' https://librenms.org/api/v0/rules
|
||||
curl -X POST -d '{"device_id":[1,2,3], "name": "testrule", builder":"{\"condition\":\"AND\",\"rules\":[{\"id\":\"devices.hostname\",\"field\":\"devices.hostname\",\"type\":\"string\",\"input\":\"text\",\"operator\":\"equal\",\"value\":\"localhost\"}],\"valid\":true}","severity": "critical","count":15,"delay":"5 m","mute":false}' -H 'X-Auth-Token: YOURAPITOKENHERE' https://librenms.org/api/v0/rules
|
||||
```
|
||||
|
||||
Output:
|
||||
@@ -278,8 +277,8 @@ Route: `/api/v0/rules`
|
||||
Input (JSON):
|
||||
|
||||
- rule_id: You must specify the rule_id to edit an existing rule, if this is absent then a new rule will be created.
|
||||
- device_id: This is either the device id or -1 for a global rule
|
||||
- rule: The rule which should be in the format %entity $condition $value (i.e %devices.status != 0 for devices marked as down).
|
||||
- devices: This is either an array of device ids or -1 for a global rule
|
||||
- builder: The rule which should be in the format entity.condition value (i.e devices.status != 0 for devices marked as down). It must be json encoded in the format rules are currently stored.
|
||||
- severity: The severity level the alert will be raised against, Ok, Warning, Critical.
|
||||
- disabled: Whether the rule will be disabled or not, 0 = enabled, 1 = disabled
|
||||
- count: This is how many polling runs before an alert will trigger and the frequency.
|
||||
@@ -290,7 +289,7 @@ Input (JSON):
|
||||
|
||||
Example:
|
||||
```curl
|
||||
curl -X PUT -d '{"rule_id":1,"device_id":"-1", "rule":"%devices.os != \"Cisco\"","severity": "critical","count":15,"delay":"5 m","mute":false}' -H 'X-Auth-Token: YOURAPITOKENHERE' https://librenms.org/api/v0/rules
|
||||
curl -X PUT -d '{"rule_id":1,"device_id":"-1", "name": "testrule", "builder":"{\"condition\":\"AND\",\"rules\":[{\"id\":\"devices.hostname\",\"field\":\"devices.hostname\",\"type\":\"string\",\"input\":\"text\",\"operator\":\"equal\",\"value\":\"localhost\"}],\"valid\":true}","severity": "critical","count":15,"delay":"5 m","mute":false}' -H 'X-Auth-Token: YOURAPITOKENHERE' https://librenms.org/api/v0/rules
|
||||
```
|
||||
|
||||
Output:
|
||||
|
@@ -60,6 +60,17 @@ Here are some of the other options available when adding an alerting rule:
|
||||
- Invert match: Invert the matching rule (ie. alert on items that _don't_ match the rule).
|
||||
- Recovery alerts: This will disable the recovery notification from being sent if turned off.
|
||||
|
||||
# Advanced
|
||||
|
||||
On the Advanced tab, you can specify some additional options for the alert rule:
|
||||
|
||||
- Override SQL: Enable this if you using a custom query
|
||||
- Query: The query to be used for the alert.
|
||||
|
||||
> An example of this would be an average rule for all CPUs over 10%:
|
||||
> SELECT *,AVG(processors.processor_usage) as cpu_avg FROM devices,processors WHERE (devices.device_id = ? AND devices.device_id = processors.device_id) AND (devices.status = 1 && (devices.disabled = 0 && devices.ignore = 0)) = 1 HAVING AVG(processors.processor_usage) > 10
|
||||
> cpu_avg would then contain the average CPU usage value.
|
||||
|
||||
## Procedure
|
||||
You can associate a rule to a procedure by giving the URL of the procedure when creating the rule. Only links like "http://" are supported, otherwise an error will be returned. Once configured, procedure can be opened from the Alert widget through the "Open" button, which can be shown/hidden from the widget configuration box.
|
||||
|
||||
|
@@ -43,7 +43,7 @@ Placeholders are special variables that if used within the template will be repl
|
||||
- long uptime of the Device (28 days, 22h 30m 7s): `$alert->uptime_long`
|
||||
- description (purpose db field) of the Device: `$alert->description`
|
||||
- notes of the Device: `$alert->notes`
|
||||
- notes of the alert: `$alert->alert_notes`
|
||||
- notes of the alert (ack notes): `$alert->alert_notes`
|
||||
- ping timestamp (if icmp enabled): `$alert->ping_timestamp`
|
||||
- ping loss (if icmp enabled): `$alert->ping_loss`
|
||||
- ping min (if icmp enabled): `$alert->ping_min`
|
||||
|
@@ -13,6 +13,7 @@
|
||||
*/
|
||||
|
||||
use LibreNMS\Authentication\LegacyAuth;
|
||||
use LibreNMS\Alerting\QueryBuilderParser;
|
||||
|
||||
function authToken(\Slim\Route $route)
|
||||
{
|
||||
@@ -1063,20 +1064,28 @@ function add_edit_rule()
|
||||
check_is_admin();
|
||||
$app = \Slim\Slim::getInstance();
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
if (json_last_error()) {
|
||||
api_error(500, "We couldn't parse the provided json");
|
||||
}
|
||||
|
||||
$rule_id = mres($data['rule_id']);
|
||||
$device_id = mres($data['device_id']);
|
||||
if (empty($device_id) && !isset($rule_id)) {
|
||||
api_error(400, 'Missing the device id or global device id (-1)');
|
||||
$tmp_devices = (array)mres($data['devices']);
|
||||
$groups = (array)$data['groups'];
|
||||
if (empty($tmp_devices) && !isset($rule_id)) {
|
||||
api_error(400, 'Missing the devices or global device (-1)');
|
||||
}
|
||||
|
||||
if ($device_id == 0) {
|
||||
$device_id = '-1';
|
||||
$devices = [];
|
||||
foreach ($tmp_devices as $device) {
|
||||
if ($device == "-1") {
|
||||
continue;
|
||||
}
|
||||
$devices[] = ctype_digit($device) ? $device : getidbyname($device);
|
||||
}
|
||||
|
||||
$rule = $data['rule'];
|
||||
if (empty($rule)) {
|
||||
api_error(400, 'Missing the alert rule');
|
||||
$builder = $data['builder'] ?: $data['rule'];
|
||||
if (empty($builder)) {
|
||||
api_error(400, 'Missing the alert builder rule');
|
||||
}
|
||||
|
||||
$name = mres($data['name']);
|
||||
@@ -1102,6 +1111,8 @@ function add_edit_rule()
|
||||
$count = mres($data['count']);
|
||||
$mute = mres($data['mute']);
|
||||
$delay = mres($data['delay']);
|
||||
$override_query = $data['override_query'];
|
||||
$adv_query = $data['adv_query'];
|
||||
$delay_sec = convert_delay($delay);
|
||||
if ($mute == 1) {
|
||||
$mute = true;
|
||||
@@ -1109,31 +1120,46 @@ function add_edit_rule()
|
||||
$mute = false;
|
||||
}
|
||||
|
||||
$extra = array(
|
||||
$extra = [
|
||||
'mute' => $mute,
|
||||
'count' => $count,
|
||||
'delay' => $delay_sec,
|
||||
);
|
||||
'options' =>
|
||||
[
|
||||
'override_query' => $override_query
|
||||
],
|
||||
];
|
||||
$extra_json = json_encode($extra);
|
||||
|
||||
if ($override_query === 'on') {
|
||||
$query = $adv_query;
|
||||
} else {
|
||||
$query = QueryBuilderParser::fromJson($builder)->toSql();
|
||||
if (empty($query)) {
|
||||
api_error(500, "We couldn't parse your rule");
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($rule_id)) {
|
||||
if (dbFetchCell('SELECT `name` FROM `alert_rules` WHERE `name`=?', array($name)) == $name) {
|
||||
api_error(500, 'Addition failed : Name has already been used');
|
||||
}
|
||||
} else {
|
||||
if (dbFetchCell("SELECT name FROM alert_rules WHERE name=? AND id !=? ", array($name, $rule_id)) == $name) {
|
||||
api_error(500, 'Addition failed : Name has already been used');
|
||||
api_error(500, 'Update failed : Invalid rule id');
|
||||
}
|
||||
}
|
||||
|
||||
if (is_numeric($rule_id)) {
|
||||
if (!(dbUpdate(array('name' => $name, 'rule' => $rule, 'severity' => $severity, 'disabled' => $disabled, 'extra' => $extra_json), 'alert_rules', 'id=?', array($rule_id)) >= 0)) {
|
||||
if (!(dbUpdate(array('name' => $name, 'builder' => $builder, 'query' => $query, 'severity' => $severity, 'disabled' => $disabled, 'extra' => $extra_json), 'alert_rules', 'id=?', array($rule_id)) >= 0)) {
|
||||
api_error(500, 'Failed to update existing alert rule');
|
||||
}
|
||||
} elseif (!dbInsert(array('name' => $name, 'device_id' => $device_id, 'rule' => $rule, 'severity' => $severity, 'disabled' => $disabled, 'extra' => $extra_json), 'alert_rules')) {
|
||||
} elseif (!$rule_id = dbInsert(array('name' => $name, 'builder' => $builder, 'query' => $query, 'severity' => $severity, 'disabled' => $disabled, 'extra' => $extra_json), 'alert_rules')) {
|
||||
api_error(500, 'Failed to create new alert rule');
|
||||
}
|
||||
|
||||
dbSyncRelationship('alert_device_map', 'rule_id', $rule_id, 'device_id', $devices);
|
||||
dbSyncRelationship('alert_group_map', 'rule_id', $rule_id, 'group_id', $groups);
|
||||
api_success_noresult(200);
|
||||
}
|
||||
|
||||
|
@@ -38,8 +38,18 @@ if (!LegacyAuth::user()->hasGlobalAdmin()) {
|
||||
$status = 'ok';
|
||||
$message = '';
|
||||
|
||||
$builder_json = $_POST['builder_json'];
|
||||
$builder_json = $vars['builder_json'];
|
||||
$override_query = $vars['override_query'];
|
||||
|
||||
$options = [
|
||||
'override_query' => $override_query,
|
||||
];
|
||||
|
||||
if ($override_query === 'on') {
|
||||
$query = $vars['adv_query'];
|
||||
} else {
|
||||
$query = QueryBuilderParser::fromJson($builder_json)->toSql();
|
||||
}
|
||||
$rule_id = $_POST['rule_id'];
|
||||
$count = mres($_POST['count']);
|
||||
$delay = mres($_POST['delay']);
|
||||
@@ -79,6 +89,7 @@ $extra = array(
|
||||
'invert' => $invert,
|
||||
'interval' => $interval_sec,
|
||||
'recovery' => $recovery,
|
||||
'options' => $options,
|
||||
);
|
||||
|
||||
$extra_json = json_encode($extra);
|
||||
|
@@ -77,5 +77,6 @@ if (is_array($rule)) {
|
||||
'proc' => $rule['proc'],
|
||||
'builder' => $builder,
|
||||
'severity' => $rule['severity'],
|
||||
'adv_query' => $rule['query'],
|
||||
]);
|
||||
}
|
||||
|
@@ -29,7 +29,11 @@ if (LegacyAuth::user()->hasGlobalAdmin()) {
|
||||
<h5 class="modal-title" id="Create">Alert Rule :: <a href="https://docs.librenms.org/Alerting/"><i class="fa fa-book fa-1x"></i> Docs</a> </h5>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li role="presentation" class="active"><a href="#main" aria-controls="main" role="tab" data-toggle="tab">Main </a></li>
|
||||
<li role="presentation"><a href="#advanced" aria-controls="advanced" role="tab" data-toggle="tab">Advanced</a></li>
|
||||
</ul>
|
||||
<br />
|
||||
<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); ?>">
|
||||
@@ -37,6 +41,8 @@ if (LegacyAuth::user()->hasGlobalAdmin()) {
|
||||
<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="tab-content">
|
||||
<div role="tabpanel" class="tab-pane active" id="main">
|
||||
<div class='form-group' title="The description of this alert rule.">
|
||||
<label for='name' class='col-sm-3 col-md-2 control-label'>Rule name: </label>
|
||||
<div class='col-sm-9 col-md-10'>
|
||||
@@ -78,7 +84,7 @@ if (LegacyAuth::user()->hasGlobalAdmin()) {
|
||||
<div class="col-sm-2" title="How many notifications to issue while active before stopping. -1 means no limit. If interval is 0, this has no effect.">
|
||||
<input type='text' id='count' name='count' class='form-control' size="4" value="123">
|
||||
</div>
|
||||
<div class="col-sm-3" title="How long to wait before issuing a notification. If the alert clears before the delay, no notification will be issued. (s,m,h,d)">
|
||||
<div class="col-sm-3" title="How log to wait before issuing a notification. If the alert clears before the delay, no notification will be issued. (s,m,h,d)">
|
||||
<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>
|
||||
@@ -109,18 +115,28 @@ if (LegacyAuth::user()->hasGlobalAdmin()) {
|
||||
<select id="maps" name="maps[]" class="form-control" multiple="multiple"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" title="Restricts this alert rule to specified transports.">
|
||||
<label for="transports" class="col-sm-3 col-md-2 control-label">Transports: </label>
|
||||
<div class="col-sm-9 col-md-10">
|
||||
<select id="transports" name="transports[]" class="form-control" multiple="multiple"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class='form-group' title="A link to some documentation on how to handle this alert. This will be included in notifications.">
|
||||
<label for='proc' class='col-sm-3 col-md-2 control-label'>Procedure URL: </label>
|
||||
<div class='col-sm-9 col-md-10'>
|
||||
<input type='text' id='proc' name='proc' class='form-control validation' pattern='(http|https)://.*' maxlength='80'>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="advanced">
|
||||
<div class="form-group">
|
||||
<label for="override_query" class="col-sm-3 col-md-2 control-label">Override SQL</label>
|
||||
<div class="col-sm-9 col-md-10">
|
||||
<input type='checkbox' name='override_query' id='override_query'>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="adv_query" class="col-sm-3 col-md-2 control-label">Query</label>
|
||||
<div class="col-sm-9 col-md-10">
|
||||
<input type="text" id="adv_query" name="adv_query" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12 text-center">
|
||||
<button type="button" class="btn btn-success" id="btn-save" name="save-alert">
|
||||
@@ -269,15 +285,17 @@ if (LegacyAuth::user()->hasGlobalAdmin()) {
|
||||
$("#mute").bootstrapSwitch('state', false);
|
||||
$("#invert").bootstrapSwitch('state', false);
|
||||
$("#recovery").bootstrapSwitch('state', true);
|
||||
$("#override_query").bootstrapSwitch('state', false);
|
||||
$(this).find("input[type=text]").val("");
|
||||
$('#count').val('-1');
|
||||
$('#delay').val('1m');
|
||||
$('#interval').val('5m');
|
||||
$('#adv_query').val('');
|
||||
|
||||
var $maps = $('#maps');
|
||||
$maps.empty();
|
||||
$maps.val(null).trigger('change');
|
||||
setRuleDevice() // pre-populate device in the maps if this is a per-device rule
|
||||
setRuleDevice();// pre-populate device in the maps if this is a per-device rule
|
||||
|
||||
var $transports = $("#transports");
|
||||
$transports.empty();
|
||||
@@ -291,6 +309,7 @@ if (LegacyAuth::user()->hasGlobalAdmin()) {
|
||||
$('#proc').val(rule.proc);
|
||||
$('#builder').queryBuilder("setRules", rule.builder);
|
||||
$('#severity').val(rule.severity).trigger('change');
|
||||
$('#adv_query').val(rule.adv_query);
|
||||
|
||||
var $maps = $('#maps');
|
||||
$maps.empty();
|
||||
@@ -337,12 +356,20 @@ if (LegacyAuth::user()->hasGlobalAdmin()) {
|
||||
$('#interval').val(extra.interval);
|
||||
}
|
||||
|
||||
if (extra.adv_query) {
|
||||
$('#adv_query').val(extra.adv_query);
|
||||
}
|
||||
|
||||
$("[name='mute']").bootstrapSwitch('state', extra.mute);
|
||||
$("[name='invert']").bootstrapSwitch('state', extra.invert);
|
||||
if (typeof extra.recovery == 'undefined') {
|
||||
extra.recovery = true;
|
||||
}
|
||||
$("[name='recovery']").bootstrapSwitch('state', extra.recovery)
|
||||
if (typeof extra.options.override_query == 'undefined') {
|
||||
extra.options.override_query = false;
|
||||
}
|
||||
$("[name='recovery']").bootstrapSwitch('state', extra.recovery);
|
||||
$("[name='override_query']").bootstrapSwitch('state', extra.options.override_query);
|
||||
} else {
|
||||
$('#count').val('-1');
|
||||
}
|
||||
|
@@ -55,14 +55,21 @@ switch ($type) {
|
||||
$response = 'no match';
|
||||
}
|
||||
|
||||
if ($rule['builder']) {
|
||||
$extra = json_decode($rule['extra'], true);
|
||||
if ($extra['options']['override_query'] === 'on') {
|
||||
$qb = $extra['options']['override_query'];
|
||||
} elseif ($rule['builder']) {
|
||||
$qb = QueryBuilderParser::fromJson($rule['builder']);
|
||||
} else {
|
||||
$qb = QueryBuilderParser::fromOld($rule['rule']);
|
||||
}
|
||||
|
||||
$output .= 'Rule name: ' . $rule['name'] . PHP_EOL;
|
||||
if ($qb instanceof QueryBuilderParser) {
|
||||
$output .= 'Alert rule: ' . $qb->toSql(false) . PHP_EOL;
|
||||
} else {
|
||||
$output .= 'Alert rule: Custom SQL Query' . PHP_EOL;
|
||||
}
|
||||
$output .= 'Alert query: ' . $rule['query'] . PHP_EOL;
|
||||
$output .= 'Rule match: ' . $response . PHP_EOL . PHP_EOL;
|
||||
}
|
||||
|
@@ -177,6 +177,8 @@ foreach (dbFetchRows($full_query, $param) as $rule) {
|
||||
|
||||
if (empty($rule['builder'])) {
|
||||
$rule_display = $rule['rule'];
|
||||
} elseif ($rule_extra['options']['override_query'] === 'on') {
|
||||
$rule_display = 'Custom SQL Query';
|
||||
} else {
|
||||
$rule_display = QueryBuilderParser::fromJson($rule['builder'])->toSql(false);
|
||||
}
|
||||
|
Reference in New Issue
Block a user