⚙ settings saving improvements + refactor - DB lock v0.1 #685

This commit is contained in:
jokob-sk
2024-05-29 19:24:43 +10:00
parent 3853b8a4ec
commit bb2beda12a
10 changed files with 334 additions and 302 deletions

View File

@@ -26,7 +26,7 @@ services:
- ${APP_DATA_LOCATION}/pihole/etc-pihole/pihole-FTL.db:/etc/pihole/pihole-FTL.db - ${APP_DATA_LOCATION}/pihole/etc-pihole/pihole-FTL.db:/etc/pihole/pihole-FTL.db
- ${DEV_LOCATION}/server:/app/server - ${DEV_LOCATION}/server:/app/server
- ${DEV_LOCATION}/dockerfiles:/app/dockerfiles - ${DEV_LOCATION}/dockerfiles:/app/dockerfiles
- ${APP_DATA_LOCATION}/netalertx/php.ini:/etc/php/8.2/fpm/php.ini # - ${APP_DATA_LOCATION}/netalertx/php.ini:/etc/php/8.2/fpm/php.ini
- ${DEV_LOCATION}/install:/app/install - ${DEV_LOCATION}/install:/app/install
- ${DEV_LOCATION}/front/css:/app/front/css - ${DEV_LOCATION}/front/css:/app/front/css
- ${DEV_LOCATION}/front/img:/app/front/img - ${DEV_LOCATION}/front/img:/app/front/img

View File

