- remove locking in model, core framework should do that if required. (see Config->lock), deadlock issues in previous code (freezing UI)
- in settings controller, expand endpoints to one per type (in stead of "one to rule them all")
- in settings controller, align to base wrappers of ApiMutableModelControllerBase
- move extended validations to model, to avoid validation issues when another service would like to reuse the model
- align forms to standard wrappers (naming convention, simplifies code)
- remove "deleteRelations", if we need something like this at some point, it should be in the model, for now it seems safe enough to ditch
- ditch migration code, since it does the same as having a default value (defaults are always enforced during migrations)
** left UnitTest code for what it is now, probably needs changes too
This commit is contained in:
Ad Schellevis 2019-01-17 15:06:55 +01:00
parent 70c2e0e45e
commit df8d12c8a0
9 changed files with 388 additions and 913 deletions

View File

@ -2,7 +2,7 @@
/**
* Copyright (C) 2017-2018 EURO-LOG AG
*
* Copyright (c) 2019 Deciso B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -32,7 +32,6 @@ namespace OPNsense\Monit\Api;
use \OPNsense\Base\ApiMutableServiceControllerBase;
use \OPNsense\Core\Backend;
use \OPNsense\Monit\Monit;
/**
* Class ServiceController
@ -45,15 +44,6 @@ class ServiceController extends ApiMutableServiceControllerBase
protected static $internalServiceTemplate = 'OPNsense/Monit';
protected static $internalServiceName = 'monit';
/**
* initialize object properties
*/
public function onConstruct()
{
// initialize the model via parent to avoid selflock by circular references
$this->getModel();
}
/**
* test monit configuration
* @return array

View File

@ -2,7 +2,7 @@
/**
* Copyright (C) 2017-2019 EURO-LOG AG
*
* Copyright (c) 2019 Deciso B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -30,163 +30,18 @@
namespace OPNsense\Monit\Api;
use \OPNsense\Base\ApiControllerBase;
use \OPNsense\Base\ApiMutableModelControllerBase;
use \OPNsense\Core\Config;
use \OPNsense\Monit\Monit;
use \OPNsense\Base\UIModelGrid;
/**
* Class SettingsController
* @package OPNsense\Monit
*/
class SettingsController extends ApiControllerBase
class SettingsController extends ApiMutableModelControllerBase
{
/**
* @var null|object the monit model object
*/
public $mdlMonit = null;
/**
* @var array list with valid model node types
*/
private $nodeTypes = array('general', 'alert', 'service', 'test');
/**
* @var array with syntax information to build test conditions
*/
private $testSyntax = [
'serviceTestMapping' => [
'process' => ['Existence', 'Process Resource', 'Process Disk I/O', 'UID', 'GID', 'PID', 'PPID', 'Uptime', 'Connection', 'Custom'],
'file' => ['Existence', 'File Checksum', 'Timestamp', 'File Size', 'File Content', 'Permisssion', 'UID', 'GID', 'Custom'],
'fifo' => ['Existence', 'Timestamp', 'Permisssion', 'UID', 'GID', 'Custom'],
'filesystem' => ['Existence', 'Filesystem Mount Flags', 'Space Usage', 'Inode Usage', 'Disk I/O', 'Permisssion', 'Custom'],
'directory' => ['Existence', 'Timestamp', 'Permisssion', 'UID', 'GID', 'Custom'],
'host' => ['Network Ping', 'Connection', 'Custom'],
'system' => ['System Resource', 'Uptime', 'Custom'],
'custom' => ['Program Status', 'Custom'],
'network' => ['Network Interface', 'Custom']
],
'operators' => ['greater', 'less', 'equal', 'notequal'],
'units' => [
'timeUnits' => ['seconds', 'minutes', 'hours', 'days'],
'amountUnits' => ['byte', 'kilobyte', 'megabyte', 'gigabyte'],
'spaceAmountUnits' => ['byte', 'kilobyte', 'megabyte', 'gigabyte', 'percent'],
'diskAmountUnits' => ['byte', 'kilobyte', 'megabyte', 'gigabyte', 'operations'],
'netAmountUnits' => ['byte', 'kilobyte', 'megabyte', 'gigabyte', 'packets'],
],
'testConditionMapping' => [
'Existence' => [
'exist',
'not exist'
],
'System Resource' => [
'loadavg (1min)' => ['_OPERATOR' => ['_VALUE' => null]],
'loadavg (5min)' => ['_OPERATOR' => ['_VALUE' => null]],
'loadavg (15min)' => ['_OPERATOR' => ['_VALUE' => null]],
'cpu usage' => ['_OPERATOR' => ['_VALUE' => null, '_UNIT' => 'percent']],
'cpu user usage' => ['_OPERATOR' => ['_VALUE' => null, '_UNIT' => 'percent']],
'cpu system usage' => ['_OPERATOR' => ['_VALUE' => null, '_UNIT' => 'percent']],
'cpu wait usage' => ['_OPERATOR' => ['_VALUE' => null, '_UNIT' => 'percent']],
'memory usage' => ['_OPERATOR' => ['_VALUE' => null, '_UNIT' => 'percent']],
'swap usage' => ['_OPERATOR' => ['_VALUE' => null, '_UNIT' => 'percent']]
],
'Process Resource' => [
'cpu' => ['_OPERATOR' => ['_VALUE' => null, '_UNIT' => 'percent']],
'total cpu' => ['_OPERATOR' => ['_VALUE' => null, '_UNIT' => 'percent']],
'threads' => ['_OPERATOR' => ['_VALUE' => null]],
'children' => ['_OPERATOR' => ['_VALUE' => null]],
'memory usage' => ['_OPERATOR' => ['_VALUE' => null, '_UNIT' => 'amountUnits']],
'total memory usage' => ['_OPERATOR' => ['_VALUE' => null, '_UNIT' => 'percent']]
],
'Process Disk I/O' => [
'disk read rate' => ['_OPERATOR' => ['_VALUE' => null, '_UNIT' => 'operations', '_RATE' => '/s']],
'disk write rate' => ['_OPERATOR' => ['_VALUE' => null, '_UNIT' => 'operations', '_RATE' => '/s']]
],
'File Checksum' => [
'failed md5 checksum',
'changed md5 checksum',
'failed checksum expect' => ['_VALUE' => null]
],
'Timestamp' => [
'access time' => ['_OPERATOR' => ['_VALUE' => null, '_UNIT' => 'timeUnits']],
'modification time' => ['_OPERATOR' => ['_VALUE' => null, '_UNIT' => 'timeUnits']],
'change time' => ['_OPERATOR' => ['_VALUE' => null, '_UNIT' => 'timeUnits']],
'timestamp' => ['_OPERATOR' => ['_VALUE' => null, '_UNIT' => 'timeUnits']],
'changed access time',
'changed modification time',
'changed change time',
'changed timestamp'
],
'File Size' => [
'size' => ['_OPERATOR' => ['_VALUE' => null, '_UNIT' => 'amountUnits']],
'changed size'
],
'File Content' => [
'content =' => ['_VALUE' => null],
'content !=' => ['_VALUE' => null]
],
'Filesystem Mount Flags' => [
'changed fsflags'
],
'Space Usage' => [
'space' => ['_OPERATOR' => ['_VALUE' => null, '_UNIT' => 'spaceAmountUnits']],
'space free' => ['_OPERATOR' => ['_VALUE' => null, '_UNIT' => 'spaceAmountUnits']]
],
'Inode Usage' => [
'inodes' => ['_OPERATOR' => ['_VALUE' => null, '_UNIT' => 'amountUnits']],
'inodes free' => ['_OPERATOR' => ['_VALUE' => null, '_UNIT' => 'amountUnits']]
],
'Disk I/O' => [
'read rate' => ['_OPERATOR' => ['_VALUE' => null, '_UNIT' => 'diskAmountUnits', '_RATE' => '/s']],
'write rate' => ['_OPERATOR' => ['_VALUE' => null, '_UNIT' => 'diskAmountUnits', '_RATE' => '/s']],
'service time' => ['_OPERATOR' => ['_VALUE' => null, '_UNIT' => 'millisecond']]
],
'Permisssion' => [
'failed permission' => ['_VALUE' => null],
'changed permission'
],
'UID' => [
'failed uid' => ['_VALUE' => null]
],
'GID' => [
'failed uid' => ['_VALUE' => null]
],
'PID' => [
'changed pid'
],
'PPID' => [
'changed ppid'
],
'Uptime' => [
'uptime' => ['_OPERATOR' => ['_VALUE' => null, '_UNIT' => 'timeUnits']]
],
'Program Status' => [
'status' => ['_OPERATOR' => ['_VALUE' => null]],
'changed status'
],
'Network Interface' => [
'failed link',
'changed link capacity',
'saturation' => ['_OPERATOR' => ['_VALUE' => null, '_UNIT' => 'percent']],
'upload' => ['_OPERATOR' => ['_VALUE' => null, '_UNIT' => 'netAmountUnits', '_RATE' => '/s']],
'download' => ['_OPERATOR' => ['_VALUE' => null, '_UNIT' => 'netAmountUnits', '_RATE' => '/s']],
],
'Network Ping' => [
'failed ping4',
'failed ping6'
],
'Custom' => null
]
];
/**
* initialize object properties
*/
public function onConstruct()
{
$this->mdlMonit = new Monit();
}
protected static $internalModelName = 'monit';
protected static $internalModelClass = 'OPNsense\Monit\Monit';
/**
* check if changes to the monit settings were made
@ -195,224 +50,216 @@ class SettingsController extends ApiControllerBase
public function dirtyAction()
{
$result = array('status' => 'ok');
$result['monit']['dirty'] = $this->mdlMonit->configChanged();
$result['monit']['dirty'] = $this->getModel()->configChanged();
return $result;
}
/**
* query monit settings
* @param $nodeType
* @param $uuid
* @param $syntax
* @return array result
* @throws \Exception
* Retrieve alert settings or return defaults
* @param $uuid item unique id
* @return array monit alert content
* @throws \ReflectionException when not bound to model
*/
public function getAction($nodeType = null, $uuid = null, $syntax = null)
public function getAlertAction($uuid = null)
{
$result = array("result" => "failed");
if ($syntax != null) {
$result['syntax'] = $this->testSyntax;
$result['result'] = 'ok';
return $result;
}
if ($this->request->isGet() && $nodeType != null) {
$this->validateNodeType($nodeType);
if ($nodeType == 'general') {
$node = $this->mdlMonit->getNodeByReference($nodeType);
} else {
if ($uuid != null) {
$node = $this->mdlMonit->getNodeByReference($nodeType . '.' . $uuid);
} else {
$node = $this->mdlMonit->$nodeType->Add();
}
}
if ($node != null) {
$result['monit'] = array($nodeType => $node->getNodes());
$result['result'] = 'ok';
}
}
return $result;
return $this->getBase("alert", "alert", $uuid);
}
/**
* set monit properties
* @param $nodeType
* @param $uuid
* @return array status
* @throws \Exception
* Update alert with given properties
* @param string $uuid internal id
* @return array save result + validation output
* @throws \Phalcon\Validation\Exception when field validations fail
* @throws \ReflectionException when not bound to model
*/
public function setAction($nodeType = null, $uuid = null)
public function setAlertAction($uuid)
{
$result = array("result" => "failed", "validations" => array());
if ($this->request->isPost() && $this->request->hasPost("monit") && $nodeType != null) {
$this->validateNodeType($nodeType);
if ($nodeType == 'general') {
$node = $this->mdlMonit->getNodeByReference($nodeType);
} else {
if ($uuid != null) {
$node = $this->mdlMonit->getNodeByReference($nodeType . '.' . $uuid);
} else {
$node = $this->mdlMonit->$nodeType->Add();
}
}
if ($node != null) {
$monitInfo = $this->request->getPost("monit");
// perform plugin specific validations
if ($nodeType == 'service') {
$tests = explode(',', $monitInfo[$nodeType]['tests']);
foreach ($tests as $testUUID) {
$test = $this->mdlMonit->getNodeByReference('test.' . $testUUID);
if ($test != null) {
$testName = $test->name->__toString();
$testType = $test->type->getNodeData()[$test->type->__toString()]['value'];
if (array_search($testType, $this->testSyntax['serviceTestMapping'][$monitInfo[$nodeType]['type']]) === false) {
$result["validations"]['monit.service.tests'] = "Test " . $testName . ' with type ' . $testType . ' not allowed for service type ' . $monitInfo[$nodeType]['type'];
}
}
}
switch ($monitInfo[$nodeType]['type']) {
case 'process':
if (empty($monitInfo[$nodeType]['pidfile']) && empty($monitInfo[$nodeType]['match'])) {
$result["validations"]['monit.service.pidfile'] = "Please set at least one of Pidfile or Match.";
$result["validations"]['monit.service.match'] = $result["validations"]['monit.service.pidfile'];
}
break;
case 'host':
if (empty($monitInfo[$nodeType]['address'])) {
$result["validations"]['monit.service.address'] = "Address is mandatory for 'Remote Host' checks.";
}
break;
case 'network':
if (empty($monitInfo[$nodeType]['address']) && empty($monitInfo[$nodeType]['interface'])) {
$result["validations"]['monit.service.address'] = "Please set at least one of Address or Interface.";
$result["validations"]['monit.service.interface'] = $result["validations"]['monit.service.address'];
}
break;
case 'system':
break;
default:
if (empty($monitInfo[$nodeType]['path'])) {
$result["validations"]['monit.service.path'] = "Path is mandatory.";
}
}
}
$node->setNodes($monitInfo[$nodeType]);
$valMsgs = $this->mdlMonit->performValidation();
foreach ($valMsgs as $field => $msg) {
$fieldnm = str_replace($node->__reference, "monit." . $nodeType, $msg->getField());
$result["validations"][$fieldnm] = $msg->getMessage();
}
if (empty($result["validations"])) {
unset($result["validations"]);
$result['result'] = 'ok';
$this->mdlMonit->serializeToConfig();
Config::getInstance()->save();
if ($this->mdlMonit->configDirty()) {
$result['status'] = 'ok';
}
}
}
}
return $result;
return $this->setBase("alert", "alert", $uuid);
}
/**
* delete monit settings
* @param $nodeType
* @param $uuid
* @return array status
* @throws \Exception
* Add alert with given properties
* @return array save result + validation output
* @throws \Phalcon\Validation\Exception when field validations fail
* @throws \ReflectionException when not bound to model
*/
public function delAction($nodeType = null, $uuid = null)
public function addAlertAction()
{
$result = array("result" => "failed");
if ($nodeType != null) {
$this->validateNodeType($nodeType);
if ($uuid != null) {
$node = $this->mdlMonit->getNodeByReference($nodeType . '.' . $uuid);
if ($node != null) {
if ($this->mdlMonit->$nodeType->del($uuid) == true) {
// delete relations
if ($nodeType == 'test') {
$nodeName = $this->mdlMonit->getNodeByReference($nodeType . '.' . $uuid . '.name');
if ($nodeName != null) {
$nodeName = $nodeName->__toString();
$this->deleteRelations('service', 'tests', $uuid, 'test', $nodeName, $this->mdlMonit);
}
}
$this->mdlMonit->serializeToConfig();
Config::getInstance()->save();
if ($this->mdlMonit->configDirty()) {
$result['status'] = 'ok';
}
}
}
}
}
return $result;
return $this->addBase("alert", "alert");
}
/**
* toggle monit items (enable/disable)
* @param $nodeType
* @param $uuid
* @return array result
* Delete alert by uuid
* @param string $uuid internal id
* @return array save status
* @throws \Phalcon\Validation\Exception when field validations fail
* @throws \ReflectionException when not bound to model
*/
public function toggleAction($nodeType = null, $uuid = null)
public function delAlertAction($uuid)
{
$result = array("result" => "failed");
if ($this->request->isPost() && $nodeType != null) {
if ($uuid != null) {
$node = $this->mdlMonit->getNodeByReference($nodeType . '.' . $uuid);
if ($node != null) {
if ($node->enabled->__toString() == "1") {
$node->enabled = "0";
} else {
$node->enabled = "1";
}
$this->mdlMonit->serializeToConfig();
Config::getInstance()->save();
if ($this->mdlMonit->configDirty()) {
$result['status'] = 'ok';
}
} else {
$result['result'] = "not found";
}
} else {
$result['result'] = "uuid not given";
}
}
return $result;
return $this->delBase("alert", $uuid);
}
/**
* search monit settings
* @param $nodeType
* @return array result
* @throws \Exception
* Search monit alerts
* @return array list of found alerts
* @throws \ReflectionException when not bound to model
*/
public function searchAction($nodeType = null)
public function searchAlertAction()
{
$this->sessionClose();
if ($this->request->isPost() && $nodeType != null) {
$this->validateNodeType($nodeType);
$grid = new UIModelGrid($this->mdlMonit->$nodeType);
$fields = array();
switch ($nodeType) {
case 'alert':
$fields = array("enabled", "recipient", "noton", "events", "description");
break;
case 'service':
$fields = array("enabled", "name", "type");
break;
case 'test':
$fields = array("name", "type", "condition", "action");
break;
}
return $grid->fetchBindRequest($this->request, $fields);
}
return $this->searchBase(
"alert",
array("enabled", "recipient", "noton", "events", "description"),
"description"
);
}
/**
* Toggle alert defined by uuid (enable/disable)
* @param $uuid alert internal id
* @param $enabled desired state enabled(1)/disabled(1), leave empty for toggle
* @return array save result
* @throws \Phalcon\Validation\Exception when field validations fail
* @throws \ReflectionException when not bound to model
*/
public function toggleAlertAction($uuid, $enabled = null)
{
return $this->toggleBase("alert", $uuid, $enabled);
}
/**
* Retrieve service settings or return defaults
* @param $uuid item unique id
* @return array monit service content
* @throws \ReflectionException when not bound to model
*/
public function getServiceAction($uuid = null)
{
return $this->getBase("service", "service", $uuid);
}
/**
* Update service with given properties
* @param string $uuid internal id
* @return array save result + validation output
* @throws \Phalcon\Validation\Exception when field validations fail
* @throws \ReflectionException when not bound to model
*/
public function setServiceAction($uuid)
{
return $this->setBase("service", "service", $uuid);
}
/**
* Add service with given properties
* @return array save result + validation output
* @throws \Phalcon\Validation\Exception when field validations fail
* @throws \ReflectionException when not bound to model
*/
public function addServiceAction()
{
return $this->addBase("service", "service");
}
/**
* Delete service by uuid
* @param string $uuid internal id
* @return array save status
* @throws \Phalcon\Validation\Exception when field validations fail
* @throws \ReflectionException when not bound to model
*/
public function delServiceAction($uuid)
{
return $this->delBase("service", $uuid);
}
/**
* Search monit services
* @return array list of found services
* @throws \ReflectionException when not bound to model
*/
public function searchServiceAction()
{
return $this->searchBase("service", array("enabled", "name", "type"), "name");
}
/**
* Toggle service defined by uuid (enable/disable)
* @param $uuid service internal id
* @param $enabled desired state enabled(1)/disabled(1), leave empty for toggle
* @return array save result
* @throws \Phalcon\Validation\Exception when field validations fail
* @throws \ReflectionException when not bound to model
*/
public function toggleServiceAction($uuid, $enabled = null)
{
return $this->toggleBase("service", $uuid, $enabled);
}
/**
* Retrieve test settings or return defaults
* @param $uuid item unique id
* @return array monit test content
* @throws \ReflectionException when not bound to model
*/
public function getTestAction($uuid = null)
{
return $this->getBase("test", "test", $uuid);
}
/**
* Update test with given properties
* @param string $uuid internal id
* @return array save result + validation output
* @throws \Phalcon\Validation\Exception when field validations fail
* @throws \ReflectionException when not bound to model
*/
public function setTestAction($uuid)
{
return $this->setBase("test", "test", $uuid);
}
/**
* Add test with given properties
* @return array save result + validation output
* @throws \Phalcon\Validation\Exception when field validations fail
* @throws \ReflectionException when not bound to model
*/
public function addTestAction()
{
return $this->addBase("test", "test");
}
/**
* Delete test by uuid
* @param string $uuid internal id
* @return array save status
* @throws \Phalcon\Validation\Exception when field validations fail
* @throws \ReflectionException when not bound to model
*/
public function delTestAction($uuid)
{
return $this->delBase("test", $uuid);
}
/**
* Search monit services
* @return array list of found services
* @throws \ReflectionException when not bound to model
*/
public function searchTestAction()
{
return $this->searchBase("test", array("name", "condition", "action"), "name");
}
/**
* Retrieve general settings
* @return array monit general settings content
* @throws \ReflectionException when not bound to model
*/
public function getGeneralAction()
{
return ['monit' => $this->getModel()->general->getNodes(), 'result' => 'ok'];
}
/**
@ -427,7 +274,7 @@ class SettingsController extends ApiControllerBase
$cfg = Config::getInstance();
$cfgObj = $cfg->object();
$node = $this->mdlMonit->getNodeByReference('general');
$node = $this->getModel()->getNodeByReference('general');
$generalSettings = array();
// inherit SMTP settings from System->Settings->Notifications
@ -448,72 +295,19 @@ class SettingsController extends ApiControllerBase
// apply them
$node->setNodes($generalSettings);
$valMsgs = $this->mdlMonit->performValidation();
$valMsgs = $this->getModel()->performValidation();
foreach ($valMsgs as $field => $msg) {
$fieldnm = str_replace($node->__reference, "monit.general.", $msg->getField());
$result["validations"][$fieldnm] = $msg->getMessage();
}
if (empty($result["validations"])) {
unset($result["validations"]);
$this->mdlMonit->serializeToConfig();
$this->getModel()->serializeToConfig();
Config::getInstance()->save();
if ($this->mdlMonit->configDirty()) {
$result['status'] = 'ok';
$result['result'] = 'OK';
}
$result['status'] = 'ok';
$result['result'] = 'OK';
}
}
return $result;
}
/**
* validate nodeType
* @param $nodeType
* @throws \Exception
*/
private function validateNodeType($nodeType = null)
{
if (array_search($nodeType, $this->nodeTypes) === false) {
throw new \Exception('unknown nodeType: ' . $nodeType);
}
}
/**
* delete relations
* @param string|null $nodeType
* @param string|null $nodeField
* @param string|null $relUuid
* @param string|null $relNodeType
* @param string|null $relNodeName
* @throws \Exception
*/
private function deleteRelations(
$nodeType = null,
$nodeField = null,
$relUuid = null,
$relNodeType = null,
$relNodeName = null
) {
$nodes = $this->mdlMonit->$nodeType->getNodes();
// get nodes with relations
foreach ($nodes as $nodeUuid => $node) {
// get relation uuids
foreach ($node[$nodeField] as $fieldUuid => $field) {
// remove uuid from field
if ($fieldUuid == $relUuid) {
$refField = $nodeType . '.' . $nodeUuid . '.' . $nodeField;
$relNode = $this->mdlMonit->getNodeByReference($refField);
$nodeRels = str_replace($relUuid, '', $relNode->__toString());
$nodeRels = str_replace(',,', ',', $nodeRels);
$nodeRels = rtrim($nodeRels, ',');
$nodeRels = ltrim($nodeRels, ',');
$this->mdlMonit->setNodeByReference($refField, $nodeRels);
if ($relNode->isEmptyAndRequired()) {
$nodeName = $this->mdlMonit->getNodeByReference($nodeType . '.' . $nodeUuid . '.name')->__toString();
throw new \Exception("Cannot delete $relNodeType '$relNodeName' from $nodeType '$nodeName'");
}
}
}
}
}
}

