Files
NetAlertX/front/settings.php

615 lines
21 KiB
PHP
Executable File

<?php
require 'php/templates/header.php';
//------------------------------------------------------------------------------
// Action selector
//------------------------------------------------------------------------------
// Set maximum execution time to 15 seconds
ini_set ('max_execution_time','30');
// check permissions
$dbPath = "../db/pialert.db";
$confPath = "../config/pialert.conf";
checkPermissions([$dbPath, $confPath]);
// get settings from the API json file
// path to your JSON file
$file = '../front/api/table_settings.json';
// put the content of the file in a variable
$data = file_get_contents($file);
// JSON decode
$settingsJson = json_decode($data);
// get settings from the DB
global $db;
global $settingKeyOfLists;
$result = $db->query("SELECT * FROM Settings");
// array
$settingKeyOfLists = array();
$settingCoreGroups = array('General', 'NewDeviceDefaults', 'Email', 'Webhooks', 'Apprise', 'NTFY', 'PUSHSAFER', 'MQTT', 'DynDNS', 'PiHole', 'Pholus', 'Nmap', 'API');
$settings = array();
while ($row = $result -> fetchArray (SQLITE3_ASSOC)) {
// Push row data
$settings[] = array( 'Code_Name' => $row['Code_Name'],
'Display_Name' => $row['Display_Name'],
'Description' => $row['Description'],
'Type' => $row['Type'],
'Options' => $row['Options'],
'RegEx' => $row['RegEx'],
'Value' => $row['Value'],
'Group' => $row['Group'],
'Events' => $row['Events']
);
}
?>
<!-- Page ------------------------------------------------------------------ -->
<!-- Page ------------------------------------------------------------------ -->
<script src="js/pialert_common.js"></script>
<div id="settingsPage" class="content-wrapper">
<!-- Content header--------------------------------------------------------- -->
<section class="content-header">
<?php require 'php/templates/notification.php'; ?>
<h1 id="pageTitle">
<?= lang('Navigation_Settings');?>
<a style="cursor:pointer">
<span>
<i id='toggleSettings' onclick="toggleAllSettings()" class="settings-expand-icon fa fa-angle-double-down"></i>
</span>
</a>
</h1>
<div class="settingsImported"><?= lang("settings_imported");?> <span id="lastImportedTime"></span></div>
</section>
<div class="content " id='accordion_gen'>
<!-- PLACEHOLDER -->
</div>
<!-- /.content -->
<div class="row" >
<div class="row">
<button type="button" class="center top-margin btn btn-primary btn-default pa-btn bg-green dbtools-button" id="save" onclick="saveSettings()"><?= lang('DevDetail_button_Save');?></button>
</div>
<div id="result"></div>
</div>
</div>
<!-- /.content-wrapper -->
<!-- ----------------------------------------------------------------------- -->
<?php
require 'php/templates/footer.php';
?>
<script>
function getData(){
$.get('api/table_settings.json', function(res) {
settingsData = res["data"];
initSettingsPage(settingsData);
})
}
function initSettingsPage(settingsData){
const settingGroups = [];
const settingKeyOfLists = [];
// core groups are the ones not generated by plugins
const settingCoreGroups = ['General', 'NewDeviceDefaults', 'Email', 'Webhooks', 'Apprise', 'NTFY', 'PUSHSAFER', 'MQTT', 'DynDNS', 'PiHole', 'Pholus', 'Nmap', 'API'];
// Loop through the settingsArray and collect unique settingGroups
settingsData.forEach((set) => {
if (!settingGroups.includes(set.Group)) {
settingGroups.push(set.Group);
}
});
console.log(settingGroups);
let headersHtml = '';
let pluginHtml = `
<div class="row table_row">
<div class="table_cell bold">
<i class="fa-regular fa-book fa-sm"></i>
<a href="https://github.com/jokob-sk/Pi.Alert/tree/main/front/plugins" target="_blank">
<?= lang('Gen_ReadDocs');?>
</a>
</div>
</div>
`;
let isIn = ' in '; // to open the active panel in AdminLTE
for (const group of settingGroups) {
let isPlugin = false;
let settingGroupTypeHtml = '';
if (settingCoreGroups.includes(group)) {
settingGroupTypeHtml = '';
} else {
settingGroupTypeHtml = ' (<i class="fa-regular fa-plug fa-sm"></i>) ';
isPlugin = true;
}
headersHtml += `<div class="box panel panel-default">
<a data-toggle="collapse" data-parent="#accordion_gen" href="#${group}">
<div class="panel-heading">
<h4 class="panel-title">${getString(group+"_icon")} ${getString(group+"_display_name")} ${settingGroupTypeHtml}</h4>
</div>
</a>
<div id="${group}" data-myid="collapsible" class="panel-collapse collapse ${isIn}">
<div class="panel-body">
${isPlugin ? pluginHtml: ""}
</div>
</div>
</div>
`;
isIn = ' '; // open the first panel only by default on page load
}
// generate headers/sections
$('#accordion_gen').html(headersHtml);
// generate panel content
for (const group of settingGroups) {
// go thru all settings and collect settings per settings group
settingsData.forEach((set) => {
setHtml = ""
if(set["Group"] == group)
{
setHtml += `
<div class="row table_row">
<div class="table_cell setting_name bold">
<label>${getString(set['Code_Name'] + '_name', set['Display_Name'])}</label>
<div class="small">
<code>${set['Code_Name']}</code>
</div>
</div>
<div class="table_cell setting_description">
${getString(set['Code_Name'] + '_description', set['Description'])}
</div>
<div class="table_cell setting_input input-group">
`;
// Render different input types based on the settings type
let input = "";
if (set['Type'] === 'text' || set['Type'] === 'string' || set['Type'] === 'date-time') {
input = `<input class="form-control" onChange="settingsChanged()" my-data-type="${set['Type']}" id="${set['Code_Name']}" value="${set['Value']}"/>`;
} else if (set['Type'] === 'password') {
input = `<input onChange="settingsChanged()" my-data-type="${set['Type']}" class="form-control input" id="${set['Code_Name']}" type="password" value="${set['Value']}"/>`;
} else if (set['Type'] === 'readonly') {
input = `<input class="form-control input" my-data-type="${set['Type']}" id="${set['Code_Name']}" value="${set['Value']}" readonly/>`;
} else if (set['Type'] === 'boolean' || set['Type'] === 'integer.checkbox') {
let checked = set['Value'] === 'True' || set['Value'] === '1' ? 'checked' : '';
input = `<input onChange="settingsChanged()" my-data-type="${set['Type']}" class="checkbox" id="${set['Code_Name']}" type="checkbox" value="${set['Value']}" ${checked} />`;
} else if (set['Type'] === 'integer') {
input = `<input onChange="settingsChanged()" my-data-type="${set['Type']}" class="form-control" id="${set['Code_Name']}" type="number" value="${set['Value']}"/>`;
} else if (set['Type'] === 'text.select' || set['Type'] === 'integer.select') {
input = `<select onChange="settingsChanged()" my-data-type="${set['Type']}" class="form-control" name="${set['Code_Name']}" id="${set['Code_Name']}">`;
values = createArray(set['Value']);
options = createArray(set['Options']);
options.forEach(option => {
let selected = values.includes(option) ? 'selected' : '';
input += `<option value="${option}" ${selected}>${option}</option>`;
});
input += '</select>';
} else if (set['Type'] === 'text.multiselect') {
input = `<select onChange="settingsChanged()" my-data-type="${set['Type']}" class="form-control" name="${set['Code_Name']}" id="${set['Code_Name']}" multiple>`;
values = createArray(set['Value']);
options = createArray(set['Options']);
options.forEach(option => {
let selected = values.includes(option) ? 'selected' : '';
input += `<option value="${option}" ${selected}>${option}</option>`;
});
input += '</select>';
} else if (set['Type'] === 'subnets') {
input = `
<div class="row form-group">
<div class="col-xs-5">
<input class="form-control" id="ipMask" type="text" placeholder="192.168.1.0/24"/>
</div>
<div class="col-xs-4">
<input class="form-control" id="ipInterface" type="text" placeholder="eth0" />
</div>
<div class="col-xs-3">
<button class="btn btn-primary" onclick="addInterface()">Add</button>
</div>
</div>
<div class="form-group">
<select class="form-control" my-data-type="${set['Type']}" name="${set['Code_Name']}" id="${set['Code_Name']}" multiple readonly>`;
options = createArray(set['Value']);
options.forEach(option => {
input += `<option value="${option}" disabled>${option}</option>`;
});
input += '</select></div>' +
'<div><button class="btn btn-primary" onclick="removeInterfaces()">Remove all</button></div>';
} else if (set['Type'] === 'list') {
settingKeyOfLists.push(set['Code_Name']);
input = `
<div class="row form-group">
<div class="col-xs-9">
<input class="form-control" type="text" id="${set['Code_Name']}_input" placeholder="Enter value"/>
</div>
<div class="col-xs-3">
<button class="btn btn-primary" onclick="addList${set['Code_Name']}()">Add</button>
</div>
</div>
<div class="form-group">
<select class="form-control" my-data-type="${set['Type']}" name="${set['Code_Name']}" id="${set['Code_Name']}" multiple readonly>`;
let options = createArray(set['Value']);
options.forEach(option => {
input += `<option value="${option}" disabled>${option}</option>`;
});
input += '</select></div>' +
`<div><button class="btn btn-primary" onclick="removeFromList${set['Code_Name']}()">Remove last</button></div>`;
} else if (set['Type'] === 'json') {
input = `<textarea class="form-control input" my-data-type="${set['Type']}" id="${set['Code_Name']}" readonly>${JSON.stringify(set['Value'], null, 2)}</textarea>`;
}
let eventsHtml = "";
const eventsList = createArray(set['Events']);
if (eventsList.length > 0) {
eventsList.forEach(event => {
eventsHtml += `<span class="input-group-addon pointer"
data-myparam="${set['Code_Name']}"
data-myevent="${event}"
>
<i title="${getString(event + "_event_tooltip")}" class="fa ${getString(event + "_event_icon")}">
</i>
</span>`;
});
}
setHtml += input + eventsHtml + `
</div>
</div>
`
// generate settings in the correct group section
$(`#${group} .panel-body`).append(setHtml);
}
});
}
}
// todo fix
function createArray(input) {
// Empty array
if (input === '[]') {
return [];
}
// Regex patterns
const patternBrackets = /(^\s*\[)|(\]\s*$)/g;
const patternQuotes = /(^\s*')|('\s*$)/g;
const replacement = '';
// Remove brackets
const noBrackets = input.replace(patternBrackets, replacement);
const options = [];
// Create array
const optionsTmp = noBrackets.split(',');
// Handle only one item in array
if (optionsTmp.length === 0) {
return [noBrackets.replace(patternQuotes, replacement)];
}
// Remove quotes
optionsTmp.forEach(item => {
options.push(item.replace(patternQuotes, replacement).trim());
});
return options;
}
// number of settings has to be equal to
// display the name of the first person
// echo $settingsJson[0]->name;
var settingsNumber = <?php echo count($settingsJson->data)?>;
// Wrong number of settings processing
if(<?php echo count($settings)?> != settingsNumber)
{
showModalOk('WARNING', "<?= lang("settings_missing")?>");
}
<?php
// generate javascript methods to handle add and remove items to lists
foreach($settingKeyOfLists as $settingKey )
{
$addList = 'function addList'.$settingKey.'()
{
input = $("#'.$settingKey.'_input").val();
$("#'.$settingKey.'").append($("<option disabled></option>").attr("value", input).text(input));
$("#'.$settingKey.'_input").val("");
settingsChanged();
}
';
$remList = 'function removeFromList'.$settingKey.'()
{
settingsChanged();
// $("#'.$settingKey.'").empty();
$("#'.$settingKey.'").find("option:last").remove();
}';
echo $remList;
echo $addList;
}
?>
// ---------------------------------------------------------
function addInterface()
{
ipMask = $('#ipMask').val();
ipInterface = $('#ipInterface').val();
full = ipMask + " --interface=" + ipInterface;
console.log(full)
if(ipMask == "" || ipInterface == "")
{
showModalOk ('Validation error', 'Specify both, the network mask and the interface');
} else {
$('#SCAN_SUBNETS').append($('<option disabled></option>').attr('value', full).text(full));
$('#ipMask').val('');
$('#ipInterface').val('');
settingsChanged();
}
}
// ---------------------------------------------------------
function removeInterfaces()
{
settingsChanged();
$('#SCAN_SUBNETS').empty();
}
// ---------------------------------------------------------
function collectSettings()
{
var settingsArray = [];
// generate javascript to collect values
const noConversion = ['text', 'integer', 'string', 'password', 'readonly', 'text.select', 'integer.select', 'text.multiselect'];
settingsJSON["data"].forEach(set => {
if (noConversion.includes(set['Type'])) {
console.log($('#'+set["Code_Name"]).val())
console.log(set["Code_Name"])
settingsArray.push([set["Group"], set["Code_Name"], set["Type"], $('#'+set["Code_Name"]).val()]);
} else if (set['Type'] === 'boolean' || set['Type'] === 'integer.checkbox') {
const temp = $(`#${set["Code_Name"]}`).is(':checked') ? 1 : 0;
settingsArray.push([set["Group"], set["Code_Name"], set["Type"], temp]);
} else if (set['Code_Name'] === 'SCAN_SUBNETS') {
const temps = [];
$('#SCAN_SUBNETS option').each(function (i, selected) {
temps.push($(selected).val());
});
settingsArray.push([set["Group"], set["Code_Name"], set["Type"], JSON.stringify(temps)]);
} else if (set['Type'] === 'list') {
const temps = [];
$(`#${set["Code_Name"]} option`).each(function (i, selected) {
const vl = $(selected).val();
if (vl !== '') {
temps.push(vl);
}
});
settingsArray.push([set["Group"], set["Code_Name"], set["Type"], JSON.stringify(temps)]);
} else if (set['Type'] === 'json') {
const temps = $('#'+set["Code_Name"]).val();
settingsArray.push([set["Group"], set["Code_Name"], set["Type"], temps]);
}
});
return settingsArray;
}
// ---------------------------------------------------------
function saveSettings() {
if(<?php echo count($settings)?> != settingsNumber)
{
showModalOk('WARNING', "<?= lang("settings_missing_block")?>");
} else
{
$.ajax({
method: "POST",
url: "../php/server/util.php",
data: {
function: 'savesettings',
settings: JSON.stringify(collectSettings()) },
success: function(data, textStatus) {
showModalOk ('Result', data );
// Remove navigation prompt "Are you sure you want to leave..."
window.onbeforeunload = null;
}
});
}
}
// ---------------------------------------------------------
function getParam(targetId, key, skipCache = false) {
skipCacheQuery = "";
if(skipCache)
{
skipCacheQuery = "&skipcache";
}
// get parameter value
$.get('php/server/parameters.php?action=get&defaultValue=0&parameter='+ key + skipCacheQuery, function(data) {
var result = data;
if(key == "Back_Settings_Imported")
{
fileModificationTime = <?php echo filemtime($confPath)*1000;?>;
importedMiliseconds = parseInt(result.match( /\d+/g ).join('')); // sanitize the string and get only the numbers
result = (new Date(importedMiliseconds)).toLocaleString("en-UK", { timeZone: "<?php echo $timeZone?>" }); //.toDateString("");
// check if displayed settings are outdated
if(fileModificationTime > importedMiliseconds)
{
showModalOk('WARNING: Outdated settings displayed', "<?= lang("settings_old")?>");
}
} else{
result = result.replaceAll('"', '');
}
document.getElementById(targetId).innerHTML = result.replaceAll('"', '');
});
}
// -----------------------------------------------------------------------------
function toggleAllSettings()
{
inStr = ' in';
allOpen = true;
openIcon = 'fa-angle-double-down';
closeIcon = 'fa-angle-double-up';
$('.panel-collapse').each(function(){
if($(this).attr('class').indexOf(inStr) == -1)
{
allOpen = false;
}
})
if(allOpen)
{
// close all
$('div[data-myid="collapsible"]').each(function(){$(this).attr('class', 'panel-collapse collapse ')})
$('#toggleSettings').attr('class', $('#toggleSettings').attr('class').replace(closeIcon, openIcon))
}
else{
// open all
$('div[data-myid="collapsible"]').each(function(){$(this).attr('class', 'panel-collapse collapse in')})
$('div[data-myid="collapsible"]').each(function(){$(this).attr('style', 'height:inherit')})
$('#toggleSettings').attr('class', $('#toggleSettings').attr('class').replace(openIcon, closeIcon))
}
}
getData()
</script>
<script defer>
// -----------------------------------------------------------------------------
// handling events on the backend initiated by the front end START
// -----------------------------------------------------------------------------
$(window).on('load', function() {
$('span[data-myevent]').each(function(index, element){
$(element).attr('onclick',
'handleEvent(\"' + $(element).attr('data-myevent') + '|'+ $(element).attr('data-myparam') + '\")'
);
});
});
modalEventStatusId = 'modal-message-front-event'
function handleEvent (value){
setParameter ('Front_Event', value)
// show message
showModalOk("<?= lang("general_event_title")?>", "<?= lang("general_event_description")?> <code id='"+modalEventStatusId+"'></code>");
// Periodically update state of the requested action
getParam(modalEventStatusId,"Front_Event", true, updateModalState)
updateModalState()
}
function updateModalState(){
setTimeout(function(){
displayedEvent = $('#'+modalEventStatusId).html()
// loop until finished
if(displayedEvent.indexOf('finished') == -1) // if the message is different from finished, check again in 4s
{
getParam(modalEventStatusId,"Front_Event", true)
updateModalState()
}
}, 2000);
}
// -----------------------------------------------------------------------------
// handling events on the backend initiated by the front end END
// -----------------------------------------------------------------------------
// ---------------------------------------------------------
// Show last time settings have been imported
getParam("lastImportedTime", "Back_Settings_Imported", skipCache = true);
</script>