Device Components.

The purpose of this feature is to provide a framework for discovery modules to store
information in the database, without needing to add new tables for each feature.

This Feature provides:
- A database structure to store data.
- An API to store and retrieve data from the database.
- Integration to the LibreNMS APIv0.
- Ability to disable/ignore components.
- Default alerting rules.

- The API returns $COMPONENT[$device_id][$component_id] to allow pulling of data for multiple devices.
  The intent is to be able to create 'Applications' the consolidate data from applications covering multiple devices.
- Added some developer documentation
This commit is contained in:
Aaron Daniels
2015-08-15 16:01:43 +10:00
parent 40b2b7552c
commit c63b7119cb
11 changed files with 947 additions and 0 deletions

View File

@ -13,6 +13,7 @@
- [`get_graphs`](#api-route-5)
- [`get_graph_generic_by_hostname`](#api-route-6)
- [`get_port_graphs`](#api-route-7)
- [`get_components`](#api-route-25)
- [`get_port_stats_by_port_hostname`](#api-route-8)
- [`get_graph_by_port_hostname`](#api-route-9)
- [`list_devices`](#api-route-10)
@ -267,6 +268,70 @@ Output:
}
```
### <a name="api-route-25">Function: `get_components`</a> [`top`](#top)
Get a list of components for a particular device.
Route: /api/v0/devices/:hostname/components
- hostname can be either the device hostname or id
Input:
- type: Filter the result by type (Equals).
- id: Filter the result by id (Equals).
- label: Filter the result by label (Contains).
- status: Filter the result by status (Equals).
- disabled: Filter the result by disabled (Equals).
- ignore: Filter the result by ignore (Equals).
Example:
```curl
curl -H 'X-Auth-Token: YOURAPITOKENHERE' https://librenms.org/api/v0/devices/localhost/components
```
Output:
```text
{
"status": "ok",
"err-msg": "",
"count": 3,
"components": {
"2": {
"TestAttribute-1": "Value1",
"TestAttribute-2": "Value2",
"TestAttribute-3": "Value3",
"type": "TestComponent-1",
"label": "This is a really cool blue component",
"status": "1",
"ignore": "0",
"disabled": "0"
},
"20": {
"TestAttribute-1": "Value4",
"TestAttribute-2": "Value5",
"TestAttribute-3": "Value6",
"type": "TestComponent-1",
"label": "This is a really cool red component",
"status": "1",
"ignore": "0",
"disabled": "0"
},
"27": {
"TestAttribute-1": "Value7",
"TestAttribute-2": "Value8",
"TestAttribute-3": "Value9",
"type": "TestComponent-2",
"label": "This is a really cool yellow widget",
"status": "1",
"ignore": "0",
"disabled": "0"
}
}
}
```
### <a name="api-route-8">Function: `get_port_stats_by_port_hostname`</a> [`top`](#top)
Get information about a particular port for a device.

309
doc/Extensions/Component.md Normal file
View File

@ -0,0 +1,309 @@
Table of Content:
- [About](#about)
- [Database Structure](#database)
- [Reserved Fields](#reserved)
- [Using Components](#using)
- [Getting Current Components](#get)
- [Options](#options)
- [Filtering Results](#filter)
- [Sorting Results](#sort)
- [Creating Components](#create)
- [Deleting Components](#delete)
- [Editing Components](#update)
- [Edit the Array](#update-edit)
- [Writing the Components](#update-write)
- [API](#api)
- [Alerting](#alert)
- [Example Code](#example)
# <a name="about">About</a>
The Component extension provides a generic database storage mechanism for discovery and poller modules.
The Driver behind this extension was to provide the features of ports, in a generic manner to discovery/poller modules.
It provides a status (Normal or Alert), the ability to Disable (do not poll), or Ignore (do not Alert).
# <a name="database">Database Structure</a>
The database structure contains the component table:
```SQL
mysql> select * from component limit 1;
+----+-----------+------+------------+--------+----------+--------+-------+
| id | device_id | type | label | status | disabled | ignore | error |
+----+-----------+------+------------+--------+----------+--------+-------+
| 9 | 1 | TEST | TEST LABEL | 0 | 1 | 1 | |
+----+-----------+------+------------+--------+----------+--------+-------+
1 row in set (0.00 sec)
```
These fields are described below:
- id - ID for each component, unique index
- device_id - device_id from the devices table
- type - name from the component_type table
- label - Display label for the component
- status - The status of the component, retrieved from the device
- disabled - Should this component be polled?
- ignore - Should this component be alerted on
- error - Error message if in Alert state
The component_prefs table holds custom data in an Attribute/Value format:
```SQL
mysql> select * from component_prefs limit 1;
+----+-----------+-----------+-----------+
| id | component | attribute | value |
+----+-----------+-----------+-----------+
| 4 | 9 | TEST_ATTR | TEST_ATTR |
+----+-----------+-----------+-----------+
2 rows in set (0.00 sec)
```
## <a name="reserved">Reserved Fields</a>
When this data from both the component and component_prefs tables is returned in one single consolidated array, there is the potential for someone to attempt to set an attribute (in the component_prefs) table that is used in the components table.
Because of this all fields of the component table are reserved, they cannot be used as custom attributes, if you update these the module will attempt to write them to the component table, not the component_prefs table.
# <a name="using">Using Components</a>
To use components in you application, first you need to include the code.
From a Discovery/Poller module:
```php
require_once 'includes/component.php';
```
From the html tree:
```php
require_once "../includes/component.php";
```
Once the code is loaded, create an instance of the component class:
```php
$COMPONENT = new component();
```
## <a name="get">Retrieving Components</a>
Now you can retrieve an array of the available components:
```php
$ARRAY = $COMPONENT->getComponents($DEVICE_ID,$OPTIONS);
```
`getComponents` takes 2 arguments:
- DEVICE_ID or null for all devices.
- OPTIONS - an array of various options.
`getComponents` will return an array containing components in the following format:
Array
(
[X] => Array
(
[Y1] => Array
(
[device_id] => 1
[TEST_ATTR] => TEST_ATTR
[type] => TEST
[label] => TEST LABEL
[status] => 0
[ignore] => 1
[disabled] => 1
[error] =>
),
[Y2] => Array
(
[device_id] => 1
[TEST_ATTR] => TEST_ATTR
[type] => TESTING
[label] => TEST LABEL
[status] => 0
[ignore] => 1
[disabled] => 0
[error] =>
),
)
)
Where X is the Device ID and Y1/Y2 is the Component ID. In the example above, 'TEST_ATTR' is a custom field, the rest are reserved fields.
### <a name="options">Options</a>
Options can be supplied to `getComponents` to influence which and how components are returned.
#### <a name="filter">Filtering</a>
You can filter on any of the [reserved](#reserved) fields. Filters are created in the following format:
```php
$OPTIONS['filter'][FIELD] = array ('OPERATOR', 'CRITERIA');
```
Where:
- FIELD - The [reserved](#reserved) field to filter on
- OPERATOR - 'LIKE' or '=', are we checking if the FIELD equals or contains the CRITERIA.
- CRITERIA - The criteria to search on
There are 2 filtering shortcuts:
$DEVICE_ID is a synonym for:
```php
$OPTIONS['filter']['device_id'] = array ('=', $DEVICE_ID);
```
`$OPTIONS['type'] = $TYPE` is a synonym for:
```php
$OPTIONS['filter']['type'] = array ('=', $TYPE);
```
#### <a name="sort">Sorting</a>
You can sort the records that are returned by specifying the following option:
```php
$OPTIONS['sort'][FIELD] = 'DIRECTION';
```
Where Direction is one of:
- ASC - Ascending, from Low to High
- DESC - Descending, from High to Low
## <a name="creating">Creating Components</a>
To create a new component, run the createComponent function.
```php
$ARRAY = $COMPONENT->createComponent($DEVICE_ID,$TYPE);
```
`createComponent` takes 2 arguments:
- DEVICE_ID - The ID of the device to attach the component to.
- TYPE - The unique type for your module.
This will return a new, empty array with a component ID and Type set, all other fields will be set to defaults.
Array
(
[1] => Array
(
[type] => TESTING
[label] =>
[status] => 1
[ignore] => 0
[disabled] => 0
[error] =>
)
)
## <a name="delete">Deleting Components</a>
When a component is no longer needed, it can be deleted.
```php
$COMPONENT->deleteComponent($COMPONENT_ID)
```
This will return True on success or False on failure.
## <a name="update">Editing Components</a>
To edit a component, the procedure is:
1. [Get the Current Components](#get)
2. [Edit the array](#update-edit)
3. [Write the components](#update-write)
### <a name="update-edit">Edit the Array</a>
Once you have a component array from getComponents the first thing to do is extract the components for only the single device you are editing. This is required because the setComponentPrefs function only saves a single device at a time.
```php
$ARRAY = $COMPONENT->getComponents($DEVICE_ID,$OPTIONS);
$ARRAY = $ARRAY[$DEVICE_ID];
```
Then simply edit this array to suit your needs.
If you need to add a new Attribute/Value pair you can:
```php
$ARRAY[COMPONENT_ID]['New Attribute'] = 'Value';
```
If you need to delete a previously set Attribute/Value pair you can:
```php
unset($ARRAY[COMPONENT_ID]['New Attribute']);
```
If you need to edit a previously set Attribute/Value pair you can:
```php
$ARRAY[COMPONENT_ID]['Existing Attribute'] = "New Value";
```
### <a name="update-write">Write the components</a>
To write component changes back to the database simply:
```php
$COMPONENT->setComponentPrefs($DEVICE_ID,$ARRAY)
```
When writing the component array there are several caveats to be aware of, these are:
- $ARRAY must be in the format of a single device ID - `$ARRAY[$COMPONENT_ID][Attribute] = 'Value';` NOT in the multi device format returned by getComponents - $ARRAY[$DEVICE_ID][$COMPONENT_ID][Attribute] = 'Value';`
- You cannot edit the Component ID or the Device ID
- [reserved](#reserved) fields can not be removed
- if a change is found an entry will be written to the eventlog.
## <a name="api">API</a>
Component details are available via the API.
Please see the [API-Docs](http://docs.librenms.org/API/API-Docs/#api-route-25) for details.
## <a name="alert">Alerting</a>
A default alerting rule exists that will raise an alert for any component which has a status of 0.
This can be disabled:
- Globally - by removing the 'Component Alert' alerting rule.
- Locally - by setting the ignore field of an individual component to 1.
The data that is written to each alert when it is raised is in the following format:
`COMPONENT_TYPE - LABEL - ERROR`
if you are creating a poller module which can detect a fault condition simply set STATUS to 0 and ERROR to a message that indicates the problem.
# <a name="examples">Example Code</a>
To see an example of how the component module can used, please see the following modules:
- Cisco CBQoS
- includes/discovery/cisco-cbqos.inc.php
- includes/poller/cisco-cbqos.inc.php
- html/includes/graphs/device/cbqos_traffic.inc.php
- Cisco OTV
- includes/discovery/cisco-otv.inc.php
- includes/poller/applications/cisco-otv.inc.php
- html/includes/graphs/device/cisco-otv-mac.inc.php
- html/pages/device/apps/cisco-otv.inc.php

View File

@ -49,6 +49,8 @@ $app->group(
// api/v0/devices/$hostname/graphs
$app->get('/:hostname/ports', 'authToken', 'get_port_graphs')->name('get_port_graphs');
// api/v0/devices/$hostname/ports
$app->get('/:hostname/components', 'authToken', 'get_components')->name('get_components');
// api/v0/devices/$hostname/components
$app->get('/:hostname/:type', 'authToken', 'get_graph_generic_by_hostname')->name('get_graph_generic_by_hostname');
// api/v0/devices/$hostname/$type
$app->get('/:hostname/ports/:ifname', 'authToken', 'get_port_stats_by_port_hostname')->name('get_port_stats_by_port_hostname');

View File

@ -13,6 +13,7 @@
*/
require_once '../includes/functions.php';
require_once '../includes/component.php';
function authToken(\Slim\Route $route) {
@ -500,6 +501,59 @@ function get_graph_by_portgroup() {
}
function get_components() {
global $config;
$code = 200;
$status = 'ok';
$message = '';
$app = \Slim\Slim::getInstance();
$router = $app->router()->getCurrentRoute()->getParams();
$hostname = $router['hostname'];
// Do some filtering if the user requests.
$options = array();
if (isset($_GET['type'])) {
// set a type = filter
$options['filter']['type'] = array('=',$_GET['type']);
}
if (isset($_GET['id'])) {
// set a id = filter
$options['filter']['id'] = array('=',$_GET['id']);
}
if (isset($_GET['label'])) {
// set a label like filter
$options['filter']['label'] = array('LIKE',$_GET['label']);
}
if (isset($_GET['status'])) {
// set a status = filter
$options['filter']['status'] = array('=',$_GET['status']);
}
if (isset($_GET['disabled'])) {
// set a disabled = filter
$options['filter']['disabled'] = array('=',$_GET['disabled']);
}
if (isset($_GET['ignore'])) {
// set a ignore = filter
$options['filter']['ignore'] = array('=',$_GET['ignore']);
}
// use hostname as device_id if it's all digits
$device_id = ctype_digit($hostname) ? $hostname : getidbyname($hostname);
$COMPONENT = new component();
$components = $COMPONENT->getComponents($device_id,$options);
$output = array(
'status' => "$status",
'err-msg' => $message,
'count' => count($components[$device_id]),
'components' => $components[$device_id],
);
$app->response->setStatus($code);
$app->response->headers->set('Content-Type', 'application/json');
echo _json_encode($output);
}
function get_graphs() {
global $config;
$code = 200;

View File

@ -0,0 +1,84 @@
<?php
if (is_admin() === false) {
$response = array(
'status' => 'error',
'message' => 'Need to be admin',
);
echo _json_encode($response);
exit;
}
$status = 'error';
$message = 'Error with config';
// enable/disable components on devices.
$device_id = intval($_POST['device']);
require_once "../includes/component.php";
$OBJCOMP = new component();
// Go get the component array.
$COMPONENTS = $OBJCOMP->getComponents($device_id);
// We only care about our device id.
$COMPONENTS = $COMPONENTS[$device_id];
// Track how many updates we are making.
$UPDATE = array();
foreach ($COMPONENTS as $ID => $AVP) {
// Is the component disabled?
if (isset($_POST['dis_'.$ID])) {
// Yes it is, was it disabled before?
if ($COMPONENTS[$ID]['disabled'] == 0) {
// No it wasn't, best we disable it then..
$COMPONENTS[$ID]['disabled'] = 1;
$UPDATE[$ID] = true;
}
}
else {
// No its not, was it disabled before?
if ($COMPONENTS[$ID]['disabled'] == 1) {
// Yes it was, best we enable it then..
$COMPONENTS[$ID]['disabled'] = 0;
$UPDATE[$ID] = true;
}
}
// Is the component ignored?
if (isset($_POST['ign_'.$ID])) {
// Yes it is, was it ignored before?
if ($COMPONENTS[$ID]['ignore'] == 0) {
// No it wasn't, best we ignore it then..
$COMPONENTS[$ID]['ignore'] = 1;
$UPDATE[$ID] = true;
}
}
else {
// No its not, was it ignored before?
if ($COMPONENTS[$ID]['ignore'] == 1) {
// Yes it was, best we un-ignore it then..
$COMPONENTS[$ID]['ignore'] = 0;
$UPDATE[$ID] = true;
}
}
}
if (count($UPDATE) > 0) {
// Update our edited components.
$STATUS = $OBJCOMP->setComponentPrefs($device_id,$COMPONENTS);
$message = count($UPDATE).' Device records updated.';
$status = 'ok';
}
else {
$message = 'Record unchanged. No update necessary.';
$status = 'ok';
}
$response = array(
'status' => $status,
'message' => $message,
);
echo _json_encode($response);

View File

@ -1156,6 +1156,16 @@ function alert_details($details) {
$fallback = false;
}
if ($tmp_alerts['type'] && $tmp_alerts['label']) {
if ($tmp_alerts['error'] == "") {
$fault_detail .= ' '.$tmp_alerts['type'].' - '.$tmp_alerts['label'].';&nbsp;';
}
else {
$fault_detail .= ' '.$tmp_alerts['type'].' - '.$tmp_alerts['label'].' - '.$tmp_alerts['error'].';&nbsp;';
}
$fallback = false;
}
if ($fallback === true) {
foreach ($tmp_alerts as $k => $v) {
if (!empty($v) && $k != 'device_id' && (stristr($k, 'id') || stristr($k, 'desc') || stristr($k, 'msg')) && substr_count($k, '_') <= 1) {

View File

@ -0,0 +1,59 @@
<?php
$row = 1;
$device_id = $_POST['device_id'];
require_once "../includes/component.php";
$OBJCOMP = new component();
// Add a filter if supplied
if (isset($searchPhrase) && !empty($searchPhrase)) {
$options['filter']['label'] = array('LIKE', $searchPhrase);
}
// Add a Sort option
if (!isset($sort) || empty($sort)) {
// Nothing supplied, default is id ASC.
$options['sort'] = 'id asc';
}
else {
$options['sort'] = $sort;
}
// Define the Limit parameters
if (isset($current)) {
$start = (($current * $rowCount) - ($rowCount));
}
if ($rowCount != -1) {
$options['limit'] = array($start,$rowCount);
}
$COMPONENTS = $OBJCOMP->getComponents($device_id,$options);
$response[] = array(
'id' => '<button type="submit" id="save-form" class="btn btn-success btn-sm" title="Save current component disable/ignore settings">Save</button><button type="submit" id="form-reset" class="btn btn-danger btn-sm" title="Reset form to when the page was loaded">Reset</button>',
'label' => '&nbsp;',
'status' => '<button type="submit" id="alert-select" class="btn btn-default btn-sm" title="Disable alerting on all currently-alerting components">Alerting</button>',
'disable' => '<button type="submit" id="disable-toggle" class="btn btn-default btn-sm" title="Toggle polling for all components">Toggle</button><button type="button" id="disable-select" class="btn btn-default btn-sm" title="Disable polling on all components">Select All</button>',
'ignore' => '<button type="submit" id="ignore-toggle" class="btn btn-default btn-sm" title="Toggle alerting for all components">Toggle</button><button type="button" id="ignore-select" class="btn btn-default btn-sm" title="Disable alerting on all components">Select All</button>',
);
foreach ($COMPONENTS[$device_id] as $ID => $AVP) {
$response[] = array(
'id' => $ID,
'type' => $AVP['type'],
'label' => $AVP['label'],
'status' => ($AVP['status'] ? "<span name='status_".$ID."' class='green'>Normal</span>" : "<span name='status_".$ID."' class='red'>Alert</span>"),
'disable' => '<input type="checkbox" class="disable-check" name="dis_'.$ID.'"'.($AVP['disabled'] ? 'checked' : '').'>',
'ignore' => '<input type="checkbox" class="ignore-check" name="ign_'.$ID.'"'.($AVP['ignore'] ? 'checked' : '').'>',
);
}//end foreach
$output = array(
'current' => $current,
'rowCount' => $rowCount,
'rows' => $response,
'total' => count($COMPONENTS[$device_id]),
);
echo _json_encode($output);

View File

@ -38,6 +38,8 @@ else {
$panes['storage'] = 'Storage';
$panes['misc'] = 'Misc';
$panes['component'] = 'Components';
print_optionbar_start();
unset($sep);

View File

@ -0,0 +1,102 @@
<span id="message"><small><div class="alert alert-danger">n.b For the first time, please click any button twice.</div></small></span>
<form id='components' class='form-inline' method='POST'>
<table id='table' class='table table-condensed table-responsive table-striped'>
<thead>
<tr>
<th data-column-id='id'>ID</th>
<th data-column-id='type'>Type</th>
<th data-column-id='label'>Label</th>
<th data-column-id='status'>Status</th>
<th data-column-id='disable' data-sortable='false'>Disable</th>
<th data-column-id='ignore' data-sortable='false'>Ignore</th>
</tr>
</thead>
</table>
<input type='hidden' name='component' value='yes'>
<input type='hidden' name='type' value='component'>
<input type='hidden' name='device' value='<?php echo $device['device_id'];?>'>
</form>
<script>
// Waiting for the document to be ready.
$(document).ready(function() {
$('form#components').submit(function (event) {
$('#disable-toggle').click(function (event) {
// invert selection on all disable buttons
event.preventDefault();
$('input[name^="dis_"]').trigger('click');
});
$('#disable-select').click(function (event) {
// select all disable buttons
event.preventDefault();
$('.disable-check').prop('checked', true);
});
$('#ignore-toggle').click(function (event) {
// invert selection on all ignore buttons
event.preventDefault();
$('input[name^="ign_"]').trigger('click');
});
$('#ignore-select').click(function (event) {
// select all ignore buttons
event.preventDefault();
$('.ignore-check').prop('checked', true);
});
$('#alert-select').click(function (event) {
// select ignore buttons for all ports which are down
event.preventDefault();
$('[name^="status_"]').each(function () {
var name = $(this).attr('name');
var text = $(this).text();
if (name && text == 'Alert') {
// get the component number from the object name
var id = name.split('_')[1];
// find its corresponding checkbox and toggle it
$('input[name="ign_' + id + '"]').trigger('click');
}
});
});
$('#form-reset').click(function (event) {
// reset objects in the form to the value when the page was loaded
event.preventDefault();
$('#components')[0].reset();
});
$('#save-form').click(function (event) {
event.preventDefault();
$.ajax({
type: "POST",
url: "/ajax_form.php",
data: $('form#components').serialize(),
dataType: "json",
success: function(data){
if (data.status == 'ok') {
$("#message").html('<div class="alert alert-info">' + data.message + '</div>')
} else {
$("#message").html('<div class="alert alert-danger">' + data.message + '</div>');
}
},
error: function(){
$("#message").html('<div class="alert alert-danger">Error creating config item</div>');
}
});
});
event.preventDefault();
});
});
var grid = $("#table").bootgrid({
ajax: true,
rowCount: [50,100,250,-1],
post: function ()
{
return {
id: 'component',
device_id: "<?php echo $device['device_id']; ?>"
};
},
url: "/ajax_table.php"
});
</script>

254
includes/component.php Normal file
View File

@ -0,0 +1,254 @@
<?php
/*
* LibreNMS module to Interface with the Component System
*
* Copyright (c) 2015 Aaron Daniels <aaron@daniels.id.au>
*
* 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.
*/
class component {
/*
* These fields are used in the component table. They are returned in the array
* so that they can be modified but they can not be set as user attributes. We
* also set their default values.
*/
private $reserved = array(
'type' => '',
'label' => '',
'status' => 1,
'ignore' => 0,
'disabled' => 0,
'error' => '',
);
public function getComponentType($TYPE=null) {
if (is_null($TYPE)) {
$SQL = "SELECT DISTINCT `type` as `name` FROM `component` ORDER BY `name`";
$row = dbFetchRow($SQL, array());
}
else {
$SQL = "SELECT DISTINCT `type` as `name` FROM `component` WHERE `type` = ? ORDER BY `name`";
$row = dbFetchRow($SQL, array($TYPE));
}
if (!isset($row)) {
// We didn't find any component types
return false;
}
else {
// We found some..
return $row;
}
}
public function getComponents($device_id=null,$options=array()) {
// Define our results array, this will be set even if no rows are returned.
$RESULT = array();
$PARAM = array();
// Our base SQL Query, with no options.
$SQL = "SELECT `C`.`id`,`C`.`device_id`,`C`.`type`,`C`.`label`,`C`.`status`,`C`.`disabled`,`C`.`ignore`,`C`.`error`,`CP`.`attribute`,`CP`.`value` FROM `component` as `C` LEFT JOIN `component_prefs` as `CP` on `C`.`id`=`CP`.`component` WHERE ";
// Device_id is shorthand for filter C.device_id = $device_id.
if (!is_null($device_id)) {
$options['filter']['device_id'] = array('=', $device_id);
}
// Type is shorthand for filter type = $type.
if (isset($options['type'])) {
$options['filter']['type'] = array('=', $options['type']);
}
// filter field => array(operator,value)
// Filters results based on the field, operator and value
$COUNT = 0;
if (isset($options['filter'])) {
$COUNT++;
$SQL .= " ( ";
foreach ($options['filter'] as $field => $array) {
if ($array[0] == 'LIKE') {
$SQL .= "`".$field."` LIKE ? AND ";
$array[1] = "%".$array[1]."%";
}
else {
// Equals operator is the default
$SQL .= "`".$field."` = ? AND ";
}
array_push($PARAM,$array[1]);
}
// Strip the last " AND " before closing the bracket.
$SQL = substr($SQL,0,-5)." )";
}
if ($COUNT == 0) {
// Strip the " WHERE " that we didn't use.
$SQL = substr($SQL,0,-7);
}
// sort column direction
// Add SQL sorting to the results
if (isset($options['sort'])) {
$SQL .= " ORDER BY ".$options['sort'];
}
// Get our component records using our built SQL.
$COMPONENTS = dbFetchRows($SQL, $PARAM);
// if we have no components we need to return nothing
if (count($COMPONENTS) == 0) {
return $RESULT;
}
// Add the AVP's to the array.
foreach ($COMPONENTS as $COMPONENT) {
if ($COMPONENT['attribute'] != "") {
// if this component has attributes, set them in the array.
$RESULT[$COMPONENT['device_id']][$COMPONENT['id']][$COMPONENT['attribute']] = $COMPONENT['value'];
}
}
// Populate our reserved fields into the Array, these cant be used as user attributes.
foreach ($COMPONENTS as $COMPONENT) {
foreach ($this->reserved as $k => $v) {
$RESULT[$COMPONENT['device_id']][$COMPONENT['id']][$k] = $COMPONENT[$k];
}
// Sort each component array so the attributes are in order.
ksort($RESULT[$RESULT[$COMPONENT['device_id']][$COMPONENT['id']]]);
ksort($RESULT[$RESULT[$COMPONENT['device_id']]]);
}
// limit array(start,count)
if (isset($options['limit'])) {
$TEMP = array();
$COUNT = 0;
// k = device_id, v = array of components for that device_id
foreach ($RESULT as $k => $v) {
// k1 = component id, v1 = component array
foreach ($v as $k1 => $v1) {
if ( ($COUNT >= $options['limit'][0]) && ($COUNT < $options['limit'][0]+$options['limit'][1])) {
$TEMP[$k][$k1] = $v1;
}
// We are counting components.
$COUNT++;
}
}
$RESULT = $TEMP;
}
return $RESULT;
}
public function createComponent ($device_id,$TYPE) {
// Prepare our default values to be inserted.
$DATA = $this->reserved;
// Add the device_id and type
$DATA['device_id'] = $device_id;
$DATA['type'] = $TYPE;
// Insert a new component into the database.
$id = dbInsert($DATA, 'component');
// Create a default component array based on what was inserted.
$ARRAY[$id] = $DATA;
unset ($ARRAY[$id]['device_id']); // This doesn't belong here.
return $ARRAY;
}
public function deleteComponent ($id) {
// Delete a component from the database.
return dbDelete('component', "`id` = ?",array($id));
}
public function setComponentPrefs ($device_id,$ARRAY) {
// Compare the arrays. Update/Insert where necessary.
$OLD = $this->getComponents($device_id);
// Loop over each component.
foreach ($ARRAY as $COMPONENT => $AVP) {
// Make sure the component already exists.
if (!isset($OLD[$device_id][$COMPONENT])) {
// Error. Component doesn't exist in the database.
continue;
}
// Ignore type, we cant change that.
unset($AVP['type'],$OLD[$device_id][$COMPONENT]['type']);
// Process our reserved components first.
$UPDATE = array();
foreach ($this->reserved as $k => $v) {
// does the reserved field exist, if not skip.
if (isset($AVP[$k])) {
// Has the value changed?
if ($AVP[$k] != $OLD[$device_id][$COMPONENT][$k]) {
// The value has been modified, add it to our update array.
$UPDATE[$k] = $AVP[$k];
}
// Unset the reserved field. We don't want to insert it below.
unset($AVP[$k],$OLD[$device_id][$COMPONENT][$k]);
}
}
// Has anything changed, do we need to update?
if (count($UPDATE) > 0) {
// We have data to update
dbUpdate($UPDATE, 'component', '`id` = ?', array($COMPONENT));
// Log the update to the Eventlog.
$MSG = "Component ".$COMPONENT." has been modified: ";
foreach ($UPDATE as $k => $v) {
$MSG .= $k." => ".$v.",";
}
$MSG = substr($MSG,0,-1);
log_event($MSG,$device_id,'component',$COMPONENT);
}
// Process our AVP Adds and Updates
foreach ($AVP as $ATTR => $VALUE) {
// We have our AVP, lets see if we need to do anything with it.
if (!isset($OLD[$device_id][$COMPONENT][$ATTR])) {
// We have a newly added attribute, need to insert into the DB
$DATA = array('component'=>$COMPONENT, 'attribute'=>$ATTR, 'value'=>$VALUE);
$id = dbInsert($DATA, 'component_prefs');
// Log the addition to the Eventlog.
log_event ("Component: " . $AVP[$COMPONENT]['type'] . "(" . $COMPONENT . "). Attribute: " . $ATTR . ", was added with value: " . $VALUE, $device_id, 'component', $COMPONENT);
}
elseif ($OLD[$device_id][$COMPONENT][$ATTR] != $VALUE) {
// Attribute exists but the value is different, need to update
$DATA = array('value'=>$VALUE);
dbUpdate($DATA, 'component_prefs', '`component` = ? AND `attribute` = ?', array($COMPONENT, $ATTR));
// Add the modification to the Eventlog.
log_event("Component: ".$AVP[$COMPONENT]['type']."(".$COMPONENT."). Attribute: ".$ATTR.", was modified from: ".$OLD[$COMPONENT][$ATTR].", to: ".$VALUE,$device_id,'component',$COMPONENT);
}
} // End Foreach COMPONENT
// Process our Deletes.
$DELETE = array_diff_key($OLD[$device_id][$COMPONENT], $AVP);
foreach ($DELETE as $KEY => $VALUE) {
// As the Attribute has been removed from the array, we should remove it from the database.
dbDelete('component_prefs', "`component` = ? AND `attribute` = ?",array($COMPONENT,$KEY));
// Log the addition to the Eventlog.
log_event ("Component: " . $AVP[$COMPONENT]['type'] . "(" . $COMPONENT . "). Attribute: " . $ATTR . ", was deleted.", $COMPONENT);
}
}
return true;
}
}

6
sql-schema/084.sql Normal file
View File

@ -0,0 +1,6 @@
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 `alert_rules` (`device_id`,`rule`,`severity`,`extra`,`disabled`,`name`) VALUES ('-1','%macros.component_alert = \"1\"','critical','{\"mute\":false,\"count\":\"-1\",\"delay\":\"300\"}',0,'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','(%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');