View File

@ -1,43 +1,43 @@
<form>
<field>
<id>monit.alert.enabled</id>
<id>alert.enabled</id>
<label>Enable alert</label>
<type>checkbox</type>
<help><![CDATA[Enable or disable alert.]]></help>
</field>
<field>
<id>monit.alert.recipient</id>
<id>alert.recipient</id>
<label>Recipient</label>
<type>text</type>
<help><![CDATA[The email address to send alerts to.]]></help>
</field>
<field>
<id>monit.alert.noton</id>
<id>alert.noton</id>
<label>Not on</label>
<type>checkbox</type>
<help><![CDATA[Do not send alerts for the following events but on all others.]]></help>
</field>
<field>
<id>monit.alert.events</id>
<id>alert.events</id>
<label>Events</label>
<type>select_multiple</type>
<nbDropdownElements>28</nbDropdownElements>
<help><![CDATA[List with events. Leave it empty for all events.]]></help>
</field>
<field>
<id>monit.alert.format</id>
<id>alert.format</id>
<label>Mail format</label>
<type>textbox</type>
<help><![CDATA[The email format for alerts.<br><i>Subject: $SERVICE on $HOST failed</i>]]></help>
</field>
<field>
<id>monit.alert.reminder</id>
<id>alert.reminder</id>
<label>Reminder</label>
<type>text</type>
<help><![CDATA[Send a reminder after some cycles]]></help>
</field>
<field>
<id>monit.alert.description</id>
<id>alert.description</id>
<label>Description</label>
<type>text</type>
</field>

