feature: Added support for Host dependencies (#7332)

* First draft of the modals and the config interfaces

* GUI part done

* Backend code and db schema addition

* Documentation added, fixed alerting bug

* Fix typos

* Do not try to push an older db_schame.yaml

* Small db fix

* More db fixes

* Travis CI fixes

* missed a line in the travis error

* Fixed dependency clearing bug, Manage Host dependencies button now shows current selections

* Removed unnecessary index

* Correct faulty query

* Fixed sql query as requested, and renamed sql file

* Added requested changes

* Removed debug code

* Renamed sql file

* More fixes as requested

* Trying to fix db_schema.yaml

* adding laf's diff

* Corrected a small bug

* Try to resolve scrutinizer issue

* Main page bootgrid ajax modifications

* Also corrected travis ci errors

* Added select2 for pull downs, removed a redundant debug output. Changed parent_id to text

* Add missing class in the device settings page

* Fix bug where a link wasn't added after save

* Better parent down detection

* Add missing comma

* Behold the multi-parent code

* Added lookup table

* Ready for testing

* Trying to fix documentation conflicts

* Fix copy paste errors, and possible sql injection

* indentation problems

* Modified db_schema.yaml as well

* Typos, typos

* This should correct alerts

* Try to fix travis ci error

* Fix the typo in index.php

* Changed to Tony's query

* function explanation text changed

* Updated db_schema.yaml

* Trying to make automated tests happy

* Changes as requested

* Added acknowledgment for select2

* Added laf's patch

* dbBulkInsert when adding parents
This commit is contained in:
Aldemir Akpinar
2017-12-20 17:17:52 +03:00
committed by Neil Lathwood
parent d6d4a3a69a
commit 56561aa4dc
20 changed files with 840 additions and 1 deletions
+7
View File
@@ -0,0 +1,7 @@
source: Alerting/Hostdepencies.md
# Host Dependencies
It is possible to set one or more parents for a device. The aim for that is, if all parent devices are down, alert contacts will not receive redundant alerts for dependent devices. This is very useful when you have an outage, say in a branch office, where normally you'd receive hundreds of alerts, but when this is properly configured, you'd only receive an alert for the parent hosts.
There are three ways to configure this feature. First one is from general settings of a device. The other two can be done in the 'Host Dependencies' item under 'Devices' menu. In this page, you can see all devices and with its parents. Clicking on the 'bin' icon will clear the dependency setting. Clicking on the 'pen' icon will let you edit or change the current setting for chosen device. There's also a 'Manage Host Dependencies' button on the top. This will let you set parents for multiple devices at once.
+1
View File
@@ -59,3 +59,4 @@ Table of Content:
- [Time](Macros.md#macros-time)
- [Sensors](Macros.md#macros-sensors)
- [Misc](Macros.md#macros-misc)
- [Host Dependencies](Hostdependencies.md)
+1
View File
@@ -38,6 +38,7 @@ We list below what we make use of including the license compliance.
- [CorsSlim](https://github.com/palanik/CorsSlim): MIT
- [Confluence HTTP Authenticator](https://github.com/chauth/confluence_http_authenticator)
- [Graylog SSO Authentication Plugin](https://github.com/Graylog2/graylog-plugin-auth-sso)
- [Select2](https://select2.org): MIT License
## 3rd Party GPLv3 Non-compliant
+1
View File
File diff suppressed because one or more lines are too long
@@ -0,0 +1,30 @@
<?php
/*
* LibreNMS
*
* Copyright (c) 2017 Aldemir Akpinar <https://github.com/aldemira/>
*
* 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) {
$status = array('status' => 1, 'message' => 'You need to be admin');
} else {
if (!is_numeric($_POST['device_id'])) {
$status = array('status' => 1, 'message' => 'Wrong device id!');
} else {
if (dbDelete('device_relationships', '`child_device_id` = ?', array($_POST['device_id']))) {
$status = array('status' => 0, 'message' => 'Device dependency has been deleted.');
} else {
$status = array('status' => 1, 'message' => 'Device Dependency cannot be deleted.');
}
}
}
header('Content-Type: application/json');
echo _json_encode($status);
@@ -0,0 +1,102 @@
<?php
/*
* LibreNMS
*
* Copyright (c) 2017 Aldemir Akpinar <https://github.com/aldemira/>
*
* 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) {
$status = array('status' => 1, 'message' => 'You need to be admin');
} else {
if (isset($_POST['viewtype'])) {
if ($_POST['viewtype'] == 'fulllist') {
$count_query = "SELECT count(device_id) from devices";
$deps_query = "SELECT a.device_id as id, a.hostname as hostname, GROUP_CONCAT(b.hostname) as parents, GROUP_CONCAT(b.device_id) as parentid FROM devices as a LEFT JOIN device_relationships a1 ON a.device_id=a1.child_device_id LEFT JOIN devices b ON b.device_id=a1.parent_device_id GROUP BY a.device_id, a.hostname";
if (isset($_POST['format'])) {
if (isset($_POST['searchPhrase']) && !empty($_POST['searchPhrase'])) {
#This is a bit ugly
$deps_query = "SELECT * FROM (".$deps_query;
$deps_query .= " ) as t WHERE t.hostname LIKE ? OR t.parents LIKE ? ";
$deps_query .= " ORDER BY t.hostname";
} else {
$deps_query .= " ORDER BY a.hostname";
}
if (is_numeric($_POST['rowCount']) && is_numeric($_POST['current'])) {
$rows = $_POST['rowCount'];
$current = $_POST['current'];
$deps_query .= " LIMIT ".$rows * ($current - 1).", ".$rows;
}
} else {
$deps_query .= " ORDER BY a.hostname";
}
if (isset($_POST['format']) && !empty($_POST['searchPhrase'])) {
$searchphrase = '%'.mres($_POST['searchPhrase']).'%';
$device_deps = dbFetchRows($deps_query, array($searchphrase, $searchphrase));
} else {
$device_deps = dbFetchRows($deps_query);
}
if (isset($_POST['searchPhrase']) && !empty($_POST['searchPhrase'])) {
$rec_count = count($device_deps);
} else {
$rec_count = dbFetchCell($count_query);
}
if (isset($_POST['format'])) {
$res_arr = array();
foreach ($device_deps as $myrow) {
if ($myrow['parents'] == null || $myrow['parents'] == '') {
$parent = 'None';
} else {
$parent = $myrow['parents'];
}
array_push($res_arr, array( "deviceid" => $myrow['id'], "hostname" => $myrow['hostname'], "parent" => $parent, "parentid" => $myrow['parentid'] ));
}
$status = array('current' => $_POST['current'], 'rowCount' => $_POST['rowCount'], 'rows' => $res_arr, 'total' => $rec_count);
} else {
$status = array('status' => 0, 'deps' => $device_deps);
}
} else {
// Get childs from parent id(s)
if ($_POST['viewtype'] == 'fromparent') {
if ($_POST['parent_ids'] == 0) {
$device_deps = dbFetchRows('SELECT `device_id`,`hostname` from `devices` as a LEFT JOIN `device_relationships` as b ON b.`child_device_id` = a.`device_id` WHERE b.`child_device_id` is null ORDER BY `hostname`');
} else {
$parents = implode(',', $_POST['parent_ids']);
$device_deps = dbFetchRows("SELECT a.device_id as device_id, a.hostname as hostname, GROUP_CONCAT(b.hostname) as parents, GROUP_CONCAT(b.device_id) as parentid FROM devices as a LEFT JOIN device_relationships a1 ON a.device_id=a1.child_device_id LEFT JOIN devices b ON b.device_id=a1.parent_device_id GROUP BY a.device_id, a.hostname HAVING parentid = ?", array($parents));
}
$status = array('status' => 0, 'deps' => $device_deps);
}
}
} else {
// Find devices by child.
if (!is_numeric($_POST['device_id'])) {
$status = array('status' => 1, 'message' => 'Wrong device id!');
} else {
$deps_query = 'SELECT `device_id`, `hostname` FROM `devices` AS a INNER JOIN `device_relationships` AS b ON a.`device_id` = b.`parent_device_id` WHERE ';
// device_id == 0 is the case where we have no parents.
if ($_POST['device_id'] == 0) {
$device_deps = dbFetchRows($deps_query. ' b.`parent_device_id` is null OR b.`parent_device_id` = 0 ');
} else {
$device_deps = dbFetchRows($deps_query. ' b.`child_device_id` = ?', array($_POST['device_id']));
}
$status = array('status' => 0, 'deps' => $device_deps);
}
}
}
header('Content-Type: application/json');
echo _json_encode($status);
@@ -0,0 +1,58 @@
<?php
/*
* LibreNMS
*
* Copyright (c) 2017 Aldemir Akpinar <https://github.com/aldemira/>
*
* 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) {
$status = array('status' => 1, 'message' => 'You need to be admin');
} else {
foreach ($_POST['parent_ids'] as $parent) {
if (!is_numeric($parent)) {
$status = array('status' => 1, 'message' => 'Parent ID must be an integer!');
break;
}
}
if (count($_POST['parent_ids']) > 1 && in_array('0', $_POST['parent_ids'])) {
$status = array('status' => 1, 'message' => 'Multiple parents cannot contain None-Parent!');
}
// A bit of an effort to reuse this code with dependency editing and the dependency wizard (editing multiple hosts at the same time)
$device_arr = array();
foreach ($_POST['device_ids'] as $dev) {
if (!is_numeric($dev)) {
$status = array('status' => 1, 'message' => 'Device ID must be an integer!');
break;
} elseif (in_array($dev, $_POST['parent_ids'])) {
$status = array('status' => 1, 'message' => 'A device cannot depend itself');
break;
}
$insert = array();
foreach ($_POST['parent_ids'] as $parent) {
if (is_numeric($parent) && $parent != 0) {
$insert[] = array('parent_device_id' => $parent, 'child_device_id' => $dev);
} else if ($parent == 0) {
// In case we receive a mixed array with $parent = 0 (which shouldn't happen)
// Empty the insert array so we remove any previous dependency so 'None' takes precedence
$insert = array();
break;
}
}
dbDelete('device_relationships', '`child_device_id` = ?', array($dev));
if (!empty($insert)) {
dbBulkInsert($insert, 'device_relationships');
}
$status = array('status' => 0, 'message' => 'Device dependencies have been saved');
}
}
header('Content-Type: application/json');
echo _json_encode($status);
@@ -0,0 +1,72 @@
<?php
/*
* LibreNMS
*
* Copyright (c) 2017 Aldemir Akpinar <https://github.com/aldemira>
*
* 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" 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">&times;</button>
<h5 class="modal-title" id="Delete">Confirm Delete</h5>
</div>
<div class="modal-body">
<p>Clicking Delete will remove device dependency from <strong class="modalhostname"></strong></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="hostdep-removal" data-target="hostdep-removal">Delete</button>
<input type="hidden" name="row_id" id="delete-row_id" value="">
<input type="hidden" name="device_id" id="delete-device_id" value="">
<input type="hidden" name="parent_id" id="delete-parent_id" value="">
<input type="hidden" name="confirm" id="confirm" value="yes">
</form>
</div>
</div>
</div>
</div>
<script>
$('#hostdep-removal').click('', function(event) {
event.preventDefault();
var parent_id = $("#delete-parent_id").val();
var device_id = $("#delete-device_id").val();
var row_id = $("#delete-row_id").val();
$("#modal_hostname").text();
$.ajax({
type: 'POST',
url: 'ajax_form.php',
data: { type: "delete-host-dependency", device_id: device_id },
dataType: "json",
success: function(output) {
if (output.status == 0) {
toastr.success(output.message);
$("#confirm-delete").modal('hide');
// Clear the host association from html
$('[data-row-id=' + row_id + ']').find('.parenthost').text('None');
} else {
toastr.error(output.message);
}
},
error: function() {
toastr.error('The device dependency could not be deleted.');
$("#confirm-delete").modal('hide');
}
});
});
</script>
@@ -0,0 +1,135 @@
<?php
/*
* LibreNMS
*
* Copyright (c) 2017 Aldemir Akpinar <https://github.com/aldemira>
*
* 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="edit-dependency" role="dialog" aria-labelledby="Delete" aria-hidden="true">
<div class="modal-dialog modal-md">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h5 class="modal-title" id="Delete">Edit Dependency</h5>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-12">
<p>Please choose a parent device for <strong class="modalhostname"></strong> and click Save</p>
</div>
</div>
<br />
<div class="row">
<div class="col-md-6">
<div class="form-group">
<select multiple class="form-control" name="parent_id" id="availableparents" style="width: 100%">
</select>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<form role="form" class="remove_token_form">
<button type="button" class="btn btn-danger" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-success" id="hostdep-save" data-target="hostdep-save">Save</button>
<input type="hidden" name="row_id" id="edit-row_id" value="">
<input type="hidden" name="device_id" id="edit-device_id" value="">
<input type="hidden" name="orig_parent_id" id="edit-parent_id" value="">
</form>
</div>
</div>
</div>
</div>
<script>
$('#edit-dependency').on('show.bs.modal', function() {
var device_id = $("#edit-device_id").val();
$.ajax({
type: 'POST',
url: 'ajax_form.php',
data: { type: "get-host-dependencies", "device_id": device_id },
dataType: "json",
success: function(output) {
if (output.status == 0) {
var tempArr = [];
$.each(output.deps, function (i, elem) {
tempArr.push(elem.device_id);
});
$('#availableparents').val(tempArr);
$('#availableparents').trigger('change');
} else {
toastr.error(output.message);
}
},
error: function() {
toastr.error('The device dependency could not be fetched.');
$("#manage-dependencies").modal('hide');
}
})
});
$('#hostdep-save').click('', function(event) {
event.preventDefault();
var row_id = $("#edit-row_id").val();
var device_id = $("#edit-device_id").val();
var device_ids = [];
var parent_ids = [];
var parent_hosts = [];
device_ids.push(device_id);
$("#availableparents option:selected").each( function() {
if ($(this).length) {
parent_ids.push($(this).val());
parent_hosts.push($(this).text());
}
});
$("#modal_hostname").text();
$.ajax({
type: 'POST',
url: 'ajax_form.php',
data: { type: "save-host-dependency", device_ids: device_ids, parent_ids: parent_ids },
dataType: "json",
success: function(output) {
if (output.status == 0) {
toastr.success(output.message);
$("#edit-dependency").modal('hide');
$('#hostdeps').bootgrid('reload');
} else {
toastr.error(output.message);
}
},
error: function() {
toastr.error('The device dependency could not be saved.');
$("#edit-dependency").modal('hide');
$('#availableparents').val(null);
$('#availableparents').trigger('change');
}
});
});
$('#edit-dependency').on('hide.bs.modal', function() {
$('#availableparents').val(null);
$('#availableparents').trigger('change');
});
$('#availableparents').on('select2:select', function(e) {
if (e.params.data.id == 0) {
$('#availableparents').val(0);
$('#availableparents').trigger('change');
}
});
</script>
@@ -0,0 +1,184 @@
<?php
/*
* LibreNMS
*
* Copyright (c) 2017 Aldemir Akpinar <https://github.com/aldemira>
*
* 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="manage-dependencies" role="dialog" aria-labelledby="mandeps" aria-hidden="true">
<div class="modal-dialog modal-md">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h5 class="modal-title" id="mandeps">Device Dependency for Multiple Devices</h5>
</div>
<div class="modal-body">
<div id="mandeps-msg"></div>
<p>Here you can modify multiple device dependencies. Setting the parent device to "None" will clear the dependency.</p>
<br />
<div class="form-group">
<label for="manavailableparents">Parent Host:</label>
<select multiple name="parent_id" class="form-control" id="manavailableparents" style='width: 100%'>
</select>
</div>
<div class="form-group">
<label for="manalldevices">Child Hosts:</label>
<select multiple name="device_ids" class="form-control" id="manalldevices" style='width: 100%'>
</select>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-success" id="manhostdep-save" data-target="manhostdep-save">Save</button>
</div>
</div>
</div>
</div>
<script>
function changeParents(e, evttype)
{
e.preventDefault();
if (evttype == 'select' && e.params.data.id == 0) {
$('#manavailableparents').val(0);
$('#manavailableparents').trigger('change');
}
var cur_option = $('#manavailableparents').select2('data');
// So that we'll see all devices.
var device_id = 0;
var parent_ids = [];
// This is needed to remove the None option if it is with another parent id
for (var i=0;i<cur_option.length;i++) {
if (cur_option.length > 1 && cur_option[i].id == 0) {
continue;
}
parent_ids.push(cur_option[i].id);
}
// Set parents to new value
$('#manavailableparents').val(temp_arr).trigger('change');
$.ajax({
type: 'POST',
url: 'ajax_form.php',
data: { type: "get-host-dependencies", "parent_ids": parent_ids, "viewtype": "fromparent" },
dataType: "json",
success: function(output) {
if (output.status == 0) {
if (output.deps != null && output.deps != '') {
var temp_arr2 = [];
$.each(output.deps, function (i, elem) {
temp_arr2.push(elem.device_id);
});
$('#manalldevices').val(temp_arr2);
} else {
$('#manalldevices').val(null);
}
$('#manalldevices').trigger('change');
} else {
toastr.error(output.message);
}
},
error: function() {
toastr.error('Device dependencies could not be retrieved from the database');
}
});
}
$('#manage-dependencies').on('hide.bs.modal', function() {
$('#manavailableparents').val('0');
$('#manavailableparents').trigger('change');
$('#manalldevices').val(null);
$('#manalldevices').trigger('change');
});
$('#manage-dependencies').on('show.bs.modal', function() {
var device_id = 0;
$.ajax({
type: 'POST',
url: 'ajax_form.php',
data: { type: "get-host-dependencies", "viewtype": 'fromparent', "parent_ids": 0},
dataType: "json",
success: function(output) {
if (output.status == 0) {
var tempArr = [];
$.each(output.deps, function (i, elem) {
tempArr.push(elem.device_id);
});
$('#manalldevices').val(tempArr);
$('#manalldevices').trigger('change');
$('#manavailableparents').val(device_id);
$('#manavailableparents').trigger('change');
} else {
toastr.error(output.message);
}
},
error: function() {
toastr.error('Device dependencies could not be retrieved from the database');
}
})
});
$('#manhostdep-save').click('', function(event) {
event.preventDefault();
var device_ids = [];
var children = [];
var parent_id = [];
var parent_host = '';
// Get selections
var parents = $('#manavailableparents').select2('data');
var devices = $('#manalldevices').select2('data');
for (var i=0;i<parents.length;i++) {
parent_id.push(parents[i].id);
parent_host = parent_host + '<a href="device/device='+ parents[i].id +'/">' + parents[i].text + '</a>, ';
}
for (var i=0;i<devices.length;i++) {
device_ids.push(devices[i].id);
children.push(devices[i].text);
}
parent_host = parent_host.slice(0, -2);
$.ajax({
type: 'POST',
url: 'ajax_form.php',
data: { type: "save-host-dependency", device_ids: device_ids, parent_ids: parent_id },
dataType: "json",
success: function(output) {
$("#manage-dependencies").modal('hide');
$('#hostdeps').bootgrid('reload');
if (output.status == 0) {
toastr.success('Device dependencies saved successfully');
} else {
toastr.error('The device dependency could not be saved.');
}
},
error: function() {
toastr.error('The device dependency could not be saved.');
$("#manage-dependencies").modal('hide');
}
});
});
$(document).ready(function() {
$('#manavailableparents').on('select2:select', function(e) {changeParents(e, 'select')});
$('#manavailableparents').on('select2:unselect', function(e) {changeParents(e, 'unselect')});
});
</script>
+2
View File
@@ -212,6 +212,8 @@ if ($_SESSION['userlevel'] >= '10') {
echo '<li><a href="'.generate_url(array('page'=>'device-groups')).'"><i class="fa fa-th fa-fw fa-lg" aria-hidden="true"></i> Manage Groups</a></li>';
}
echo '<li><a href="'.generate_url(array('page'=>'device-dependencies')).'"><i class="fa fa-group fa-fw fa-lg"></i> Device Dependencies</a></li>';
echo '
<li role="presentation" class="divider"></li>
<li><a href="addhost/"><i class="fa fa-plus fa-fw fa-lg" aria-hidden="true"></i> Add Device</a></li>
+2
View File
@@ -127,6 +127,7 @@ if (empty($config['favicon'])) {
<link href="css/MarkerCluster.css" rel="stylesheet" type="text/css" />
<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=291727421" rel="stylesheet" type="text/css" />
<link href="css/<?php echo $config['site_style']; ?>.css?ver=632417639" rel="stylesheet" type="text/css" />
<?php
@@ -160,6 +161,7 @@ foreach ((array)$config['webui']['custom_css'] as $custom_css) {
<?php
}
?>
<script src="js/select2.min.js"></script>
<script src="js/librenms.js"></script>
<script type="text/javascript">
+1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -30,7 +30,7 @@ if ($_SESSION['userlevel'] == 11) {
<input type="hidden" name="id" value="<?php echo $_REQUEST['id'] ?>" />
<input type="hidden" name="confirm" value="1" />
<!--<input type="hidden" name="remove_rrd" value="<?php echo $_POST['remove_rrd']; ?>">-->
<button type="submit" class="btn btn-danger">Confirm host deletion</button>
<button type="submit" class="btn btn-danger">Confirm device deletion</button>
</div>
</form>
</center>
+166
View File
@@ -0,0 +1,166 @@
<?php
/*
* LibreNMS
*
* Copyright (c) 2017 Aldemir Akpinar <https://github.com/aldemira>
*
* 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.
*/
$no_refresh = true;
$pagetitle[] = 'Device Dependencies';
require_once 'includes/modal/delete_host_dependency.inc.php';
require_once 'includes/modal/edit_host_dependency.inc.php';
require_once 'includes/modal/manage_host_dependencies.inc.php';
?>
<div class="row">
<div class="col-sm-12">
<span id="message"></span>
</div>
</div>
<div class="table-responsive">
<table id="hostdeps" class="table table-hover table-condensed table-striped">
<thead>
<tr>
<th data-column-id="deviceid" data-visible="false" data-css-class="deviceid">No</th>
<th data-column-id="hostname" data-type="string" data-css-class="childhost" data-formatter="hostname">Hostname</th>
<th data-column-id="parent" data-type="string" data-css-class="parenthost" data-formatter="parent">Parent Device(s)</th>
<th data-column-id="parentid" data-visible="false">Parent ID</th>
<th data-column-id="actions" data-searchable="false" data-formatter="actions">Actions</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<script>
var grid = $("#hostdeps").bootgrid({
rowCount: [50, 100, 250, -1],
ajax: true,
post: function() {
return {
type: "get-host-dependencies",
viewtype: "fulllist",
format: "mainpage"
};
},
url: "ajax_form.php",
templates: {
header: '<div id="{{ctx.id}}" class="{{css.header}}"> \
<div class="row"> \
<?php if (is_admin()) { ?>
<div class="col-sm-8 actionBar"> \
<span class="pull-left"> \
<button type="button" class="btn btn-primary btn-sm command-manage" data-toggle="modal" data-target="#manage-dependencies" data-template_id="">Manage Device Dependencies</button> \
</span> \
</div> \
<div class="col-sm-4 actionBar"><p class="{{css.search}}"></p><p class="{{css.actions}}"></p></div></div></div>'
<?php } else { ?>
<div class="actionBar"><p class="{{css.search}}"></p><p class="{{css.actions}}"></p></div></div></div>'
<?php } ?>
},
formatters: {
"actions": function(column, row) {
var buttonClass = '';
var response = "<button type='button' class='btn btn-primary btn-sm command-edit' aria-label='Edit' data-toggle='modal' data-target='#edit-dependency' data-device_id='"+row.deviceid+"' data-host_name='"+row.hostname+"' data-parent_id='"+row.parentid+"' name='edit-host-dependency'><i class='fa fa-pencil' aria-hidden='true'></i></button> ";
if (row.parent == 'None') {
buttonClass = 'command-delete btn btn-danger btn-sm disabled';
} else {
buttonClass = 'command-delete btn btn-danger btn-sm';
}
response += "<button type='button' class='"+buttonClass+"' aria-label='Delete' data-toggle='modal' data-target='#confirm-delete' data-device_id='"+row.deviceid+"' data-device_parent ='"+row.parentid+"' data-host_name='"+row.hostname+"' name='delete-host-dependency'><i class='fa fa-trash' aria-hidden='true'></i></button>";
return response;
},
"hostname": function(column, row) {
return '<a href="device/device='+row.deviceid+'/">'+row.hostname+'</a>';
},
"parent": function(column, row) {
if (row.parent == 'None') {
return 'None';
} else {
var temp = Array();
var tempids = Array();
var counter = 0;
temp = row.parent.split(',');
tempids = row.parentid.split(',');
var retstr = '';
for (i=0; i < temp.length; i++) {
retstr = retstr + '<a href="device/device='+tempids[i]+'/">'+temp[i]+'</a>, ';
}
return retstr.slice(0, -2);
}
}
},
}).on("loaded.rs.jquery.bootgrid", function(e) {
e.preventDefault();
/* Executes after data is loaded and rendered */
grid.find(".command-edit").on("click", function(e) {
$('#edit-row_id').val($(this).parent().parent().data('row-id'));
$("#edit-device_id").val($(this).data("device_id"));
$("#edit-parent_id").val($(this).data("parent_id"));
$('#edit-dependency').modal('show');
$('.modalhostname').text($(this).data("host_name"));
}).end().find(".command-delete").on("click", function(e) {
$('#delete-row_id').val($(this).parent().parent().data('row-id'));
$("#delete-device_id").val($(this).data("device_id"));
$("#delete-parent_id").val($(this).data("device_parent"));
$('#confirm-delete').modal('show');
$('.modalhostname').text($(this).data("host_name"));
}).end().find(".command-manage").on("click", function(e) {
$('#manage-dependencies').modal('show');
});
});
$(document).ready(function() {
var editSelect = $('#availableparents').select2({
dropdownParent: $('#edit-dependency'),
width: 'resolve',
tags: true,
});
var manParentDevs = $('#manavailableparents').select2({
dropdownParent: $('#manage-dependencies'),
width: 'resolve',
tags: true
});
var manAllDevs = $('#manalldevices').select2({
dropdownParent: $('#manage-dependencies'),
width: 'resolve',
tags: true
});
$.ajax({
type: "POST",
url: 'ajax_form.php',
data: {type: 'get-host-dependencies', "viewtype": 'fulllist' },
dataType: "json",
success: function(output) {
if (output.status == 0) {
manParentDevs.append($('<option>', { value: 0, text: 'None'}));
editSelect.append($('<option>', { value: 0, text: 'None'}));
$.each(output.deps, function (i,elem) {
manParentDevs.append($('<option>',{value:elem.id, text:elem.hostname}));
editSelect.append($('<option>',{value:elem.id, text:elem.hostname}));
manAllDevs.append($('<option>',{value:elem.id, text:elem.hostname}));
});
} else {
toastr.error('Device dependencies could not be retrieved from the database');
}
},
error: function() {
toastr.error('Device dependencies could not be retrieved from the database');
}
});
});
</script>
+41
View File
@@ -3,6 +3,16 @@ if ($_POST['editing']) {
if ($_SESSION['userlevel'] > "7") {
$updated = 0;
if (isset($_POST['parent_id'])) {
$parent_id = $_POST['parent_id'];
$res = dbDelete('device_relationships', '`child_device_id` = ?', array($device['device_id']));
if (!in_array('0', $pr)) {
foreach ($parent_id as $pr) {
dbInsert(array('parent_device_id' => $pr, 'child_device_id' => $device['device_id']), 'device_relationships');
}
}
}
$override_sysLocation_bool = mres($_POST['override_sysLocation']);
if (isset($_POST['sysLocation'])) {
$override_sysLocation_string = $_POST['sysLocation'];
@@ -164,6 +174,33 @@ if ($updated && $update_message) {
?> value="<?php echo($override_sysLocation_string); ?>" />
</div>
</div>
<div class="form-group">
<label for="parent_id" class="col-sm-2 control-label">This device depends on:</label>
<div class="col-sm-6">
<select multiple name="parent_id[]" id="parent_id" class="form-control">
<?php
$dev_parents = dbFetchColumn('SELECT device_id from devices WHERE device_id IN (SELECT dr.parent_device_id from devices as d, device_relationships as dr WHERE d.device_id = dr.child_device_id AND d.device_id = ?)', array($device['device_id']));
if (!$dev_parents) {
$selected = 'selected="selected"';
} else {
$selected = '';
}
?>
<option value="0" <?=$selected?>>None</option>
<?php
$available_devs = dbFetchRows('SELECT `device_id`,`hostname` FROM `devices` WHERE `device_id` <> ? ORDER BY `hostname` ASC', array($device['device_id']));
foreach ($available_devs as $dev) {
if (in_array($dev['device_id'], $dev_parents)) {
$selected = 'selected="selected"';
} else {
$selected = '';
}
echo "<option value=".$dev['device_id']." ".$selected.">".$dev['hostname']."</option>";
}
?>
</select>
</div>
</div>
<div class="form-group">
<label for="disabled" class="col-sm-2 control-label">Disable:</label>
<div class="col-sm-6">
@@ -222,6 +259,10 @@ if ($updated && $update_message) {
document.getElementById('edit-hostname-input').disabled = true;
}
});
$('#parent_id').select2({
width: 'resolve',
tags: true,
});
</script>
<?php
print_optionbar_start();
+27
View File
@@ -828,6 +828,11 @@ function RunAlerts()
$noiss = true;
}
if (IsParentDown($alert['device_id'])) {
$noiss = true;
log_event('Skipped alerts because all parent devices are down', $alert['device_id'], 'alert', 1);
}
if (!$noiss) {
IssueAlert($alert);
dbUpdate(array('alerted' => $alert['state']), 'alerts', 'rule_id = ? && device_id = ?', array($alert['rule_id'], $alert['device_id']));
@@ -879,3 +884,25 @@ function ExtTransports($obj)
echo '; ';
}
}//end ExtTransports()
/**
* Check if a device's all parent are down
* Returns true if all parents are down
* @param int $device Device-ID
* @return bool
*/
function IsParentDown($device)
{
$parent_count = dbFetchCell("SELECT count(*) from `device_relationships` WHERE `child_device_id` = ?", array($device));
if (!$parent_count) {
return false;
}
$down_parent_count = dbFetchCell("SELECT count(*) from devices as d LEFT JOIN devices_attribs as a ON d.device_id=a.device_id LEFT JOIN device_relationships as r ON d.device_id=r.parent_device_id WHERE d.status=0 AND d.ignore=0 AND d.disabled=0 AND r.child_device_id=? AND (d.status_reason='icmp' OR (a.attrib_type='override_icmp_disable' AND a.attrib_value=true))", array($device));
if ($down_parent_count == $parent_count) {
return true;
}
return false;
} //end IsParentDown()
+7
View File
@@ -525,6 +525,13 @@ device_perf:
Indexes:
id: { Name: id, Columns: [id], Unique: false, Type: BTREE }
device_id: { Name: device_id, Columns: [device_id], Unique: false, Type: BTREE }
device_relationships:
Columns:
- { Field: parent_device_id, Type: 'int(11) unsigned', 'Null': false, Extra: '', Default: '0' }
- { Field: child_device_id, Type: 'int(11) unsigned', 'Null': false, Extra: '' }
Indexes:
PRIMARY: { Name: PRIMARY, Columns: [parent_device_id, child_device_id], Unique: true, Type: BTREE }
device_relationship_child_device_id_fk: { Name: device_relationship_child_device_id_fk, Columns: [child_device_id], Unique: false, Type: BTREE }
entityState:
Columns:
- { Field: entity_state_id, Type: int(11), 'Null': false, Extra: auto_increment }
+1
View File
@@ -105,6 +105,7 @@ pages:
- Alerting/Macros.md
- Alerting/Testing.md
- Alerting/Rule-Mapping.md
- Alerting/Hostdependencies.md
- 9. Getting help:
- How to get help: Support/index.md
- Support/FAQ.md
+1
View File
@@ -0,0 +1 @@
CREATE TABLE device_relationships ( parent_device_id int(11) unsigned NOT NULL DEFAULT 0, child_device_id int(11) unsigned NOT NULL, PRIMARY KEY (parent_device_id, child_device_id), CONSTRAINT device_relationship_parent_device_id_fk FOREIGN KEY (parent_device_id) REFERENCES devices (device_id) ON DELETE CASCADE, CONSTRAINT device_relationship_child_device_id_fk FOREIGN KEY (child_device_id) REFERENCES devices (device_id) ON DELETE CASCADE )ENGINE=InnoDB;