From aefe470d31a92f049e3d227c9d816d1916465da9 Mon Sep 17 00:00:00 2001 From: jokob-sk Date: Sat, 14 Sep 2024 09:37:27 +1000 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=84Sync=20hub=202.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dockerfiles/README.md | 2 +- front/plugins/sync/config.json | 75 ++++++++++++++++++++++++++++++++++ front/plugins/sync/hub.php | 51 +++++++++++++++++++++-- front/plugins/sync/sync.py | 70 +++++++++++++++++++++++++++++-- 4 files changed, 190 insertions(+), 8 deletions(-) diff --git a/dockerfiles/README.md b/dockerfiles/README.md index ba796d30..0a01fdc9 100755 --- a/dockerfiles/README.md +++ b/dockerfiles/README.md @@ -42,7 +42,7 @@ docker run -d --rm --network=host \ | `LISTEN_ADDR` |Set the specific IP Address for the listener address for the nginx webserver (web interface). This could be useful when using multiple subnets to hide the web interface from all untrusted networks. | `0.0.0.0` | |`TZ` |Time zone to display stats correctly. Find your time zone [here](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) | `Europe/Berlin` | |`APP_CONF_OVERRIDE` | JSON override for settings, e.g. `{"SCAN_SUBNETS":"['192.168.1.0/24 --interface=eth1']","UI_dark_mode":"True"}` (Experimental 🧪) | `N/A` | -|`ALWAYS_FRESH_INSTALL` | Setting to `true` will delete the content of the `/db` & `/config` folders. For testing purposes. Can be coupled with [watchtower](https://github.com/containrrr/watchtower) to have an always freshly installed `netalertx`/`-dev` image. | `N/A` | +|`ALWAYS_FRESH_INSTALL` | If `true` will delete the content of the `/db` & `/config` folders. For testing purposes. Can be coupled with [watchtower](https://github.com/containrrr/watchtower) to have an always freshly installed `netalertx`/`netalertx-dev` image. | `N/A` | ### Docker paths diff --git a/front/plugins/sync/config.json b/front/plugins/sync/config.json index 884767ce..a5092923 100755 --- a/front/plugins/sync/config.json +++ b/front/plugins/sync/config.json @@ -159,6 +159,81 @@ "string": "Encryption key used to encrypt the data before sending and for decryption on the hub. The key needs to be the same on the hub and on the nodes." } ] + },{ + "function": "nodes", + "type": { + "dataType": "array", + "elements": [ + { + "elementType": "input", + "elementOptions": [ + { "placeholder": "Enter full url" }, + { "suffix": "_in" }, + { "cssClasses": "col-sm-10" }, + { "prefillValue": "null" } + ], + "transformers": [] + }, + { + "elementType": "button", + "elementOptions": [ + { "sourceSuffixes": ["_in"] }, + { "separator": "" }, + { "cssClasses": "col-xs-12" }, + { "onClick": "addList(this, false)" }, + { "getStringKey": "Gen_Add" } + ], + "transformers": [] + }, + { + "elementType": "select", + "elementHasInputValue": 1, + "elementOptions": [ + { "multiple": "true" }, + { "readonly": "true" }, + { "editable": "true" } + ], + "transformers": [] + }, + { + "elementType": "button", + "elementOptions": [ + { "sourceSuffixes": [] }, + { "separator": "" }, + { "cssClasses": "col-xs-6" }, + { "onClick": "removeAllOptions(this)" }, + { "getStringKey": "Gen_Remove_All" } + ], + "transformers": [] + }, + { + "elementType": "button", + "elementOptions": [ + { "sourceSuffixes": [] }, + { "separator": "" }, + { "cssClasses": "col-xs-6" }, + { "onClick": "removeFromList(this)" }, + { "getStringKey": "Gen_Remove_Last" } + ], + "transformers": [] + } + ] + }, + "default_value": [], + "options": [], + "localized": ["name", "description"], + "name": [ + { + "language_code": "en_us", + "string": "Nodes [h]" + } + ], + "description": [ + { + "language_code": "en_us", + "string": "If specified, the hub will pull Devices data from the listed nodes." + } + ] }, { "function": "hub_url", diff --git a/front/plugins/sync/hub.php b/front/plugins/sync/hub.php index 98f209ab..0e9b2ee3 100755 --- a/front/plugins/sync/hub.php +++ b/front/plugins/sync/hub.php @@ -3,8 +3,11 @@ // External files require '/app/front/php/server/init.php'; +$method = $_SERVER['REQUEST_METHOD']; -if ($_SERVER['REQUEST_METHOD'] === 'POST') { +// ---------------------------------------------- +// Method to check authorization +function checkAuthorization($method) { // Retrieve the authorization header $headers = apache_request_headers(); $auth_header = $headers['Authorization'] ?? ''; @@ -14,16 +17,56 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($auth_header !== $expected_token) { http_response_code(403); echo 'Forbidden'; - write_notification("[Plugin: SYNC] Incoming data: Incorrect API Token", "alert"); + write_notification("[Plugin: SYNC] Incoming data: Incorrect API Token (".$method.")", "alert"); exit; } +} + +// ---------------------------------------------- +// Function to return JSON response +function jsonResponse($status, $data = [], $message = '') { + http_response_code($status); + header('Content-Type: application/json'); + echo json_encode([ + 'node_name' => getSettingValue('SYNC_node_name'), + 'status' => $status, + 'message' => $message, + 'data' => $data, + 'timestamp' => date('Y-m-d H:i:s') + ]); +} + +// ---------------------------------------------- +// MAIN +// ---------------------------------------------- + + +// requesting data (this is a NODE) +if ($method === 'GET') { + checkAuthorization($method); + + $file_path = "/app/front/api/table_devices.json"; + + $data = file_get_contents($file_path); + + // Prepare the data to return as a JSON response + $response_data = [ + 'data_base64' => base64_encode($data), + ]; + + // Return JSON response + jsonResponse(200, $response_data, 'OK'); + +} +// receiving data (this is a HUB) +else if ($method === 'POST') { + checkAuthorization($method); // Retrieve and decode the data from the POST request $data = $_POST['data'] ?? ''; $plugin_folder = $_POST['plugin_folder'] ?? ''; $node_name = $_POST['node_name'] ?? ''; - $storage_path = "/app/front/plugins/{$plugin_folder}"; // Create the storage directory if it doesn't exist @@ -43,12 +86,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $file_path = "{$storage_path}/last_result.encoded.{$node_name}.{$file_count}.log"; - // Save the decoded data to the file file_put_contents($file_path, $data); http_response_code(200); echo 'Data received and stored successfully'; write_notification("[Plugin: SYNC] Data received ({$plugin_folder})", "info"); + } else { http_response_code(405); echo 'Method Not Allowed'; diff --git a/front/plugins/sync/sync.py b/front/plugins/sync/sync.py index 32440884..eae9a2a6 100755 --- a/front/plugins/sync/sync.py +++ b/front/plugins/sync/sync.py @@ -46,6 +46,7 @@ def main(): hub_url = get_setting_value('SYNC_hub_url') node_name = get_setting_value('SYNC_node_name') send_devices = get_setting_value('SYNC_devices') + pull_nodes = get_setting_value('SYNC_nodes') # Get all plugin configurations all_plugins = get_plugins_configs() @@ -78,7 +79,9 @@ def main(): else: mylog('verbose', [f'[{pluginName}] {plugin_folder}/last_result.log not found']) - # Devices procesing + # DEVICES sync + # PUSH/SEND (NODE) + if send_devices: file_path = f"{INSTALL_PATH}/front/api/table_devices.json" @@ -93,10 +96,40 @@ def main(): mylog('verbose', [f'[{pluginName}] Sending file_content: "{file_content}"']) send_data(api_token, file_content, encryption_key, plugin_folder, node_name, pref, hub_url) - # process any received data for the Device DB table - # Create the file path + + # DEVICES sync + # PULL/GET (HUB) + file_dir = os.path.join(pluginsPath, 'sync') file_prefix = 'last_result' + + # pull data from nodes if specified + if len(pull_nodes) > 0: + for node_url in pull_nodes: + response_json, node_name = get_data(api_token, node_url) + + # Extract node_name and base64 data + node_name = response_json.get('node_name', 'unknown_node') + data_base64 = response_json.get('data_base64', '') + + # Decode base64 data + decoded_data = base64.b64decode(data_base64) + + # Create log file name using node name + log_file_name = f'{file_prefix}.{node_name}.log' + + # Write decoded data to log file + with open(file_path = os.path.join(file_dir, log_file_name), 'wb') as log_file: + log_file.write(decoded_data) + + message = f'[{pluginName}] Data for "{plugin_folder}" from node "{node_name}" written to {log_file_name}' + mylog('verbose', [message]) + write_notification(message, 'info', timeNowTZ()) + + + # process any received data for the Device DB table + # Create the file path + # Decode files, rename them, and get the list of files files_to_process = decode_and_rename_files(file_dir, file_prefix) @@ -196,6 +229,7 @@ def main(): return 0 +# send data to the HUB def send_data(api_token, file_content, encryption_key, plugin_folder, node_name, pref, hub_url): # Encrypt the log data using the encryption_key encrypted_data = encrypt_data(file_content, encryption_key) @@ -223,6 +257,36 @@ def send_data(api_token, file_content, encryption_key, plugin_folder, node_name, message = f'[{pluginName}] Failed to send data for "{plugin_folder}" (Status code: {response.status_code})' mylog('verbose', [message]) write_notification(message, 'alert', timeNowTZ()) + +# get data from the nodes to the HUB +def get_data(api_token, node_url): + mylog('verbose', [f'[{pluginName}] Getting data from node: "{node_url}"']) + + # Set the authorization header with the API token + headers = {'Authorization': f'Bearer {api_token}'} + api_endpoint = f"{node_url}/plugins/sync/hub.php" + response = requests.get(api_endpoint, headers=headers) + + # mylog('verbose', [f'[{pluginName}] response: "{response}"']) + + if response.status_code == 200: + try: + # Parse JSON response + response_json = response.json() + + return response_json + + except json.JSONDecodeError: + message = f'[{pluginName}] Failed to parse JSON response from "{node_url}"' + mylog('verbose', [message]) + write_notification(message, 'alert', timeNowTZ()) + return "" + + else: + message = f'[{pluginName}] Failed to send data for "{plugin_folder}" (Status code: {response.status_code})' + mylog('verbose', [message]) + write_notification(message, 'alert', timeNowTZ()) + return ""