View File

@ -1,85 +1,85 @@
<form>
<field>
<id>monit.service.enabled</id>
<id>service.enabled</id>
<label>Enable service checks</label>
<type>checkbox</type>
<help><![CDATA[Enable or disable service checks.]]></help>
</field>
<field>
<id>monit.service.name</id>
<id>service.name</id>
<label>Name</label>
<type>text</type>
<help><![CDATA[The name of the service.]]></help>
</field>
<field>
<id>monit.service.type</id>
<id>service.type</id>
<label>Type</label>
<type>dropdown</type>
<help><![CDATA[The service check type.]]></help>
</field>
<field>
<id>monit.service.pidfile</id>
<id>service.pidfile</id>
<label>PID File</label>
<type>text</type>
<help><![CDATA[The PID file of the process.]]></help>
</field>
<field>
<id>monit.service.match</id>
<id>service.match</id>
<label>Match</label>
<type>text</type>
<help><![CDATA[Find the process by regular expression. Test your pattern with <br><b>monit procmatch &lt;PATTERN&gt;</b>]]></help>
</field>
<field>
<id>monit.service.path</id>
<id>service.path</id>
<label>Path</label>
<type>text</type>
<help><![CDATA[According to the service type path can be a file or a directory.]]></help>
</field>
<field>
<id>monit.service.timeout</id>
<id>service.timeout</id>
<label>Program Timeout</label>
<type>text</type>
<advanced>true</advanced>
<help><![CDATA[The timeout for custom program checks.]]></help>
</field>
<field>
<id>monit.service.address</id>
<id>service.address</id>
<label>Address</label>
<type>text</type>
<help><![CDATA[The target IP address for 'Remote Host' and 'Network' checks.]]></help>
</field>
<field>
<id>monit.service.interface</id>
<id>service.interface</id>
<label>Interface</label>
<type>dropdown</type>
<help><![CDATA[The Interface for 'Network' checks.]]></help>
</field>
<field>
<id>monit.service.start</id>
<id>service.start</id>
<label>Start</label>
<type>text</type>
<help><![CDATA[The start script of the service.]]></help>
</field>
<field>
<id>monit.service.stop</id>
<id>service.stop</id>
<label>Stop</label>
<type>text</type>
<help><![CDATA[The stop script of the service.]]></help>
</field>
<field>
<id>monit.service.tests</id>
<id>service.tests</id>
<label>Tests</label>
<type>select_multiple</type>
<help><![CDATA[This is a list with service tests.]]></help>
</field>
<field>
<id>monit.service.depends</id>
<id>service.depends</id>
<label>Depends</label>
<type>select_multiple</type>
<help><![CDATA[Optionally define a (list of) service(s) which are required before monitoring this one, if any of the dependencies are either stopped or unmonitored this service will stop/unmonitor too.]]></help>
</field>
<field>
<id>monit.service.description</id>
<id>service.description</id>
<label>Description</label>
<type>text</type>
</field>

