false, 'error' => 'Method not allowed']); exit(); } // init request params if (array_key_exists('function', $_POST)) { $FUNCTION = $_POST['function']; } if (array_key_exists('settings', $_POST)) { $SETTINGS = $_POST['settings']; } // call functions based on requested params switch ($FUNCTION) { case 'savesettings': requireCsrfToken(); saveSettings(); break; default: // Handle any other cases or errors if needed break; } // ------------------------------------------------------------------------------------------- function saveSettings() { global $SETTINGS, $FUNCTION, $config_file, $fullConfPath, $configFolderPath, $timestamp; // save to the file $new_name = $config_file.'_'.$timestamp.'.backup'; $new_location = $configFolderPath.'/'.$new_name; if(file_exists( $fullConfPath) != 1) { displayInAppNoti('File "'.$fullConfPath.'" not found or missing read permissions. Creating a new config file.', 'warning'); } // create a backup copy elseif (!copy($fullConfPath, $new_location)) { displayInAppNoti("Failed to copy file ".$fullConfPath." to ".$new_location." Check your permissions to allow read/write access to the /config folder.", 'error'); } // generate a clean .conf file $groups = []; $txt = ""; $txt = $txt."#-----------------AUTOGENERATED FILE-----------------#\n"; $txt = $txt."# #\n"; $txt = $txt."# Generated: ".$timestamp." #\n"; $txt = $txt."# #\n"; $txt = $txt."# Config file for the LAN intruder detection app: #\n"; $txt = $txt."# https://github.com/netalertx/NetAlertX #\n"; $txt = $txt."# #\n"; $txt = $txt."#-----------------AUTOGENERATED FILE-----------------#\n"; // collect all groups $decodedSettings = json_decode((string)$SETTINGS, true); if ($decodedSettings === null) { echo "Error: Invalid JSON in settings data."; return; } foreach ($decodedSettings as $setting) { if( in_array($setting[0] , $groups) == false) { array_push($groups ,$setting[0]); } } // go thru the groups and prepare settings to write to file foreach ($groups as $group) { $txt .= "\n\n# " . $group; $txt .= "\n#---------------------------\n"; foreach ($decodedSettings as $setting) { $settingGroup = $setting[0]; $setKey = $setting[1]; $dataType = $setting[2]; $settingValue = $setting[3]; // Sanity check if($setKey == "UI_LANG" && $settingValue == "") { echo "🔴 Error: important settings missing. Refresh the page with 🔃 on the top and try again."; return; } if ($group == $settingGroup) { if ($dataType == 'string' ) { $val = encode_python_string($settingValue); $txt .= $setKey . "='" . $val . "'\n"; } elseif ($dataType == 'integer') { $txt .= $setKey . "=" . $settingValue . "\n"; } elseif ($dataType == 'none') { $txt .= $setKey . "=''\n"; } elseif ($dataType == 'json') { $txt .= $setKey . "=" . $settingValue . "\n"; } elseif ($dataType == 'boolean') { $val = ($settingValue === true || $settingValue === 1 || strtolower($settingValue) === 'true') ? "True" : "False"; $txt .= $setKey . "=" . $val . "\n"; } elseif ($dataType == 'array' ) { $temp = ''; if(is_array($settingValue) == FALSE) { $settingValue = json_decode($settingValue); } // skipping __metadata entries (?) if (count($setting) > 3 && is_array($settingValue) == true) { foreach ($settingValue as $val) { $temp .= "'" . encode_python_string($val) . "',"; } $temp = substr_replace($temp, "", -1); // remove last comma ',' } $temp = '['.$temp.']'; // wrap brackets $txt .= $setKey . "=" . $temp . "\n"; } else { $txt .= $setKey . "='⭕Not handled⭕'\n"; } } } } $txt = $txt."\n\n"; $txt = $txt."#-------------------IMPORTANT INFO-------------------#\n"; $txt = $txt."# This file is ingested by a python script, so if #\n"; $txt = $txt."# modified it needs to use python syntax #\n"; $txt = $txt."#-------------------IMPORTANT INFO-------------------#\n"; // open new file and write the new configuration // Backup the original file if (file_exists($fullConfPath)) { 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 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 // equivalent: /settings/ // 🔺----- API ENDPOINTS SUPERSEDED -----🔺 function getSettingValue($setKey) { // Define the JSON endpoint URL $apiRoot = rtrim(getenv('NETALERTX_API') ?: '/tmp/api', '/'); $url = $apiRoot . '/table_settings.json'; // Fetch the JSON data $json = file_get_contents($url); // Check if the JSON data was successfully fetched if ($json === false) { return 'Could not get json data'; } // Decode the JSON data $data = json_decode($json, true); // Check if the JSON decoding was successful if (json_last_error() !== JSON_ERROR_NONE) { return 'Could not decode json data'; } // Search for the setting by setKey foreach ($data['data'] as $setting) { if ($setting['setKey'] === $setKey) { return $setting['setValue']; } } // Return false if the setting was not found return 'Could not find setting '.$setKey; } // ------------------------------------------------------------------------------------------- /** * Encode a string for use inside a single-quoted Python literal in app.conf. * Doubles backslashes so they round-trip unchanged, and replaces ' with the * legacy {s-quote} placeholder that the backend converts back per use. */ function encode_python_string($val) { return str_replace(['\\', '\''], ['\\\\', '{s-quote}'], $val); } // ------------------------------------------------------------------------------------------- // Helper function to send notifications via the backend API endpoint // ------------------------------------------------------------------------------------------- function displayInAppNoti($message, $level = 'error') { try { $apiBase = getSettingValue('BACKEND_API_URL') ?: 'http://localhost:20212'; $apiToken = getSettingValue('API_TOKEN') ?: ''; if (empty($apiToken)) { // If no token available, silently fail (don't break the application) return; } $url = rtrim($apiBase, '/') . '/messaging/in-app/write'; $payload = json_encode([ 'message' => $message, 'level' => $level ]); $ch = curl_init($url); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', 'Authorization: Bearer ' . $apiToken, 'Content-Length: ' . strlen($payload) ]); curl_setopt($ch, CURLOPT_TIMEOUT, 5); curl_exec($ch); curl_close($ch); } catch (Exception $e) { // Silently fail if notification sending fails } } ?>