mirror of
https://github.com/librenms/librenms.git
synced 2024-10-07 16:52:45 +00:00
feature: Added the option to select alert rules from a collection
This commit is contained in:
@@ -32,6 +32,8 @@ require_once '../includes/defaults.inc.php';
|
|||||||
require_once '../config.php';
|
require_once '../config.php';
|
||||||
require_once '../includes/definitions.inc.php';
|
require_once '../includes/definitions.inc.php';
|
||||||
require_once '../includes/functions.php';
|
require_once '../includes/functions.php';
|
||||||
|
require_once 'includes/functions.inc.php';
|
||||||
|
require_once 'includes/vars.inc.php';
|
||||||
|
|
||||||
set_debug($_REQUEST['debug']);
|
set_debug($_REQUEST['debug']);
|
||||||
|
|
||||||
@@ -122,6 +124,18 @@ if (isset($_GET['term'],$_GET['device_id'])) {
|
|||||||
$obj = $ret;
|
$obj = $ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} elseif ($vars['type'] === 'alert_rule_collection') {
|
||||||
|
$x=0;
|
||||||
|
foreach (get_rules_from_json() as $rule) {
|
||||||
|
if (str_contains($rule['name'], $vars['term'], true)) {
|
||||||
|
$rule['id'] = $x;
|
||||||
|
$tmp[] = $rule;
|
||||||
|
}
|
||||||
|
$x++;
|
||||||
|
}
|
||||||
|
if (is_array($tmp)) {
|
||||||
|
$obj = $tmp;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
die(json_encode($obj));
|
die(json_encode($obj));
|
||||||
|
@@ -18,9 +18,22 @@ if (is_admin() === false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$alert_id = $_POST['alert_id'];
|
$alert_id = $_POST['alert_id'];
|
||||||
|
$template_id = $_POST['template_id'];
|
||||||
|
|
||||||
if (is_numeric($alert_id) && $alert_id > 0) {
|
if (is_numeric($alert_id) && $alert_id > 0) {
|
||||||
$rule = dbFetchRow('SELECT * FROM `alert_rules` WHERE `id` = ? LIMIT 1', array($alert_id));
|
$rule = dbFetchRow('SELECT * FROM `alert_rules` WHERE `id` = ? LIMIT 1', array($alert_id));
|
||||||
|
} elseif (is_numeric($template_id) && $template_id >= 0) {
|
||||||
|
$x=0;
|
||||||
|
foreach (get_rules_from_json() as $tmp_rule) {
|
||||||
|
if ($x == $template_id) {
|
||||||
|
$rule = $tmp_rule;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$x++;
|
||||||
|
}
|
||||||
|
logfile(json_encode($rule));
|
||||||
|
}
|
||||||
|
if (is_array($rule)) {
|
||||||
$rule_split = preg_split('/([a-zA-Z0-9_\-\.\=\%\<\>\ \"\'\!\~\(\)\*\/\@\|]+[&&|\|\|]{2})/', $rule['rule'], -1, (PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY));
|
$rule_split = preg_split('/([a-zA-Z0-9_\-\.\=\%\<\>\ \"\'\!\~\(\)\*\/\@\|]+[&&|\|\|]{2})/', $rule['rule'], -1, (PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY));
|
||||||
$count = (count($rule_split) - 1);
|
$count = (count($rule_split) - 1);
|
||||||
$rule_split[$count] = $rule_split[$count].' &&';
|
$rule_split[$count] = $rule_split[$count].' &&';
|
||||||
|
@@ -1364,3 +1364,9 @@ function file_download($filename, $content)
|
|||||||
header('Pragma: public');
|
header('Pragma: public');
|
||||||
echo $content;
|
echo $content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function get_rules_from_json()
|
||||||
|
{
|
||||||
|
global $config;
|
||||||
|
return json_decode(file_get_contents($config['install_dir'] . '/misc/alert_rules.json'), true);
|
||||||
|
}
|
||||||
|
120
html/includes/modal/alert_rule_collection.inc.php
Normal file
120
html/includes/modal/alert_rule_collection.inc.php
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* search_rule_collection.inc.php
|
||||||
|
*
|
||||||
|
* LibreNMS search_rule_collection modal
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* @package LibreNMS
|
||||||
|
* @link http://librenms.org
|
||||||
|
* @copyright 2016 Neil Lathwood
|
||||||
|
* @author Neil Lathwood <neil@lathwood.co.uk>
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (is_admin() === false) {
|
||||||
|
die('ERROR: You need to be admin');
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="modal fade" id="search_rule_modal" tabindex="-1" role="dialog" aria-labelledby="search_rule" 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">×</button>
|
||||||
|
<h5 class="modal-title" id="search_rule">Search alert rule collection</h5>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<input type="hidden" name="template_rule_id" id="template_rule_id" value="">
|
||||||
|
<input type="text" id="rule_suggest" name="rule_suggest" class="form-control" placeholder="Start typing..."/>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<input type="submit" id="rule_from_collection" name="rule_from_collection" value="Create" class="btn btn-sm btn-primary">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-2">
|
||||||
|
Rule:
|
||||||
|
</div>
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div id="rule_display"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var rule_suggestions = new Bloodhound({
|
||||||
|
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'),
|
||||||
|
queryTokenizer: Bloodhound.tokenizers.whitespace,
|
||||||
|
remote: {
|
||||||
|
url: "ajax_rulesuggest.php?type=alert_rule_collection&term=%QUERY",
|
||||||
|
filter: function (output) {
|
||||||
|
return $.map(output, function (item) {
|
||||||
|
return {
|
||||||
|
name: item.name,
|
||||||
|
id: item.id,
|
||||||
|
rule: item.rule,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
wildcard: "%QUERY"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
rule_suggestions.initialize();
|
||||||
|
$('#rule_suggest').typeahead({
|
||||||
|
hint: true,
|
||||||
|
highlight: true,
|
||||||
|
minLength: 1,
|
||||||
|
classNames: {
|
||||||
|
menu: 'typeahead-left'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: rule_suggestions.ttAdapter(),
|
||||||
|
async: true,
|
||||||
|
displayKey: 'name',
|
||||||
|
valueKey: 'id',
|
||||||
|
templates: {
|
||||||
|
suggestion: Handlebars.compile('<p> {{name}}</p>')
|
||||||
|
},
|
||||||
|
limit: 20
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#rule_suggest").on("typeahead:selected typeahead:autocompleted", function(e,datum) {
|
||||||
|
$("#template_rule_id").val(datum.id);
|
||||||
|
$("#rule_display").html('<mark>' + datum.rule + '</mark>');
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#rule_from_collection").click('', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
$("#template_id").val($("#template_rule_id").val());
|
||||||
|
$("#search_rule_modal").modal('hide');
|
||||||
|
$("#create-alert").modal('show');
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#search_rule_modal").on('hidden.bs.modal', function(e) {
|
||||||
|
$("#template_rule_id").val('');
|
||||||
|
$("#rule_suggest").val('');
|
||||||
|
$("#rule_display").html('');
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
@@ -31,6 +31,7 @@ if (is_admin() !== false) {
|
|||||||
<input type="hidden" name="device_id" id="device_id" value="">
|
<input type="hidden" name="device_id" id="device_id" value="">
|
||||||
<input type="hidden" name="alert_id" id="alert_id" value="">
|
<input type="hidden" name="alert_id" id="alert_id" value="">
|
||||||
<input type="hidden" name="type" id="type" value="create-alert-item">
|
<input type="hidden" name="type" id="type" value="create-alert-item">
|
||||||
|
<input type="hidden" name="template_id" id="template_id" value="">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<span id="ajax_response"></span>
|
<span id="ajax_response"></span>
|
||||||
@@ -170,6 +171,25 @@ $('#create-alert').on('show.bs.modal', function (event) {
|
|||||||
var device_id = button.data('device_id');
|
var device_id = button.data('device_id');
|
||||||
var alert_id = button.data('alert_id');
|
var alert_id = button.data('alert_id');
|
||||||
var modal = $(this)
|
var modal = $(this)
|
||||||
|
var template_id = $('#template_id').val();
|
||||||
|
$('#template_id').val('');
|
||||||
|
var arr = [];
|
||||||
|
if (template_id >= 0) {
|
||||||
|
$.ajax({
|
||||||
|
type: "POST",
|
||||||
|
url: "ajax_form.php",
|
||||||
|
data: { type: "parse-alert-rule", alert_id: null, template_id: template_id },
|
||||||
|
dataType: "json",
|
||||||
|
success: function(output) {
|
||||||
|
$.each ( output['rules'], function( key, value ) {
|
||||||
|
arr.push(value);
|
||||||
|
});
|
||||||
|
$('#response').data('tagmanager').populate(arr);
|
||||||
|
$('#name').val(output['name']);
|
||||||
|
$('#device_id').val("-1");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
$('#device_id').val(device_id);
|
$('#device_id').val(device_id);
|
||||||
$('#alert_id').val(alert_id);
|
$('#alert_id').val(alert_id);
|
||||||
$('#tagmanager').tagmanager();
|
$('#tagmanager').tagmanager();
|
||||||
@@ -182,7 +202,7 @@ $('#create-alert').on('show.bs.modal', function (event) {
|
|||||||
tagFieldName: 'maps[]',
|
tagFieldName: 'maps[]',
|
||||||
initialCap: false
|
initialCap: false
|
||||||
});
|
});
|
||||||
if( $('#alert_id').val() == '' ) {
|
if( $('#alert_id').val() == '' || template_id == '') {
|
||||||
$('#preseed-maps').show();
|
$('#preseed-maps').show();
|
||||||
} else {
|
} else {
|
||||||
$('#preseed-maps').hide();
|
$('#preseed-maps').hide();
|
||||||
@@ -193,7 +213,6 @@ $('#create-alert').on('show.bs.modal', function (event) {
|
|||||||
data: { type: "parse-alert-rule", alert_id: alert_id },
|
data: { type: "parse-alert-rule", alert_id: alert_id },
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
success: function(output) {
|
success: function(output) {
|
||||||
var arr = [];
|
|
||||||
$.each ( output['rules'], function( key, value ) {
|
$.each ( output['rules'], function( key, value ) {
|
||||||
arr.push(value);
|
arr.push(value);
|
||||||
});
|
});
|
||||||
|
@@ -37,7 +37,7 @@ if (isset($_POST['create-default'])) {
|
|||||||
);
|
);
|
||||||
$default_rules[] = array(
|
$default_rules[] = array(
|
||||||
'device_id' => '-1',
|
'device_id' => '-1',
|
||||||
'rule' => '%bgpPeers.bgpPeerFsmEstablishedTime < "300" && %bgpPeers.bgpPeerState = "established"',
|
'rule' => '%bgpPeers.bgpPeerFsmEstablishedTime < "300" && %bgpPeers.bgpPeerState = "established" && %macros.device_up = "1"',
|
||||||
'severity' => 'critical',
|
'severity' => 'critical',
|
||||||
'extra' => '{"mute":false,"count":"1","delay":"300"}',
|
'extra' => '{"mute":false,"count":"1","delay":"300"}',
|
||||||
'disabled' => 0,
|
'disabled' => 0,
|
||||||
@@ -61,7 +61,7 @@ if (isset($_POST['create-default'])) {
|
|||||||
);
|
);
|
||||||
$default_rules[] = array(
|
$default_rules[] = array(
|
||||||
'device_id' => '-1',
|
'device_id' => '-1',
|
||||||
'rule' => '%sensors.sensor_current > %sensors.sensor_limit && %sensors.sensor_alert = "1"',
|
'rule' => '%sensors.sensor_current > %sensors.sensor_limit && %sensors.sensor_alert = "1" && %macros.device_up = "1"',
|
||||||
'severity' => 'critical',
|
'severity' => 'critical',
|
||||||
'extra' => '{"mute":false,"count":"-1","delay":"300"}',
|
'extra' => '{"mute":false,"count":"-1","delay":"300"}',
|
||||||
'disabled' => 0,
|
'disabled' => 0,
|
||||||
@@ -69,7 +69,7 @@ if (isset($_POST['create-default'])) {
|
|||||||
);
|
);
|
||||||
$default_rules[] = array(
|
$default_rules[] = array(
|
||||||
'device_id' => '-1',
|
'device_id' => '-1',
|
||||||
'rule' => '%sensors.sensor_current < %sensors.sensor_limit_low && %sensors.sensor_alert = "1"',
|
'rule' => '%sensors.sensor_current < %sensors.sensor_limit_low && %sensors.sensor_alert = "1" && %macros.device_up = "1"',
|
||||||
'severity' => 'critical',
|
'severity' => 'critical',
|
||||||
'extra' => '{"mute":false,"count":"-1","delay":"300"}',
|
'extra' => '{"mute":false,"count":"-1","delay":"300"}',
|
||||||
'disabled' => 0,
|
'disabled' => 0,
|
||||||
@@ -77,7 +77,7 @@ if (isset($_POST['create-default'])) {
|
|||||||
);
|
);
|
||||||
$default_rules[] = array(
|
$default_rules[] = array(
|
||||||
'device_id' => '-1',
|
'device_id' => '-1',
|
||||||
'rule' => '%services.service_status != "0"',
|
'rule' => '%services.service_status != "0" && %macros.device_up = "1"',
|
||||||
'severity' => 'critical',
|
'severity' => 'critical',
|
||||||
'extra' => '{"mute":false,"count":"-1","delay":"300"}',
|
'extra' => '{"mute":false,"count":"-1","delay":"300"}',
|
||||||
'disabled' => 0,
|
'disabled' => 0,
|
||||||
@@ -92,6 +92,7 @@ if (isset($_POST['create-default'])) {
|
|||||||
|
|
||||||
require_once 'includes/modal/new_alert_rule.inc.php';
|
require_once 'includes/modal/new_alert_rule.inc.php';
|
||||||
require_once 'includes/modal/delete_alert_rule.inc.php';
|
require_once 'includes/modal/delete_alert_rule.inc.php';
|
||||||
|
require_once 'includes/modal/alert_rule_collection.inc.php';
|
||||||
?>
|
?>
|
||||||
<form method="post" action="" id="result_form">
|
<form method="post" action="" id="result_form">
|
||||||
<?php
|
<?php
|
||||||
@@ -117,8 +118,9 @@ echo '<div class="table-responsive">
|
|||||||
echo '<td colspan="7">';
|
echo '<td colspan="7">';
|
||||||
if ($_SESSION['userlevel'] >= '10') {
|
if ($_SESSION['userlevel'] >= '10') {
|
||||||
echo '<button type="button" class="btn btn-primary btn-sm" data-toggle="modal" data-target="#create-alert" data-device_id="'.$device['device_id'].'"><i class="fa fa-plus"></i> Create new alert rule</button>';
|
echo '<button type="button" class="btn btn-primary btn-sm" data-toggle="modal" data-target="#create-alert" data-device_id="'.$device['device_id'].'"><i class="fa fa-plus"></i> Create new alert rule</button>';
|
||||||
|
echo '<i> - OR - </i>';
|
||||||
|
echo '<button type="button" class="btn btn-primary btn-sm" data-toggle="modal" data-target="#search_rule_modal" data-device_id="'.$device['device_id'].'"><i class="fa fa-plus"></i> Create rule from collection</button>';
|
||||||
}
|
}
|
||||||
|
|
||||||
echo '</td>
|
echo '</td>
|
||||||
<td><select name="results" id="results" class="form-control input-sm" onChange="updateResults(this);">';
|
<td><select name="results" id="results" class="form-control input-sm" onChange="updateResults(this);">';
|
||||||
$result_options = array(
|
$result_options = array(
|
||||||
@@ -338,4 +340,11 @@ function changePage(page,e) {
|
|||||||
$('#result_form').submit();
|
$('#result_form').submit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function newRule(data, e) {
|
||||||
|
$('#template_id').val(data.value);
|
||||||
|
$('#create-alert').modal({
|
||||||
|
show: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
26
misc/alert_rules.json
Normal file
26
misc/alert_rules.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"rule": "%macros.bill_quota_over_quota >= \"75\"",
|
||||||
|
"name": "Quota bills over 75% used"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "%macros.bill_cdr_over_quota >= \"75\"",
|
||||||
|
"name": "CDR bills over 75% used"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "%ipsec_tunnels.tunnel_status != \"active\" && %macros.device_up = \"1\"",
|
||||||
|
"name": "IPSec tunnels down"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "%pollers.time_taken >= \"250\"",
|
||||||
|
"name": "Poller is taking too long"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "%macros.device_up = \"1\" && %devices.os = \"asa\" && %ciscoASA.data > \"5000\"",
|
||||||
|
"name": "Cisco ASA connections over 5000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "%processors.processor_usage > \"85\" && %macros.device_up = \"1\"",
|
||||||
|
"name": "Processor usage over 85%"
|
||||||
|
}
|
||||||
|
]
|
2
sql-schema/149.sql
Normal file
2
sql-schema/149.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
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.bill_quota_over_quota','((%bills.total_data/%bills.bill_quota) * 100) && %bills.bill_type = "quota"','((%bills.total_data/%bills.bill_quota) * 100) && %bills.bill_type = "quota"','Quota bills over X perc of quota','alerting',0,'macros',0,1,0);
|
||||||
|
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.bill_cdr_over_quota','((%bills.rate_95th/%bills.bill_cdr) * 100) && %bills.bill_type = "cdr"','((%bills.rate_95th/%bills.bill_cdr) * 100) && %bills.bill_type = "cdr"','CDR bills over X perc of quota','alerting',0,'macros',0,1,0);
|
38
tests/AlertingTest.php
Normal file
38
tests/AlertingTest.php
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* AlertingTest.php
|
||||||
|
*
|
||||||
|
* Tests for alerting functionality.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* @package LibreNMS
|
||||||
|
* @link http://librenms.org
|
||||||
|
* @copyright 2016 Neil Lathwood
|
||||||
|
* @author Neil Lathwood <neil@lathwood.co.uk>
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace LibreNMS\Tests;
|
||||||
|
|
||||||
|
class AlertTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
public function testJsonAlertCollection()
|
||||||
|
{
|
||||||
|
$rules = get_rules_from_json();
|
||||||
|
$this->assertInternalType('array', $rules);
|
||||||
|
foreach ($rules as $rule) {
|
||||||
|
$this->assertInternalType('array', $rule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -37,6 +37,7 @@ $runtime_stats = array('snmpget' => 0, 'snmpwalk' => 0);
|
|||||||
$classLoader->registerDir($install_dir . '/tests', 'LibreNMS\Tests');
|
$classLoader->registerDir($install_dir . '/tests', 'LibreNMS\Tests');
|
||||||
|
|
||||||
require $install_dir . '/includes/common.php';
|
require $install_dir . '/includes/common.php';
|
||||||
|
require $install_dir . '/html/includes/functions.inc.php';
|
||||||
if (getenv('SNMPSIM')) {
|
if (getenv('SNMPSIM')) {
|
||||||
require $install_dir . '/includes/functions.php';
|
require $install_dir . '/includes/functions.php';
|
||||||
} else {
|
} else {
|
||||||
|
Reference in New Issue
Block a user