View File

@ -1,30 +1,30 @@
<form>
<field>
<id>monit.test.name</id>
<id>test.name</id>
<label>Name</label>
<type>text</type>
<help><![CDATA[The name of the test.]]></help>
</field>
<field>
<id>monit.test.type</id>
<id>test.type</id>
<label>Type</label>
<type>dropdown</type>
<help><![CDATA[The type of the test.]]></help>
</field>
<field>
<id>monit.test.condition</id>
<id>test.condition</id>
<label>Condition</label>
<type>text</type>
<help><![CDATA[The test condition. E.g.<br><i>cpu is greater than 50%</i><br><i>failed host 127.0.0.1 port 22 protocol ssh</i><br>]]></help>
</field>
<field>
<id>monit.test.action</id>
<id>test.action</id>
<label>Action</label>
<type>dropdown</type>
<help><![CDATA[Simply <i>alert</i> or <i>restart</i> the service or <i>execute</i> a program.]]></help>
</field>
<field>
<id>monit.test.path</id>
<id>test.path</id>
<label>Path</label>
<type>text</type>
<help><![CDATA[Make sure the script is executable by the Monit service.]]></help>

View File

@ -1,44 +0,0 @@
<?php
/**
* Copyright (C) 2017-2019 EURO-LOG AG
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
namespace OPNsense\Monit\Migrations;
use OPNsense\Base\BaseModelMigration;
class M1_0_8 extends BaseModelMigration
{
public function run($model)
{
foreach ($model->getNodeByReference('test')->__items as $test) {
$test->type = 'Custom';
}
}
}

View File

@ -1,32 +1,32 @@
<?php
/**
* Copyright (C) 2016 EURO-LOG AG
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
/*
* Copyright (C) 2016 EURO-LOG AG
* Copyright (c) 2019 Deciso B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\Monit;
@ -39,54 +39,126 @@ use OPNsense\Base\BaseModel;
class Monit extends BaseModel
{
/**
* @var resource|null holds the file handle for the lock file
*
*/
private $internalLockHandle = null;
private $testSyntax = [
'process' => ['Existence', 'Process Resource', 'Process Disk I/O',
'UID', 'GID', 'PID', 'PPID', 'Uptime', 'Connection', 'Custom'],
'file' => ['Existence', 'File Checksum', 'Timestamp', 'File Size',
'File Content', 'Permisssion', 'UID', 'GID', 'Custom'],
'fifo' => ['Existence', 'Timestamp', 'Permisssion', 'UID', 'GID', 'Custom'],
'filesystem' => ['Existence', 'Filesystem Mount Flags',
'Space Usage', 'Inode Usage', 'Disk I/O', 'Permisssion', 'Custom'],
'directory' => ['Existence', 'Timestamp', 'Permisssion', 'UID', 'GID', 'Custom'],
'host' => ['Network Ping', 'Connection', 'Custom'],
'system' => ['System Resource', 'Uptime', 'Custom'],
'custom' => ['Program Status', 'Custom'],
'network' => ['Network Interface', 'Custom']
];
/**
* lock the model to avoid issues with concurrent access
* @throws \Exception
* validate full model using all fields and data in a single (1 deep) array
* @param bool $validateFullModel validate full model or only changed fields
* @return \Phalcon\Validation\Message\Group
*/
public function __construct($lock = true)
{
if ($lock == true) {
$this->requestLock();
$this->internalLockHandle = fopen("/tmp/monit.lock", "w+");
if (is_resource($this->internalLockHandle) == false || flock($this->internalLockHandle, LOCK_EX) == false) {
throw new \Exception("Cannot lock monit model");
public function performValidation($validateFullModel = false) {
// standard model validations
$messages = parent::performValidation($validateFullModel);
$all_nodes = $this->getFlatNodes();
foreach ($all_nodes as $key => $node) {
if ($validateFullModel || $node->isFieldChanged()) {
// the item container may have different validations attached.
$parentNode = $node->getParentNode();
// perform plugin specific validations
switch ($parentNode->getInternalXMLTagName()) {
case 'service':
// service type node validations
switch ($node->getInternalXMLTagName()) {
case 'tests':
// test dependencies defined in $this->testSyntax
foreach (explode(',', (string)$parentNode->tests) as $testUUID) {
$test = $this->getNodeByReference('test.' . $testUUID);
if ($test != null) {
if (!empty($this->testSyntax[(string)$parentNode->type])) {
$options = $this->testSyntax[(string)$parentNode->type];
if (!in_array((string)$test->type, $options)) {
$validationMsg = sprintf(
gettext("Test %s with type %s not allowed for this service type"),
(string)$test->name,
$test->type->getNodeData()[(string)$test->type]['value']
);
$messages->appendMessage(
new \Phalcon\Validation\Message($validationMsg, $key)
);
}
}
}
}
break;
case 'pidfile':
if (empty((string)$node) && (string)$parentNode->type == 'process'
&& empty((string)$parentNode->match)) {
$messages->appendMessage(new \Phalcon\Validation\Message(
gettext("Please set at least one of Pidfile or Match."), $key
));
}
break;
case 'match':
if (empty((string)$node) && (string)$parentNode->type == 'process'
&& empty((string)$parentNode->pidfile)) {
$messages->appendMessage(new \Phalcon\Validation\Message(
gettext("Please set at least one of Pidfile or Match."), $key
));
}
break;
case 'address':
if (empty((string)$node) && (string)$parentNode->type == 'host') {
$messages->appendMessage(new \Phalcon\Validation\Message(
gettext("Address is mandatory for 'Remote Host' checks."), $key
));
} elseif (empty((string)$node) && (string)$parentNode->type == 'network'
&& empty((string)$parentNode->interface) ) {
$messages->appendMessage(new \Phalcon\Validation\Message(
gettext("Please set at least one of Address or Interface."), $key
));
}
break;
case 'interface':
if (empty((string)$node) && (string)$parentNode->type == 'network'
&& empty((string)$parentNode->address) ) {
$messages->appendMessage(new \Phalcon\Validation\Message(
gettext("Please set at least one of Address or Interface."), $key
));
}
break;
case 'path':
if (empty((string)$node) && in_array((string)$parentNode->type,
['file', 'fifo', 'filesystem', 'directory'])) {
$messages->appendMessage(new \Phalcon\Validation\Message(
gettext("Path is mandatory."), $key
));
}
break;
}
break;
default:
break;
}
}
}
parent::__construct();
return $messages;
}
/**
* release lock after usage
* mark configuration as changed when data is pushed back to the config
*/
public function __destruct()
public function serializeToConfig($validateFullModel = false, $disable_validation = false)
{
$this->releaseLock();
@touch("/tmp/monit.dirty");
return parent::performValidation($validateFullModel, $disable_validation);
}
/**
* lock the model
*/
public function requestLock()
{
$this->internalLockHandle = fopen("/tmp/monit.lock", "w+");
if (is_resource($this->internalLockHandle) == false || flock($this->internalLockHandle, LOCK_EX) == false) {
throw new \Exception("Cannot lock monit model");
}
}
/**
* release lock
*/
public function releaseLock()
{
if (is_resource($this->internalLockHandle)) {
flock($this->internalLockHandle, LOCK_UN);
fclose($this->internalLockHandle);
}
}
/**
* get configuration state
@ -97,15 +169,6 @@ class Monit extends BaseModel
return file_exists("/tmp/monit.dirty");
}
/**
* mark configuration as changed
* @return bool
*/
public function configDirty()
{
return @touch("/tmp/monit.dirty");
}
/**
* mark configuration as consistent with the running config
* @return bool

View File

@ -277,6 +277,7 @@
</name>
<type type="OptionField">
<Required>Y</Required>
<default>Custom</default>
<OptionValues>
<Existence>Existence</Existence>
<SystemResource>System Resource</SystemResource>

View File

@ -1,6 +1,7 @@
{#
Copyright © 2017-2019 by EURO-LOG AG
Copyright (c) 2017-2018 by EURO-LOG AG
Copyright (c) 2019 Deciso B.V.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
@ -28,7 +29,6 @@ POSSIBILITY OF SUCH DAMAGE.
<script>
$( document ).ready(function() {
/**
* get the isSubsystemDirty value and print a notice
*/
@ -94,20 +94,20 @@ POSSIBILITY OF SUCH DAMAGE.
$('#btnImportSystemNotificationProgress').removeClass("fa fa-spinner fa-pulse");
$('#btnImportSystemNotification').blur();
ajaxCall("/api/monit/service/status", {}, function(data,status) {
mapDataToFormUI({'frm_GeneralSettings':"/api/monit/settings/get/general/"}).done(function(){
mapDataToFormUI({'frm_GeneralSettings':"/api/monit/settings/getGeneral/"}).done(function(){
formatTokenizersUI();
$('.selectpicker').selectpicker('refresh');
isSubsystemDirty();
updateServiceControlUI('monit');
});
});
});
});
});
});
/**
* general settings and syntax
* general settings
*/
mapDataToFormUI({'frm_GeneralSettings':"/api/monit/settings/get/general"}).done(function(){
mapDataToFormUI({'frm_GeneralSettings':"/api/monit/settings/getGeneral/"}).done(function(){
formatTokenizersUI();
$('.selectpicker').selectpicker('refresh');
isSubsystemDirty();
@ -163,12 +163,12 @@ POSSIBILITY OF SUCH DAMAGE.
* alert settings
*/
$("#grid-alerts").UIBootgrid({
'search':'/api/monit/settings/search/alert/',
'get':'/api/monit/settings/get/alert/',
'set':'/api/monit/settings/set/alert/',
'add':'/api/monit/settings/set/alert/',
'del':'/api/monit/settings/del/alert/',
'toggle':'/api/monit/settings/toggle/alert/'
'search':'/api/monit/settings/searchAlert/',
'get':'/api/monit/settings/getAlert/',
'set':'/api/monit/settings/setAlert/',
'add':'/api/monit/settings/addAlert/',
'del':'/api/monit/settings/delAlert/',
'toggle':'/api/monit/settings/toggleAlert/'
});
/**
@ -229,63 +229,22 @@ POSSIBILITY OF SUCH DAMAGE.
$('tr[id="row_monit.service.timeout"]').removeClass('hidden');
}
};
function SelectServiceTests(){
var serviceType = $('#monit\\.service\\.type').val();
$('#monit\\.service\\.tests').html('');
$.each(serviceTests, function(index, value) {
if ($.inArray(value.type, testSyntax.serviceTestMapping[serviceType]) !== -1) {
$('#monit\\.service\\.tests').append('<option value="' + value.uuid + '">' + value.name + '</option>');
}
});
$('.selectpicker').selectpicker('refresh');
}
/**
* get service test definitions
*/
var serviceTests = [];
$('#DialogEditService').on('shown.bs.modal', function() {
$.post("/api/monit/settings/search/test", {current: 1, rowCount: -1}, function(data, status) {
if (status == "success") {
serviceTests = Object.assign({}, data.rows);
var serviceType = $('#monit\\.service\\.type').val();
// filter test option list
$('#monit\\.service\\.tests > option').each(function(index, option){
$.each(serviceTests, function(index, value) {
if (value.uuid === option.value) {
if ($.inArray(value.type, testSyntax.serviceTestMapping[serviceType]) === -1) {
option.remove();
}
return false;
}
});
});
$('.selectpicker').selectpicker('refresh');
}
});
ShowHideFields();
});
$('#monit\\.service\\.type').on('changed.bs.select', function() {
SelectServiceTests();
ShowHideFields();
});
$('#DialogEditService').on('shown.bs.modal', function() {ShowHideFields();});
$('#monit\\.service\\.type').on('changed.bs.select', function(e) {ShowHideFields();});
$('#monit\\.service\\.pidfile').on('input', function() {ShowHideFields();});
$('#monit\\.service\\.match').on('input', function() {ShowHideFields();});
$('#monit\\.service\\.path').on('input', function() {ShowHideFields();});
$('#monit\\.service\\.timeout').on('input', function() {ShowHideFields();});
$('#monit\\.service\\.address').on('input', function() {ShowHideFields();});
$('#monit\\.service\\.interface').on('changed.bs.select', function() {ShowHideFields();});
$('#monit\\.service\\.interface').on('changed.bs.select', function(e) {ShowHideFields();});
$("#grid-services").UIBootgrid({
'search':'/api/monit/settings/search/service/',
'get':'/api/monit/settings/get/service/',
'set':'/api/monit/settings/set/service/',
'add':'/api/monit/settings/set/service/',
'search':'/api/monit/settings/searchService/',
'get':'/api/monit/settings/getService/',
'set':'/api/monit/settings/setService/',
'add':'/api/monit/settings/addService/',
'del':'/api/monit/settings/del/service/',
'toggle':'/api/monit/settings/toggle/service/'
'toggle':'/api/monit/settings/toggleService/'
});
@ -293,311 +252,24 @@ POSSIBILITY OF SUCH DAMAGE.
* service test settings
*/
// parse monit.test.condition and split it into testConditionStages array
// the testConditionStages array is used to build the #monit_test_condition_form
var testConditionStages = [];
function ParseTestCondition(conditionString = null) {
var conditionType = $('#monit\\.test\\.type option:selected').html();
// old behaviour for Custom types needs no parsing
if (conditionType === 'Custom') {
return;
}
if (conditionString === null) {
conditionString = $('#monit\\.test\\.condition').val();
}
// simple array, e.g 'Existence'
if ($.type(testSyntax.testConditionMapping[conditionType]) === 'array') {
// defaults to the first element
if (conditionString === '') {
testConditionStages[0] = testSyntax.testConditionMapping[conditionType][0];
} else {
$.each(testSyntax.testConditionMapping[conditionType], function(index, value) {
var regExp = new RegExp("^" + value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i');
if (conditionString !== undefined && regExp.test(conditionString)) {
testConditionStages[0] = value;
// first match is sufficient since elements are unique
return false;
}
});
}
}
// objects, e.g. 'System Resource'
if ($.type(testSyntax.testConditionMapping[conditionType]) === 'object') {
if (conditionString === '') {
if ($.type(testSyntax.testConditionMapping[conditionType][0]) === 'string') {
testConditionStages[0] = testSyntax.testConditionMapping[conditionType][0];
} else {
testConditionStages[0] = Object.keys(testSyntax.testConditionMapping[conditionType])[0];
}
} else {
// get the longest key match
var keyLength = 0;
$.each(testSyntax.testConditionMapping[conditionType], function(key, value) {
if ($.isNumeric(key)) {
key = value;
}
// escape metacharacters with replace() e.g. ^loadavg \(1min\)
var regExp = new RegExp(key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i');
if (regExp.test(conditionString)) {
if (key.length > keyLength) {
keyLength = key.length;
testConditionStages[0] = key;
// no break of the $.each loop because we need the longest match here
}
}
});
}
if ($.type(testSyntax.testConditionMapping[conditionType][testConditionStages[0]]) === 'object') {
if (Object.keys(testSyntax.testConditionMapping[conditionType][testConditionStages[0]])[0] === '_OPERATOR') {
if (conditionString === '') {
testConditionStages[1] = testSyntax.operators[0];
} else {
$.each(testSyntax.operators, function(index, value) {
var regExp = new RegExp(value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i');
if (regExp.test(conditionString)) {
testConditionStages[1] = value;
// first match is sufficient since operators are unique
return false;
}
});
}
if (Object.keys(testSyntax.testConditionMapping[conditionType][testConditionStages[0]]['_OPERATOR'])[0] === '_VALUE') {
if (conditionString === '') {
testConditionStages[2] = '';
} else {
if (testConditionStages[1] === undefined) {
testConditionStages[1] = testSyntax.operators[0];
}
var regExp = new RegExp(testConditionStages[0].replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '.*' + testConditionStages[1].replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '[^\\d]+(\\d+)');
var regRes = conditionString.match(regExp);
if($.type(regRes) === 'array') {
testConditionStages[2] = regRes[1];
} else {
testConditionStages[2] = '';
}
}
if (Object.keys(testSyntax.testConditionMapping[conditionType][testConditionStages[0]]['_OPERATOR'])[1] === '_UNIT') {
if (conditionString === '') {
var unitType = testSyntax.testConditionMapping[conditionType][testConditionStages[0]]['_OPERATOR']['_UNIT'];
if (unitType in testSyntax.units) {
testConditionStages[3] = testSyntax.units[unitType][0];
} else {
testConditionStages[3] = unitType;
}
} else {
var regExp = new RegExp(testConditionStages[0].replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '.*' + testConditionStages[1].replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '[^\\d]+\\d+\\s+([^\/]+)');
var regRes = conditionString.match(regExp);
var unitType = testSyntax.testConditionMapping[conditionType][testConditionStages[0]]['_OPERATOR']['_UNIT'];
testConditionStages[3] = undefined;
if($.type(regRes) === 'array') {
if (unitType in testSyntax.units) {
$.each(testSyntax.units.unitType, function(index, value) {
var regExp = new RegExp(value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i');
if (regExp.test(regRes[1])) {
testConditionStages[3] = value;
// first match is sufficient since units are unique
return false;
}
});
} else {
testConditionStages[3] = unitType;
}
}
if ( testConditionStages[3] === undefined) {
if (unitType in testSyntax.units) {
testConditionStages[3] = testSyntax.units[unitType][0];
} else {
testConditionStages[3] = unitType;
}
}
}
if (Object.keys(testSyntax.testConditionMapping[conditionType][testConditionStages[0]]['_OPERATOR'])[2] === '_RATE') {
testConditionStages[4] = testSyntax.testConditionMapping[conditionType][testConditionStages[0]]['_OPERATOR']['_RATE'];
}
}
}
} else if (Object.keys(testSyntax.testConditionMapping[conditionType][testConditionStages[0]])[0] === '_VALUE') {
if (conditionString === '') {
testConditionStages[1] = '';
} else {
var regExp = new RegExp(testConditionStages[0].replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '.*' + testConditionStages[1].replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '[^\\s]+(.*)');
var regRes = conditionString.match(regExp);
if($.type(regRes) === 'array') {
testConditionStages[1] = regRes[1];
}
}
}
}
}
};
function setStageSelectOnChange(i) {
var stage = '#monit_test_condition_stage' + i;
$(stage).on('changed.bs.select', function() {
testConditionStages[i-1] = $(stage).val();
UpdateTestConditionForm();
$(stage).focus();
});
};
function setStageInputOnChange(i) {
var stage = '#monit_test_condition_stage' + i;
$(stage).on('input', function() {
testConditionStages[i-1] = $(stage).val();
UpdateTestConditionForm();
$(stage).focus();
});
};
function UpdateTestConditionForm() {
var conditionType = $('#monit\\.test\\.type option:selected').html();
// old behaviour for custom types
if (conditionType === 'Custom') {
$('#monit_test_condition_form').remove();
$('#monit_test_condition_preview').remove();
$('input[id="monit.test.condition"]').removeClass('hidden');
return;
} else {
if (!$('#monit_test_condition_form').length) {
$('input[id="monit.test.condition"]').addClass('hidden');
$('input[id="monit.test.condition"]').after('<div id="monit_test_condition_preview">' + $('#monit\\.test\\.condition').val() + "</div");
$('input[id="monit.test.condition"]').after('<div id="monit_test_condition_form"></div>');
}
}
var newConditionString = '';
// Stage 1 - left operand
$('#monit_test_condition_form').html(
'<div class="dropdown bootsrap-select">' +
' <select id="monit_test_condition_stage1" class="selectpicker"></select>' +
'</div>');
setStageSelectOnChange(1);
if ($.type(testSyntax.testConditionMapping[conditionType]) === 'array') {
$.each(testSyntax.testConditionMapping[conditionType], function(index, value) {
$('#monit_test_condition_stage1').append('<option value="' + value + '">' + value + '</option>');
});
$('#monit_test_condition_stage1').val(testConditionStages[0]);
newConditionString = testConditionStages[0];
}
if ($.type(testSyntax.testConditionMapping[conditionType]) === 'object') {
$.each(testSyntax.testConditionMapping[conditionType], function(key, value) {
// for arrays use the value as option
if ($.isNumeric(key)) {
key = value;
}
$('#monit_test_condition_stage1').append('<option value="' + key + '">' + key + '</option>');
});
$('#monit_test_condition_stage1').val(testConditionStages[0]);
newConditionString = testConditionStages[0];
// Stage 2 - operator/value
if ($.type(testSyntax.testConditionMapping[conditionType][testConditionStages[0]]) === 'object') {
if (Object.keys(testSyntax.testConditionMapping[conditionType][testConditionStages[0]])[0] === '_OPERATOR') {
// add new dropdown
$('#monit_test_condition_stage1').after('<select id="monit_test_condition_stage2" class="selectpicker"></select>');
setStageSelectOnChange(2);
$.each(testSyntax.operators, function(index, value) {
$('#monit_test_condition_stage2').append('<option value="' + value + '">' + value + '</option>');
});
if (testConditionStages[1] === undefined) {
ParseTestCondition(newConditionString);
}
$('#monit_test_condition_stage2').val(testConditionStages[1]);
newConditionString += ' ' + testConditionStages[1];
// Stage 3 - right operand
if (Object.keys(testSyntax.testConditionMapping[conditionType][testConditionStages[0]]['_OPERATOR'])[0] === '_VALUE') {
$('#monit_test_condition_stage2').after('<input type="text" class="form-control" id="monit_test_condition_stage3">');
setStageInputOnChange(3);
$('#monit_test_condition_stage3').val(testConditionStages[2]);
newConditionString += ' ' + testConditionStages[2];
// Stage 4 - unit
if (Object.keys(testSyntax.testConditionMapping[conditionType][testConditionStages[0]]['_OPERATOR'])[1] === '_UNIT') {
var unitType = testSyntax.testConditionMapping[conditionType][testConditionStages[0]]['_OPERATOR']['_UNIT'];
if (testConditionStages[3] === undefined) {
ParseTestCondition(newConditionString);
}
if (unitType in testSyntax.units) {
$('#monit_test_condition_stage3').after('<select id="monit_test_condition_stage4" class="selectpicker"></select>');
setStageSelectOnChange(4);
$.each(testSyntax.units[unitType], function(index, value) {
$('#monit_test_condition_stage4').append('<option value="' + value + '">' + value + '</option>');
});
if($.inArray(testConditionStages[3], testSyntax.units[unitType]) === -1) {
ParseTestCondition(newConditionString);
}
$('#monit_test_condition_stage4').val(testConditionStages[3]);
newConditionString += ' ' + testConditionStages[3];
} else {
newConditionString += ' ' + unitType;
}
// Stage 5 - rate
if (Object.keys(testSyntax.testConditionMapping[conditionType][testConditionStages[0]]['_OPERATOR'])[2] === '_RATE') {
newConditionString += testConditionStages[4];
}
}
}
}
if (Object.keys(testSyntax.testConditionMapping[conditionType][testConditionStages[0]])[0] === '_VALUE') {
$('#monit_test_condition_stage1').after('<input type="text" class="form-control" id="monit_test_condition_stage2">');
setStageInputOnChange(2);
if (testConditionStages[1] === undefined) {
testConditionStages[1] = '';
}
$('#monit_test_condition_stage2').val(testConditionStages[1]);
newConditionString += ' ' + testConditionStages[1];
}
}
}
$('#monit\\.test\\.condition').val(newConditionString);
$('#monit_test_condition_preview').html($('#monit\\.test\\.condition').val());
$('.selectpicker').selectpicker('refresh');
};
$('#DialogEditTest').on('shown.bs.modal', function() {
ParseTestCondition();
UpdateTestConditionForm();
});
$('#DialogEditTest').on('hide.bs.modal', function() {
$('#monit_test_condition_form').remove();
$('#monit_test_condition_preview').remove();
});
$('#monit\\.test\\.type').on('changed.bs.select', function() {
$('#monit\\.test\\.condition').val('');
testConditionStages = [];
ParseTestCondition();
UpdateTestConditionForm();
$('#monit\\.test\\.type').focus();
});
$('#monit\\.test\\.action').on('changed.bs.select', function() {
// show hide execute field
function ShowHideExecField(){
var actiontype = $('#monit\\.test\\.action').val();
$('tr[id="row_monit.test.path"]').addClass('hidden');
if (actiontype === 'exec') {
$('tr[id="row_monit.test.path"]').removeClass('hidden');
}
});
};
$('#DialogEditTest').on('shown.bs.modal', function() {ShowHideExecField();});
$('#monit\\.test\\.action').on('changed.bs.select', function(e) {ShowHideExecField();});
$("#grid-tests").UIBootgrid({
'search':'/api/monit/settings/search/test/',
'get':'/api/monit/settings/get/test/',
'set':'/api/monit/settings/set/test/',
'add':'/api/monit/settings/set/test/',
'del':'/api/monit/settings/del/test/'
});
var testSyntax = {};
ajaxGet("/api/monit/settings/get/0/0/1", {}, function(data,status) {
if (status == "success") {
testSyntax = Object.assign({}, data.syntax);
}
'search':'/api/monit/settings/searchTest/',
'get':'/api/monit/settings/getTest/',
'set':'/api/monit/settings/setTest/',
'add':'/api/monit/settings/addTest/',
'del':'/api/monit/settings/delTest/'
});
});
@ -687,7 +359,6 @@ POSSIBILITY OF SUCH DAMAGE.
<thead>
<tr>
<th data-column-id="name" data-type="string">{{ lang._('Name') }}</th>
<th data-column-id="type" data-type="string">{{ lang._('Type') }}</th>
<th data-column-id="condition" data-type="string">{{ lang._('Condition') }}</th>
<th data-column-id="action" data-type="string">{{ lang._('Action') }}</th>
<th data-column-id="uuid" data-type="string" data-identifier="true" data-visible="false">{{ lang._('ID') }}</th>