@@ -492,28 +492,34 @@
/* ----------------------------------------------------------------------------- /* -----------------------------------------------------------------------------
Notification float banner Notification float banner
----------------------------------------------------------------------------- */ ----------------------------------------------------------------------------- */
.pa_alert_notification { .notification_modal {
text-align: center; text-align: center;
font-size: large; left: 0;
font-weight: bold; right: 0;
color: #258744;
background-color: #d4edda;
border-color: #c3e6cb;
border-radius: 5px;
max-width: 1000px;
/* 80% wrapper 1250px */
width: 80%; width: 80%;
z-index: 9999; z-index: 9999;
position: fixed; position: fixed;
top: 30px; top: 100px;
margin: auto;
transform: translate(0, 0);
display: none; display: none;
margin-left: auto;
margin-right: auto;
} }
.modal_green
{
color: #258744;
background-color: #d4edda;
border-color: #c3e6cb;
}
.modal_grey
{
color: white;
background-color: darkgrey;
border-color: #000000;
}
/* ticker setup */ /* ticker setup */
.ticker-li .ticker-li
{ {
@@ -979,6 +985,11 @@ input[readonly] {
border-color: #258744; border-color: #258744;
} }
.settings-sticky-bottom-section .form-group
{
margin-bottom: 0px;
}
.clear-filter .clear-filter
{ {
opacity: 0.5; opacity: 0.5;

View File

@@ -186,17 +186,24 @@ function modalWarningOK() {
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
function showMessage(textMessage = "") { function showMessage(textMessage = "", timeout = 3000, colorClass = "modal_green") {
if (textMessage.toLowerCase().includes("error")) { if (textMessage.toLowerCase().includes("error")) {
// show error // show error
alert(textMessage); alert(textMessage);
} else { } else {
// show temporal notification // show temporary notification
$("#notification").removeClass(); // remove all classes
$("#notification").addClass("alert alert-dimissible notification_modal"); // add default ones
$("#notification").addClass(colorClass); // add color modifiers
// message
$("#alert-message").html(textMessage); $("#alert-message").html(textMessage);
// timeout
$("#notification").fadeIn(1, function () { $("#notification").fadeIn(1, function () {
window.setTimeout(function () { window.setTimeout(function () {
$("#notification").fadeOut(500); $("#notification").fadeOut(500);
}, 3000); }, timeout);
}); });
} }
} }

View File

@@ -201,10 +201,94 @@
} }
} }
// -------------------------------------------------------------------
// Validation
// -------------------------------------------------------------------
function settingsCollectedCorrectly(settingsArray, settingsJSON_DB) {
// check if the required UI_LANG setting is in the array - if not something went wrong
$.each(settingsArray, function(index, value) {
if (value[1] == "UI_LANG") {
if(isEmpty(value[3]) == true)
{
console.log(`⚠ Error: Required setting UI_LANG not found`);
showModalOk('ERROR', getString('settings_missing_block'));
return false;
}
}
});
const settingsCodeNames = settingsJSON_DB.map(setting => setting.Code_Name);
const detailedCodeNames = settingsArray.map(item => item[1]);
const missingCodeNamesOnPage = detailedCodeNames.filter(codeName => !settingsCodeNames.includes(codeName));
const missingCodeNamesInDB = settingsCodeNames.filter(codeName => !detailedCodeNames.includes(codeName));
// check if the number of settings on the page and in the DB are the same
if (missingCodeNamesOnPage.length !== missingCodeNamesInDB.length) {
console.log(`⚠ Error: The following settings are missing in the DB or on the page (Reload page to fix):`);
console.log(missingCodeNamesOnPage);
console.log(missingCodeNamesInDB);
showModalOk('ERROR', getString('settings_missing_block'));
return false;
}
// all OK
return true;
}
// ------------------------------------------------------------------- // -------------------------------------------------------------------
// Manipulating Editable List options // Manipulating Editable List options
// ------------------------------------------------------------------- // -------------------------------------------------------------------
// ---------------------------------------------------------
function addList(element)
{
const fromId = $(element).attr('my-input-from');
const toId = $(element).attr('my-input-to');
input = $(`#${fromId}`).val();
$(`#${toId}`).append($("<option ></option>").attr("value", input).text(input));
// clear input
$(`#${fromId}`).val("");
settingsChanged();
}
// ---------------------------------------------------------
function removeFromList(element)
{
settingsChanged();
$(`#${$(element).attr('my-input')}`).find("option:last").remove();
}
// ---------------------------------------------------------
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 to remove an item from the select element // Function to remove an item from the select element
@@ -305,7 +389,7 @@ function filterRows(inputText) {
}); });
} }
setTimeout(() => { setTimeout(() => {
// Event listener for input change // Event listener for input change
$('#settingsSearch').on('input', function() { $('#settingsSearch').on('input', function() {
@@ -324,13 +408,156 @@ setTimeout(() => {
}, 1000); }, 1000);
// -----------------------------------------------------------------------------
// handling events on the backend initiated by the front end START
// -----------------------------------------------------------------------------
modalEventStatusId = 'modal-message-front-event'
// --------------------------------------------------------
// Calls a backend function to add a front-end event (specified by the attributes 'data-myevent' and 'data-myparam-plugin' on the passed element) to an execution queue
function addToExecutionQueue(element)
{
// value has to be in format event|param. e.g. run|ARPSCAN
action = `${getGuid()}|${$(element).attr('data-myevent')}|${$(element).attr('data-myparam-plugin')}`
$.ajax({
method: "POST",
url: "php/server/util.php",
data: { function: "addToExecutionQueue", action: action },
success: function(data, textStatus) {
// showModalOk ('Result', data );
// show message
showModalOk(getString("general_event_title"), `${getString("general_event_description")} <br/> <br/> <code id='${modalEventStatusId}'></code>`);
updateModalState()
}
})
}
// --------------------------------------------------------
// Updating the execution queue in in modal pop-up
function updateModalState() {
setTimeout(function() {
// Fetch the content from the log file using an AJAX request
$.ajax({
url: '/log/execution_queue.log',
type: 'GET',
success: function(data) {
// Update the content of the HTML element (e.g., a div with id 'logContent')
$('#'+modalEventStatusId).html(data);
updateModalState();
},
error: function() {
// Handle error, such as the file not being found
$('#logContent').html('Error: Log file not found.');
}
});
}, 2000);
}
// -----------------------------------------------------------------------------
// handling events on the backend initiated by the front end END
// -----------------------------------------------------------------------------
// ---------------------------------------------------------
// UNUSED?
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;
result = result.replaceAll('"', '');
document.getElementById(targetId).innerHTML = result.replaceAll('"', '');
});
}
// -----------------------------------------------------------------------------
// Show/hide the metadata settings
// -----------------------------------------------------------------------------
function toggleMetadata(element)
{
const id = $(element).attr('my-to-toggle');
$(`#${id}`).toggle();
}
// ---------------------------------------------------------
// Helper methods
// ---------------------------------------------------------
// Toggle readonly mode of the target element specified by the id in the "my-input-toggle-readonly" attribute
function overrideToggle(element) {
settingsChanged();
targetId = $(element).attr("my-input-toggle-readonly");
inputElement = $(`#${targetId}`)[0];
if (!inputElement) {
console.error("Input element not found!");
return;
}
if (inputElement.type === "text" || inputElement.type === "password") {
inputElement.readOnly = !inputElement.readOnly;
} else if (inputElement.type === "checkbox") {
inputElement.disabled = !inputElement.disabled;
} else {
console.warn("Unsupported input type. Only text, password, and checkbox inputs are supported.");
}
}
// ---------------------------------------------------------
// generate a list of options for a input select
function generateInputOptions(pluginsData, set, input, isMultiSelect = false)
{
multi = isMultiSelect ? "multiple" : "";
// optionsArray = getSettingOptions(set['Code_Name'] )
valuesArray = createArray(set['Value']);
// create unique ID
var targetLocation = set['Code_Name'] + "_initSettingDropdown";
// execute AJAX callabck + SQL query resolution
initSettingDropdown(set['Code_Name'] , valuesArray, targetLocation, generateDropdownOptions)
// main selection dropdown wrapper
input += `
<select onChange="settingsChanged()"
my-data-type="${set['Type']}"
class="form-control"
name="${set['Code_Name']}"
id="${set['Code_Name']}" ${multi}>
<option id="${targetLocation}" temporary="temporary"></option>
</select>`;
return input;
}

View File

@@ -12,6 +12,8 @@
// DB File Path // DB File Path
$DBFILE = dirname(__FILE__).'/../../../db/app.db'; $DBFILE = dirname(__FILE__).'/../../../db/app.db';
$db_locked = false;
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Connect DB // Connect DB
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@@ -20,12 +22,21 @@ function SQLite3_connect ($trytoreconnect) {
try try
{ {
// connect to database // connect to database
global $db_locked;
$db_locked = false;
// return new SQLite3($DBFILE, SQLITE3_OPEN_READONLY); // return new SQLite3($DBFILE, SQLITE3_OPEN_READONLY);
return new SQLite3($DBFILE, SQLITE3_OPEN_READWRITE); return new SQLite3($DBFILE, SQLITE3_OPEN_READWRITE);
} }
catch (Exception $exception) catch (Exception $exception)
{ {
// sqlite3 throws an exception when it is unable to connect // sqlite3 throws an exception when it is unable to connect
global $db_locked;
$db_locked = true;
// try to reconnect one time after 3 seconds // try to reconnect one time after 3 seconds
if($trytoreconnect) if($trytoreconnect)

View File

@@ -269,16 +269,26 @@ function delete($columnName, $id, $dbtable)
// check if the database is locked // check if the database is locked
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
function checkLock() { function checkLock() {
global $db; global $DBFILE, $db_locked;
try {
$db->exec('BEGIN EXCLUSIVE TRANSACTION'); $file = fopen($DBFILE, 'r+');
$db->exec('COMMIT');
echo 0; // Not locked if (!$file or $db_locked) {
return 0; echo 1; // Could not open the file
} catch (Exception $e) { return;
echo 1; // Locked
return 1;
} }
if (flock($file, LOCK_EX | LOCK_NB)) {
// Lock acquired, meaning the database is not locked by another process
flock($file, LOCK_UN); // Release the lock
echo 0; // Not locked
} else {
// Could not acquire lock, meaning the database is locked
echo 1; // Locked
}
fclose($file);
} }
?> ?>

View File

@@ -387,8 +387,10 @@ function saveSettings()
// Replace the original file with the temporary file // Replace the original file with the temporary file
rename($tempConfPath, $fullConfPath); rename($tempConfPath, $fullConfPath);
displayMessage("<br/>Settings saved to the <code>app.conf</code> file.<br/><br/>A time-stamped backup of the previous file created. <br/><br/> Reloading...<br/>", // displayMessage(lang('settings_saved'),
FALSE, TRUE, TRUE, TRUE); // FALSE, TRUE, TRUE, TRUE);
echo "OK";
} }

View File

@@ -657,7 +657,7 @@
"settings_imported": "Last time settings were imported from the app.conf file", "settings_imported": "Last time settings were imported from the app.conf file",
"settings_imported_label": "Settings imported", "settings_imported_label": "Settings imported",
"settings_missing": "Not all settings loaded! High load on the database or app startup sequence. Click the 🔄 reload button in the top.", "settings_missing": "Not all settings loaded! High load on the database or app startup sequence. Click the 🔄 reload button in the top.",
"settings_missing_block": "Not all settings were loaded correctly. This is probably caused by a high load on the database. Click the 🔄 reload button in the top.", "settings_missing_block": "Error: Settings not loaded correctly. Click the reload button 🔄 at the top, alternatively, check the browser log for details (F12).",
"settings_old": "Importing settings and re-initializing...", "settings_old": "Importing settings and re-initializing...",
"settings_other_scanners": "Other, non-device scanner plugins that are currently enabled.", "settings_other_scanners": "Other, non-device scanner plugins that are currently enabled.",
"settings_other_scanners_icon": "fa-solid fa-recycle", "settings_other_scanners_icon": "fa-solid fa-recycle",
@@ -665,7 +665,7 @@
"settings_publishers": "Enabled notification gateways - publishers, that will send a notification depending on your settings.", "settings_publishers": "Enabled notification gateways - publishers, that will send a notification depending on your settings.",
"settings_publishers_icon": "fa-solid fa-comment-dots", "settings_publishers_icon": "fa-solid fa-comment-dots",
"settings_publishers_label": "Publishers", "settings_publishers_label": "Publishers",
"settings_saved": "<br/>Settings saved to the <code>app.conf</code> file.<br/><br/>A time-stamped backup of the previous file created. <br/><br/> Reloading...<br/>", "settings_saved": "<br/>Settings saved. <br/><br/> Reloading... <br/><br/> <i class=\"ion ion-ios-loop-strong fa-spin fa-2x fa-fw\"></i> <br/>",
"settings_system_icon": "fa-solid fa-gear", "settings_system_icon": "fa-solid fa-gear",
"settings_system_label": "System", "settings_system_label": "System",
"settings_update_item_warning": "Update the value below. Be careful to follow the previous format. <b>Validation is not performed.</b>", "settings_update_item_warning": "Update the value below. Be careful to follow the previous format. <b>Validation is not performed.</b>",

View File

@@ -147,7 +147,7 @@
<!-- Alert float --> <!-- Alert float -->
<div id="notification" class="alert alert-dimissible pa_alert_notification"> <div id="notification" >
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button> <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<div id="alert-message"> Alert message </div> <div id="alert-message"> Alert message </div>
</div> </div>

View File

@@ -601,65 +601,6 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
} }
// ---------------------------------------------------------
// generate a list of options for a input select
function generateInputOptions(pluginsData, set, input, isMultiSelect = false)
{
multi = isMultiSelect ? "multiple" : "";
// optionsArray = getSettingOptions(set['Code_Name'] )
valuesArray = createArray(set['Value']);
// create unique ID
var targetLocation = set['Code_Name'] + "_initSettingDropdown";
// execute AJAX callabck + SQL query resolution
initSettingDropdown(set['Code_Name'] , valuesArray, targetLocation, generateDropdownOptions)
// main selection dropdown wrapper
input += `
<select onChange="settingsChanged()"
my-data-type="${set['Type']}"
class="form-control"
name="${set['Code_Name']}"
id="${set['Code_Name']}" ${multi}>
<option id="${targetLocation}" temporary="temporary"></option>
</select>`;
return input;
}
// ---------------------------------------------------------
// Helper methods
// ---------------------------------------------------------
// Toggle readonly mode of teh target element specified by the id in the "my-input-toggle-readonly" attribute
function overrideToggle(element) {
settingsChanged();
targetId = $(element).attr("my-input-toggle-readonly");
inputElement = $(`#${targetId}`)[0];
if (!inputElement) {
console.error("Input element not found!");
return;
}
if (inputElement.type === "text" || inputElement.type === "password") {
inputElement.readOnly = !inputElement.readOnly;
} else if (inputElement.type === "checkbox") {
inputElement.disabled = !inputElement.disabled;
} else {
console.warn("Unsupported input type. Only text, password, and checkbox inputs are supported.");
}
}
// number of settings has to be equal to
// display the name of the first person // display the name of the first person
// echo $settingsJson[0]->name; // echo $settingsJson[0]->name;
@@ -670,52 +611,7 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
// Wrong number of settings processing // Wrong number of settings processing
if(settingsNumberJSON != settingsNumberDB) if(settingsNumberJSON != settingsNumberDB)
{ {
showModalOk('WARNING', "<?= lang("settings_missing")?>"); showModalOk('WARNING', getString("settings_missing"));
}
// ---------------------------------------------------------
function addList(element)
{
const fromId = $(element).attr('my-input-from');
const toId = $(element).attr('my-input-to');
input = $(`#${fromId}`).val();
$(`#${toId}`).append($("<option ></option>").attr("value", input).text(input));
// clear input
$(`#${fromId}`).val("");
settingsChanged();
}
// ---------------------------------------------------------
function removeFromList(element)
{
settingsChanged();
$(`#${$(element).attr('my-input')}`).find("option:last").remove();
}
// ---------------------------------------------------------
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();
}
} }
// --------------------------------------------------------- // ---------------------------------------------------------
@@ -724,7 +620,7 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
{ {
console.log(`Error settingsNumberJSON != settingsNumberDB: ${settingsNumberJSON} != ${settingsNumberDB}`); console.log(`Error settingsNumberJSON != settingsNumberDB: ${settingsNumberJSON} != ${settingsNumberDB}`);
showModalOk('WARNING', "<?= lang("settings_missing_block")?>"); showModalOk('WARNING', getString("settings_missing_block"));
setTimeout(() => { setTimeout(() => {
clearCache() clearCache()
@@ -778,49 +674,8 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
} }
}); });
console.log(settingsArray);
// sanity check to make sure settings were loaded & collected correctly // sanity check to make sure settings were loaded & collected correctly
sanityCheck_notOK = true if(settingsCollectedCorrectly(settingsArray, settingsJSON_DB))
$.each(settingsArray, function(index, value) {
// Do something with each element of the array
if(value[1] == "UI_LANG")
{
sanityCheck_notOK = isEmpty(value[3])
}
});
// if ok, double check the number collected of settings is correct
if(sanityCheck_notOK == false)
{
console.log(settingsArray);
// Step 1: Extract Code_Name values from settingsList
const settingsCodeNames = settingsJSON_DB.map(setting => setting.Code_Name);
// Step 2: Extract second elements from detailedList
const detailedCodeNames = settingsArray.map(item => item[1]);
// Step 3: Find missing Code_Name values
const missingCodeNamesOnPage = detailedCodeNames.filter(codeName => !settingsCodeNames.includes(codeName));
const missingCodeNamesInDB = settingsCodeNames.filter(codeName => !detailedCodeNames.includes(codeName));
if(missingCodeNamesOnPage.length != missingCodeNamesInDB.length)
{
sanityCheck_notOK = true;
console.log(`⚠ Error: The following settings are missing in the DB or on the page (Reload page to fix):`);
console.log(missingCodeNamesOnPage);
console.log(missingCodeNamesInDB);
showModalOk('WARNING', "<?= lang("settings_missing_block")?>");
}
}
if(sanityCheck_notOK == false && false)
{ {
// trigger a save settings event in the backend // trigger a save settings event in the backend
$.ajax({ $.ajax({
@@ -831,7 +686,7 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
settings: JSON.stringify(settingsArray) }, settings: JSON.stringify(settingsArray) },
success: function(data, textStatus) { success: function(data, textStatus) {
showModalOk ('Result', data ); showMessage (getString("settings_saved"), 5000, "modal_grey");
// Remove navigation prompt "Are you sure you want to leave..." // Remove navigation prompt "Are you sure you want to leave..."
window.onbeforeunload = null; window.onbeforeunload = null;
@@ -849,29 +704,14 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
} }
// ---------------------------------------------------------
function getParam(targetId, key, skipCache = false) {
skipCacheQuery = ""; </script>
if(skipCache)
{
skipCacheQuery = "&skipcache";
}
// get parameter value <!-- INIT THE PAGE -->
$.get('php/server/parameters.php?action=get&defaultValue=0&parameter='+ key + skipCacheQuery, function(data) { <script defer>
var result = data; function handleLoadingDialog()
result = result.replaceAll('"', '');
document.getElementById(targetId).innerHTML = result.replaceAll('"', '');
});
}
// -----------------------------------------------------------------------------
function handleLoadingDialog()
{ {
// check if config file has been updated // check if config file has been updated
@@ -926,82 +766,6 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
} }
</script>
<script defer>
// -----------------------------------------------------------------------------
// handling events on the backend initiated by the front end START
// -----------------------------------------------------------------------------
function toggleMetadata(element)
{
const id = $(element).attr('my-to-toggle');
$(`#${id}`).toggle();
}
// -----------------------------------------------------------------------------
// handling events on the backend initiated by the front end START
// -----------------------------------------------------------------------------
modalEventStatusId = 'modal-message-front-event'
// --------------------------------------------------------
// Calls a backend function to add a front-end event (specified by the attributes 'data-myevent' and 'data-myparam-plugin' on the passed element) to an execution queue
function addToExecutionQueue(element)
{
// value has to be in format event|param. e.g. run|ARPSCAN
action = `${getGuid()}|${$(element).attr('data-myevent')}|${$(element).attr('data-myparam-plugin')}`
$.ajax({
method: "POST",
url: "php/server/util.php",
data: { function: "addToExecutionQueue", action: action },
success: function(data, textStatus) {
// showModalOk ('Result', data );
// show message
showModalOk(getString("general_event_title"), `${getString("general_event_description")} <br/> <br/> <code id='${modalEventStatusId}'></code>`);
updateModalState()
}
})
}
// --------------------------------------------------------
// Updating the execution queue in in modal pop-up
function updateModalState() {
setTimeout(function() {
// Fetch the content from the log file using an AJAX request
$.ajax({
url: '/log/execution_queue.log',
type: 'GET',
success: function(data) {
// Update the content of the HTML element (e.g., a div with id 'logContent')
$('#'+modalEventStatusId).html(data);
updateModalState();
},
error: function() {
// Handle error, such as the file not being found
$('#logContent').html('Error: Log file not found.');
}
});
}, 2000);
}
// -----------------------------------------------------------------------------
// handling events on the backend initiated by the front end END
// -----------------------------------------------------------------------------
// ---------------------------------------------------------
// Show last time settings have been imported
showSpinner() showSpinner()
handleLoadingDialog() handleLoadingDialog()