From 102a62c8b7edae2a8752f5a04e05f0fe7c18d6cd Mon Sep 17 00:00:00 2001
From: "Jokob @NetAlertX" <96159884+jokob-sk@users.noreply.github.com>
Date: Sun, 21 Jun 2026 00:57:17 +0000
Subject: [PATCH] FE: enhance settings save functionality with reload wait
detection and background processing
---
front/php/server/util.php | 61 ++++++++++++++++++++++++-
front/php/templates/language/en_us.json | 1 +
front/settings.php | 30 ++++++++----
server/conf.py | 1 +
server/initialise.py | 10 ++++
5 files changed, 94 insertions(+), 9 deletions(-)
diff --git a/front/php/server/util.php b/front/php/server/util.php
index c9b7b38c..75c8d86c 100755
--- a/front/php/server/util.php
+++ b/front/php/server/util.php
@@ -166,15 +166,74 @@ function saveSettings()
copy($fullConfPath, $fullConfPath . ".bak");
}
+ // Detect whether the frontend needs to block-wait for backend reload
+ $requiresReloadWait = getReloadWaitRequired($decodedSettings);
+
// Open the file for writing without changing permissions
$file = fopen($fullConfPath, "w") or die("Unable to open file!");
fwrite($file, $txt);
fclose($file);
- echo "OK";
+ echo json_encode(['success' => true, 'requiresReloadWait' => $requiresReloadWait]);
}
+// -------------------------------------------------------------------------------------------
+// Determines if the frontend must wait (block) for the backend to finish reloading after a
+// settings save. Blocking is required when LOADED_PLUGINS changes or UI_WAIT_FOR_SETTINGS
+// is explicitly enabled. Defaults to true (safe) on any parsing error.
+function getReloadWaitRequired($decodedSettings) {
+ $newLoadedPlugins = null;
+ $uiWaitForSettings = false;
+
+ foreach ($decodedSettings as $setting) {
+ if ($setting[1] === 'LOADED_PLUGINS') {
+ $newLoadedPlugins = $setting[3];
+ }
+ if ($setting[1] === 'UI_WAIT_FOR_SETTINGS') {
+ $uiWaitForSettings = ($setting[3] === true || $setting[3] === 1
+ || strtolower((string)$setting[3]) === 'true');
+ }
+ }
+
+ if ($uiWaitForSettings) {
+ return true;
+ }
+
+ $oldLoadedPlugins = getSettingValue('LOADED_PLUGINS');
+
+ // If the old value couldn't be read, default to blocking (safe).
+ if (strpos((string)$oldLoadedPlugins, 'Could not') !== false) {
+ return true;
+ }
+
+ return normalizePluginList($oldLoadedPlugins) !== normalizePluginList($newLoadedPlugins);
+}
+
+// -------------------------------------------------------------------------------------------
+// Normalise a plugin list value (PHP array, JSON array, or Python-style list) to a sorted
+// JSON string for reliable equality comparison.
+function normalizePluginList($value) {
+ if ($value === null || $value === '') {
+ return '[]';
+ }
+ if (is_array($value)) {
+ $arr = $value;
+ } else {
+ $arr = json_decode($value, true);
+ if (!is_array($arr)) {
+ // Handle Python-style single-quoted lists: ['A','B']
+ $jsonStr = str_replace("'", '"', (string)$value);
+ $arr = json_decode($jsonStr, true);
+ }
+ if (!is_array($arr)) {
+ return trim((string)$value);
+ }
+ }
+ sort($arr);
+ return json_encode($arr);
+}
+
// -------------------------------------------------------------------------------------------
// 🔺----- API ENDPOINTS SUPERSEDED -----🔺
// check server/api_server/api_server_start.py for equivalents
diff --git a/front/php/templates/language/en_us.json b/front/php/templates/language/en_us.json
index 102c7cb2..cfefcac5 100755
--- a/front/php/templates/language/en_us.json
+++ b/front/php/templates/language/en_us.json
@@ -807,6 +807,7 @@
"settings_publishers_label": "Publishers",
"settings_readonly": "Can't READ or WRITE app.conf. Try restarting the container and read the file permissions documentation",
"settings_saved": "
Settings saved.
Reloading…
",
+ "settings_saved_background": "Settings saved. Changes are being applied in the background.",
"settings_system_icon": "fa-solid fa-gear",
"settings_system_label": "System",
"settings_update_item_warning": "Update the value below. Be careful to follow the previous format. Validation is not performed.",
diff --git a/front/settings.php b/front/settings.php
index 03d3e291..0be479ce 100755
--- a/front/settings.php
+++ b/front/settings.php
@@ -615,19 +615,33 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
settings: JSON.stringify(settingsArray) },
success: function(data, textStatus) {
- if(data == "OK")
- {
- // showMessage (getString("settings_saved"), 5000, "modal_grey");
+ // Parse response: support both legacy "OK" string and new JSON format
+ let saveSucceeded = false;
+ let requiresReloadWait = true; // safe default
+
+ if (data === "OK") {
+ saveSucceeded = true;
+ } else {
+ let parsed = null;
+ try { parsed = (typeof data === 'object') ? data : JSON.parse(data); } catch(e) {}
+ if (parsed && parsed.success === true) {
+ saveSucceeded = true;
+ requiresReloadWait = parsed.requiresReloadWait === true;
+ }
+ }
+
+ if (saveSucceeded) {
// Remove navigation prompt "Are you sure you want to leave..."
window.onbeforeunload = null;
- // Reloads the current page
- // setTimeout("clearCache()", 5000);
-
write_notification(`[Settings] Settings saved by the user`, 'info')
- clearCache()
- } else{
+ if (requiresReloadWait) {
+ clearCache()
+ } else {
+ showMessage(getString("settings_saved_background"), 5000, "modal_green");
+ }
+ } else {
// something went wrong
write_notification("[Important] Please take a screenshot of the Console tab in the browser (F12) and next error. Submit it (with the nginx and php error logs) as a new issue here: https://github.com/netalertx/NetAlertX/issues", 'interrupt')
write_notification(data, 'interrupt')
diff --git a/server/conf.py b/server/conf.py
index 4eda3e84..c28dfc9c 100755
--- a/server/conf.py
+++ b/server/conf.py
@@ -32,6 +32,7 @@ SCAN_SUBNETS = ["192.168.1.0/24 --interface=eth1", "192.168.1.0/24 --interface=e
LOG_LEVEL = "verbose"
TIMEZONE = "Europe/Berlin"
UI_LANG = "English (en_us)"
+UI_WAIT_FOR_SETTINGS = False
UI_PRESENCE = ["online", "offline", "archived"]
UI_MY_DEVICES = ["online", "offline", "archived", "new", "down"]
UI_NOT_RANDOM_MAC = []
diff --git a/server/initialise.py b/server/initialise.py
index bf53c494..0cc06a21 100755
--- a/server/initialise.py
+++ b/server/initialise.py
@@ -452,6 +452,16 @@ def importConfigs(pm, db, all_plugins):
"UI",
)
+ conf.UI_WAIT_FOR_SETTINGS = ccd(
+ "UI_WAIT_FOR_SETTINGS",
+ False,
+ c_d,
+ "Wait for settings reload",
+ '{"dataType":"boolean", "elements": [{"elementType" : "checkbox", "elementOptions" : [] ,"transformers": []}]}',
+ "[]",
+ "UI",
+ )
+
# Init timezone in case it changed and handle invalid values
try:
if conf.TIMEZONE not in all_timezones: