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:
Neil Lathwood
2018-09-19 13:47:45 +01:00
committed by Tony Murray
parent 53a1730fc7
commit 466b5a35a8
10 changed files with 200 additions and 116 deletions

View File

@@ -146,7 +146,7 @@ class QueryBuilderParser implements \JsonSerializable
public static function fromJson($json) public static function fromJson($json)
{ {
if (!is_array($json)) { if (!is_array($json)) {
$json = json_decode($json, true); $json = json_decode($json, true) ?: [];
} }
return new static($json); return new static($json);
@@ -235,7 +235,7 @@ class QueryBuilderParser implements \JsonSerializable
$wrap = false; $wrap = false;
if ($expand) { if ($expand) {
$sql = 'SELECT * FROM ' . implode(',', $this->getTables()); $sql = 'SELECT * FROM ' .implode(',', $this->getTables());
$sql .= ' WHERE ' . $this->generateGlue() . ' AND '; $sql .= ' WHERE ' . $this->generateGlue() . ' AND ';
// only wrap in ( ) if the condition is OR and there is more than one rule // only wrap in ( ) if the condition is OR and there is more than one rule

View File

@@ -84,7 +84,6 @@ Output:
```json ```json
{ {
"status": "ok", "status": "ok",
"message": "",
"message": "Alert has been unmuted" "message": "Alert has been unmuted"
} }
``` ```
@@ -243,8 +242,8 @@ Route: `/api/v0/rules`
Input (JSON): Input (JSON):
- device_id: This is either the device id or -1 for a global rule - devices: This is either an array of device ids 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). - 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. - 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 - 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. - count: This is how many polling runs before an alert will trigger and the frequency.
@@ -255,7 +254,7 @@ Input (JSON):
Example: Example:
```curl ```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: Output:
@@ -278,8 +277,8 @@ Route: `/api/v0/rules`
Input (JSON): 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. - 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 - devices: This is either an array of device ids 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). - 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. - 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 - 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. - count: This is how many polling runs before an alert will trigger and the frequency.
@@ -290,7 +289,7 @@ Input (JSON):
Example: Example:
```curl ```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: Output:

View File

@@ -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). - 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. - 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 ## 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. 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.

View File

@@ -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` - long uptime of the Device (28 days, 22h 30m 7s): `$alert->uptime_long`
- description (purpose db field) of the Device: `$alert->description` - description (purpose db field) of the Device: `$alert->description`
- notes of the Device: `$alert->notes` - 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 timestamp (if icmp enabled): `$alert->ping_timestamp`
- ping loss (if icmp enabled): `$alert->ping_loss` - ping loss (if icmp enabled): `$alert->ping_loss`
- ping min (if icmp enabled): `$alert->ping_min` - ping min (if icmp enabled): `$alert->ping_min`

View File

@@ -13,6 +13,7 @@
*/ */
use LibreNMS\Authentication\LegacyAuth; use LibreNMS\Authentication\LegacyAuth;
use LibreNMS\Alerting\QueryBuilderParser;
function authToken(\Slim\Route $route) function authToken(\Slim\Route $route)
{ {
@@ -1063,20 +1064,28 @@ function add_edit_rule()
check_is_admin(); check_is_admin();
$app = \Slim\Slim::getInstance(); $app = \Slim\Slim::getInstance();
$data = json_decode(file_get_contents('php://input'), true); $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']); $rule_id = mres($data['rule_id']);
$device_id = mres($data['device_id']); $tmp_devices = (array)mres($data['devices']);
if (empty($device_id) && !isset($rule_id)) { $groups = (array)$data['groups'];
api_error(400, 'Missing the device id or global device id (-1)'); if (empty($tmp_devices) && !isset($rule_id)) {
api_error(400, 'Missing the devices or global device (-1)');
} }
if ($device_id == 0) { $devices = [];
$device_id = '-1'; foreach ($tmp_devices as $device) {
if ($device == "-1") {
continue;
}
$devices[] = ctype_digit($device) ? $device : getidbyname($device);
} }
$rule = $data['rule']; $builder = $data['builder'] ?: $data['rule'];
if (empty($rule)) { if (empty($builder)) {
api_error(400, 'Missing the alert rule'); api_error(400, 'Missing the alert builder rule');
} }
$name = mres($data['name']); $name = mres($data['name']);
@@ -1102,6 +1111,8 @@ function add_edit_rule()
$count = mres($data['count']); $count = mres($data['count']);
$mute = mres($data['mute']); $mute = mres($data['mute']);
$delay = mres($data['delay']); $delay = mres($data['delay']);
$override_query = $data['override_query'];
$adv_query = $data['adv_query'];
$delay_sec = convert_delay($delay); $delay_sec = convert_delay($delay);
if ($mute == 1) { if ($mute == 1) {
$mute = true; $mute = true;
@@ -1109,31 +1120,46 @@ function add_edit_rule()
$mute = false; $mute = false;
} }
$extra = array( $extra = [
'mute' => $mute, 'mute' => $mute,
'count' => $count, 'count' => $count,
'delay' => $delay_sec, 'delay' => $delay_sec,
); 'options' =>
[
'override_query' => $override_query
],
];
$extra_json = json_encode($extra); $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 (!isset($rule_id)) {
if (dbFetchCell('SELECT `name` FROM `alert_rules` WHERE `name`=?', array($name)) == $name) { if (dbFetchCell('SELECT `name` FROM `alert_rules` WHERE `name`=?', array($name)) == $name) {
api_error(500, 'Addition failed : Name has already been used'); api_error(500, 'Addition failed : Name has already been used');
} }
} else { } else {
if (dbFetchCell("SELECT name FROM alert_rules WHERE name=? AND id !=? ", array($name, $rule_id)) == $name) { 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 (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'); 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'); 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); api_success_noresult(200);
} }

View File

@@ -38,8 +38,18 @@ if (!LegacyAuth::user()->hasGlobalAdmin()) {
$status = 'ok'; $status = 'ok';
$message = ''; $message = '';
$builder_json = $_POST['builder_json']; $builder_json = $vars['builder_json'];
$query = QueryBuilderParser::fromJson($builder_json)->toSql(); $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']; $rule_id = $_POST['rule_id'];
$count = mres($_POST['count']); $count = mres($_POST['count']);
$delay = mres($_POST['delay']); $delay = mres($_POST['delay']);
@@ -79,6 +89,7 @@ $extra = array(
'invert' => $invert, 'invert' => $invert,
'interval' => $interval_sec, 'interval' => $interval_sec,
'recovery' => $recovery, 'recovery' => $recovery,
'options' => $options,
); );
$extra_json = json_encode($extra); $extra_json = json_encode($extra);

View File

@@ -70,12 +70,13 @@ if (is_array($rule)) {
header('Content-type: application/json'); header('Content-type: application/json');
echo json_encode([ echo json_encode([
'extra' => isset($rule['extra']) ? json_decode($rule['extra']) : null, 'extra' => isset($rule['extra']) ? json_decode($rule['extra']) : null,
'maps' => $maps, 'maps' => $maps,
'transports' => $transports, 'transports' => $transports,
'name' => $rule['name'], 'name' => $rule['name'],
'proc' => $rule['proc'], 'proc' => $rule['proc'],
'builder' => $builder, 'builder' => $builder,
'severity' => $rule['severity'], 'severity' => $rule['severity'],
'adv_query' => $rule['query'],
]); ]);
} }

View File

@@ -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> <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>
<div class="modal-body"> <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"> <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_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="device_name" id="device_name" value="<?php echo format_hostname($device); ?>">
@@ -37,88 +41,100 @@ if (LegacyAuth::user()->hasGlobalAdmin()) {
<input type="hidden" name="type" id="type" value="alert-rules"> <input type="hidden" name="type" id="type" value="alert-rules">
<input type="hidden" name="template_id" id="template_id" value=""> <input type="hidden" name="template_id" id="template_id" value="">
<input type="hidden" name="builder_json" id="builder_json" value=""> <input type="hidden" name="builder_json" id="builder_json" value="">
<div class='form-group' title="The description of this alert rule."> <div class="tab-content">
<label for='name' class='col-sm-3 col-md-2 control-label'>Rule name: </label> <div role="tabpanel" class="tab-pane active" id="main">
<div class='col-sm-9 col-md-10'> <div class='form-group' title="The description of this alert rule.">
<input type='text' id='name' name='name' class='form-control validation' maxlength='200' required> <label for='name' class='col-sm-3 col-md-2 control-label'>Rule name: </label>
</div> <div class='col-sm-9 col-md-10'>
</div> <input type='text' id='name' name='name' class='form-control validation' maxlength='200' required>
<div class="form-group"> </div>
<div class="col-sm-3 col-md-2"> </div>
<div class="dropdown"> <div class="form-group">
<button class="btn btn-default dropdown-toggle" type="button" <div class="col-sm-3 col-md-2">
id="import-from" data-toggle="dropdown" aria-haspopup="true" <div class="dropdown">
aria-expanded="true"> <button class="btn btn-default dropdown-toggle" type="button"
Import from id="import-from" data-toggle="dropdown" aria-haspopup="true"
<span class="caret"></span> aria-expanded="true">
</button> Import from
<ul class="dropdown-menu" aria-labelledby="import-from" id="import-dropdown"> <span class="caret"></span>
<li><a href="#" name="import-query" id="import-query">SQL Query</a></li> </button>
<li><a href="#" name="import-old-format" id="import-old-format">Old Format</a></li> <ul class="dropdown-menu" aria-labelledby="import-from" id="import-dropdown">
<li><a href="#" name="import-collection" id="import-collection">Collection</a></li> <li><a href="#" name="import-query" id="import-query">SQL Query</a></li>
</ul> <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 class="col-sm-9 col-md-10">
<div id="builder"></div>
</div>
</div>
<div class="form-group" title="How to display the alert. OK: green, Warning: yellow, Critical: red">
<label for='severity' class='col-sm-3 col-md-2 control-label'>Severity: </label>
<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 form-inline">
<label for='count' class='col-sm-3 col-md-2 control-label' title="How many notifications to issue while active before stopping. -1 means no limit. If interval is 0, this has no effect.">Max alerts: </label>
<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 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>
<div class="col-sm-4 col-md-3" title="How often to re-issue notifications while this alert is active. 0 means notify once. This is affected by the poller interval. (s,m,h,d)">
<label for='interval' class='control-label' style="vertical-align: top;">Interval: </label>
<input type='text' id='interval' name='interval' class='form-control' size="4">
</div>
</div>
<div class='form-group form-inline'>
<label for='mute' class='col-sm-3 col-md-2 control-label' title="Show alert status in the webui, but do not issue notifications.">Mute alerts: </label>
<div class='col-sm-2' title="Show alert status in the webui, but do not issue notifications.">
<input type="checkbox" name="mute" id="mute">
</div>
<label for='invert' class='col-sm-3 col-md-2 control-label' title="Alert when this rule doesn't match." style="vertical-align: top;">Invert match: </label>
<div class='col-sm-2' title="Alert when this rule doesn't match.">
<input type='checkbox' name='invert' id='invert'>
</div>
</div>
<div class="form-group" title="Issue recovery notifications.">
<label for='recovery' class='col-sm-3 col-md-2 control-label'>Recovery alerts: </label>
<div class='col-sm-2'>
<input type='checkbox' name='recovery' id='recovery'>
</div>
</div>
<div class="form-group" title="Restricts this alert rule to the selected devices or groups.">
<label for='maps' class='col-sm-3 col-md-2 control-label'>Map To: </label>
<div class="col-sm-9 col-md-10">
<select id="maps" name="maps[]" 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> </div>
<div class="col-sm-9 col-md-10"> <div role="tabpanel" class="tab-pane" id="advanced">
<div id="builder"></div> <div class="form-group">
</div> <label for="override_query" class="col-sm-3 col-md-2 control-label">Override SQL</label>
</div> <div class="col-sm-9 col-md-10">
<div class="form-group" title="How to display the alert. OK: green, Warning: yellow, Critical: red"> <input type='checkbox' name='override_query' id='override_query'>
<label for='severity' class='col-sm-3 col-md-2 control-label'>Severity: </label> </div>
<div class="col-sm-2"> </div>
<select name='severity' id='severity' class='form-control'> <div class="form-group">
<option value='ok'>OK</option> <label for="adv_query" class="col-sm-3 col-md-2 control-label">Query</label>
<option value='warning'>Warning</option> <div class="col-sm-9 col-md-10">
<option value='critical' selected>Critical</option> <input type="text" id="adv_query" name="adv_query" class="form-control">
</select> </div>
</div> </div>
</div>
<div class="form-group form-inline">
<label for='count' class='col-sm-3 col-md-2 control-label' title="How many notifications to issue while active before stopping. -1 means no limit. If interval is 0, this has no effect.">Max alerts: </label>
<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)">
<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>
<div class="col-sm-4 col-md-3" title="How often to re-issue notifications while this alert is active. 0 means notify once. This is affected by the poller interval. (s,m,h,d)">
<label for='interval' class='control-label' style="vertical-align: top;">Interval: </label>
<input type='text' id='interval' name='interval' class='form-control' size="4">
</div>
</div>
<div class='form-group form-inline'>
<label for='mute' class='col-sm-3 col-md-2 control-label' title="Show alert status in the webui, but do not issue notifications.">Mute alerts: </label>
<div class='col-sm-2' title="Show alert status in the webui, but do not issue notifications.">
<input type="checkbox" name="mute" id="mute">
</div>
<label for='invert' class='col-sm-3 col-md-2 control-label' title="Alert when this rule doesn't match." style="vertical-align: top;">Invert match: </label>
<div class='col-sm-2' title="Alert when this rule doesn't match.">
<input type='checkbox' name='invert' id='invert'>
</div>
</div>
<div class="form-group" title="Issue recovery notifications.">
<label for='recovery' class='col-sm-3 col-md-2 control-label'>Recovery alerts: </label>
<div class='col-sm-2'>
<input type='checkbox' name='recovery' id='recovery'>
</div>
</div>
<div class="form-group" title="Restricts this alert rule to the selected devices or groups.">
<label for='maps' class='col-sm-3 col-md-2 control-label'>Map To: </label>
<div class="col-sm-9 col-md-10">
<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>
<div class="form-group"> <div class="form-group">
@@ -269,15 +285,17 @@ if (LegacyAuth::user()->hasGlobalAdmin()) {
$("#mute").bootstrapSwitch('state', false); $("#mute").bootstrapSwitch('state', false);
$("#invert").bootstrapSwitch('state', false); $("#invert").bootstrapSwitch('state', false);
$("#recovery").bootstrapSwitch('state', true); $("#recovery").bootstrapSwitch('state', true);
$("#override_query").bootstrapSwitch('state', false);
$(this).find("input[type=text]").val(""); $(this).find("input[type=text]").val("");
$('#count').val('-1'); $('#count').val('-1');
$('#delay').val('1m'); $('#delay').val('1m');
$('#interval').val('5m'); $('#interval').val('5m');
$('#adv_query').val('');
var $maps = $('#maps'); var $maps = $('#maps');
$maps.empty(); $maps.empty();
$maps.val(null).trigger('change'); $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"); var $transports = $("#transports");
$transports.empty(); $transports.empty();
@@ -291,6 +309,7 @@ if (LegacyAuth::user()->hasGlobalAdmin()) {
$('#proc').val(rule.proc); $('#proc').val(rule.proc);
$('#builder').queryBuilder("setRules", rule.builder); $('#builder').queryBuilder("setRules", rule.builder);
$('#severity').val(rule.severity).trigger('change'); $('#severity').val(rule.severity).trigger('change');
$('#adv_query').val(rule.adv_query);
var $maps = $('#maps'); var $maps = $('#maps');
$maps.empty(); $maps.empty();
@@ -337,12 +356,20 @@ if (LegacyAuth::user()->hasGlobalAdmin()) {
$('#interval').val(extra.interval); $('#interval').val(extra.interval);
} }
if (extra.adv_query) {
$('#adv_query').val(extra.adv_query);
}
$("[name='mute']").bootstrapSwitch('state', extra.mute); $("[name='mute']").bootstrapSwitch('state', extra.mute);
$("[name='invert']").bootstrapSwitch('state', extra.invert); $("[name='invert']").bootstrapSwitch('state', extra.invert);
if (typeof extra.recovery == 'undefined') { if (typeof extra.recovery == 'undefined') {
extra.recovery = true; 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 { } else {
$('#count').val('-1'); $('#count').val('-1');
} }

View File

@@ -55,14 +55,21 @@ switch ($type) {
$response = 'no match'; $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']); $qb = QueryBuilderParser::fromJson($rule['builder']);
} else { } else {
$qb = QueryBuilderParser::fromOld($rule['rule']); $qb = QueryBuilderParser::fromOld($rule['rule']);
} }
$output .= 'Rule name: ' . $rule['name'] . PHP_EOL; $output .= 'Rule name: ' . $rule['name'] . PHP_EOL;
$output .= 'Alert rule: ' . $qb->toSql(false) . 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 .= 'Alert query: ' . $rule['query'] . PHP_EOL;
$output .= 'Rule match: ' . $response . PHP_EOL . PHP_EOL; $output .= 'Rule match: ' . $response . PHP_EOL . PHP_EOL;
} }

View File

@@ -177,6 +177,8 @@ foreach (dbFetchRows($full_query, $param) as $rule) {
if (empty($rule['builder'])) { if (empty($rule['builder'])) {
$rule_display = $rule['rule']; $rule_display = $rule['rule'];
} elseif ($rule_extra['options']['override_query'] === 'on') {
$rule_display = 'Custom SQL Query';
} else { } else {
$rule_display = QueryBuilderParser::fromJson($rule['builder'])->toSql(false); $rule_display = QueryBuilderParser::fromJson($rule['builder'])->toSql(false);
} }