From 170e61e73fd4f1b28aec6d7ef259b9bad783239a Mon Sep 17 00:00:00 2001 From: Jokob-sk Date: Sat, 11 Feb 2023 16:11:27 +1100 Subject: [PATCH] Plugins 0.1 - List param working --- back/pialert.py | 196 +++++++++++++++--- front/php/server/util.php | 31 ++- .../README.md | 0 .../config.json | 78 ++++--- .../script.py | 2 +- .../website_monitoring/last_result.log | 2 - front/plugins/website_monitoring/script.log | 13 -- 7 files changed, 243 insertions(+), 79 deletions(-) rename front/plugins/{website_monitoring => website_monitor}/README.md (100%) rename front/plugins/{website_monitoring => website_monitor}/config.json (79%) rename front/plugins/{website_monitoring => website_monitor}/script.py (98%) delete mode 100755 front/plugins/website_monitoring/last_result.log delete mode 100755 front/plugins/website_monitoring/script.log diff --git a/back/pialert.py b/back/pialert.py index df256da0..65938e37 100755 --- a/back/pialert.py +++ b/back/pialert.py @@ -270,10 +270,13 @@ def ccd(key, default, config, name, inputtype, options, group, events=[], desc = # use existing value if already supplied, otehrwise default value is used if key in config: - result = config[key] + result = config[key] global mySettings + if inputtype == 'text': + result = result.replace('\'', "_single_quote_") + mySettings.append((key, name, desc, inputtype, options, regex, str(result), group, str(events))) return result @@ -283,7 +286,7 @@ def ccd(key, default, config, name, inputtype, options, group, events=[], desc = def importConfigs (): # Specify globals so they can be overwritten with the new config - global lastTimeImported, mySettings, plugins + global lastTimeImported, mySettings, plugins, plugins_once_run # General global ENABLE_ARPSCAN, SCAN_SUBNETS, PRINT_LOG, TIMEZONE, PIALERT_WEB_PROTECTION, PIALERT_WEB_PASSWORD, INCLUDED_SECTIONS, SCAN_CYCLE_MINUTES, DAYS_TO_KEEP_EVENTS, REPORT_DASHBOARD_URL, DIG_GET_IP_ARG, UI_LANG # Email @@ -454,26 +457,26 @@ def importConfigs (): collect_lang_strings(plugin, pref) for set in plugin["settings"]: - setType = set["type"] + setFunction = set["function"] # Setting code name / key - key = pref + "_" + setType + key = pref + "_" + setFunction - v = ccd(key, set["default_value"], c_d, set["name"][0]["string"], get_form_control(set), str(set["options"]), pref) + v = ccd(key, set["default_value"], c_d, set["name"][0]["string"], set["type"] , str(set["options"]), pref) # Save the user defined value into the object set["value"] = v # Setup schedules - if setType == 'RUN_SCHD': + if setFunction == 'RUN_SCHD': newSchedule = Cron(v).schedule(start_date=datetime.datetime.now(tz)) mySchedules.append(schedule_class(pref, newSchedule, newSchedule.next(), False)) # Collect settings related language strings - collect_lang_strings(set, pref + "_" + set["type"]) + collect_lang_strings(set, pref + "_" + set["function"]) # ----------------- # Plugins END - + plugins_once_run = False # Insert settings into the DB sql.execute ("DELETE FROM Settings") @@ -3509,6 +3512,17 @@ def handle_test(testType): file_print('[', timeNow(), '] END Test: ', testType) +#------------------------------------------------------------------------------- +# Return whole setting touple +def get_setting(key): + result = None + # mySettings.append((key, name, desc, inputtype, options, regex, str(result), group, str(events))) + for set in mySettings: + if set[0] == key: + result = set + + return result + #------------------------------------------------------------------------------- def isNewVersion(): global newVersionAvailable @@ -3580,23 +3594,6 @@ def import_language_string(code, key, value, extra = ""): commitDB () -#------------------------------------------------------------------------------- -def get_form_control(setting): - - type = setting["type"] - - if type in ['RUN']: - return 'selecttext' - if type in ['ENABLE', 'FORCE_REPORT']: - return 'boolean' - if type in ['TIMEOUT', 'RUN_TIMEOUT']: - return 'integer' - if type in ['WATCH']: - return 'multiselect' - if type in ['LIST']: - return 'list' - - return 'text' #------------------------------------------------------------------------------- def custom_plugin_decoder(pluginDict): @@ -3614,7 +3611,7 @@ def run_plugin_scripts(runType): shouldRun = False set = get_plugin_setting(plugin, "RUN") - if set['value'] == runType: + if set != None and set['value'] == runType: if runType != "schedule": shouldRun = True elif runType == "schedule": @@ -3634,16 +3631,155 @@ def run_plugin_scripts(runType): print_plugin_info(plugin, ['display_name']) file_print(' [Plugins] CMD: ', get_plugin_setting(plugin, "CMD")["value"]) + execute_plugin(plugin) #------------------------------------------------------------------------------- -def get_plugin_setting(plugin, key): +# Executes the plugin command specified in the setting with the function specified as CMD +def execute_plugin(plugin): + + # ------- necessary settings check -------- + set = get_plugin_setting(plugin, "CMD") + + # handle missing "function":"CMD" setting + if set == None: + return + + set_CMD = set["value"] + + set = get_plugin_setting(plugin, "RUN_TIMEOUT") + + # handle missing "function":"RUN_TIMEOUT" setting + if set == None: + return - for set in plugin['settings']: - if set["type"] == key: - return set + set_RUN_TIMEOUT = set["value"] + + # Prepare custom params + params = [] + + if "params" in plugin: + for param in plugin["params"]: + resolved = "" + + # Get setting value + if param["type"] == "setting": + resolved = get_setting(param["value"]) + + if resolved != None: + resolved = plugin_param_from_glob_set(resolved) + + # TODO HERE + # if param["type"] == "sql": + # resolved = get_sql(param["value"]) + + if resolved == None: + file_print(' [Plugins] The parameter "name":"', param["name"], '" was resolved as None') + + else: + params.append( [param["name"], resolved] ) + + + # ------- prepare params -------- + # prepare command from plugin settings, custom parameters TODO HERE + command = resolve_wildcards(set_CMD, params).split() + + # Execute command + file_print(' [Plugins] Executing: ', set_CMD) + + try: + # try runnning a subprocess with a forced timeout in case the subprocess hangs + output = subprocess.check_output (command, universal_newlines=True, stderr=subprocess.STDOUT, timeout=(set_RUN_TIMEOUT)) + except subprocess.CalledProcessError as e: + # An error occured, handle it + file_print(e.output) + file_print(' [Plugins] Error - enable PRINT_LOG and check logs') + except subprocess.TimeoutExpired as timeErr: + file_print(' [Plugins] TIMEOUT - the process forcefully terminated as timeout reached') + + + # check the last run output + f = open(pluginsPath + '/' + plugin["code_name"] + '/last_result.log', 'r+') + newLines = f.read().split('\n') + f.close() + + # cleanup - select only lines containing a separator to filter out unnecessary data + newLines = list(filter(lambda x: '|' in x, newLines)) + + if len(newLines) == 0: # check if the subprocess failed / there was no valid output + file_print(' [Plugins] No output received from the plugin - enable PRINT_LOG and check logs') + return + else: + file_print('[', timeNow(), '] [Plugins]: SUCCESS, received ', len(newLines), ' entries') + + # regular logging + for line in newLines: + append_line_to_file (pluginsPath + '/plugin.log', line +'\n') + + # build SQL query parameters to insert into the DB + params = [] + + for line in newLines: + columns = line.split("|") + # There has to be always 8 columns + if len(columns) == 8: + params.append((plugin["unique_prefix"], columns[0], columns[1], columns[2], columns[3], columns[4], columns[5], columns[6], columns[7])) + else: + file_print(' [Plugins]: Skipped invalid line in the output: ', line) + + if len(params) > 0: + sql.executemany ("""INSERT INTO Plugins_State ("Plugin", "Object_PrimaryID", "Object_SecondaryID", "DateTime", "Watched_Value1", "Watched_Value2", "Watched_Value3", "Watched_Value4", "Extra") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""", params) + commitDB () #------------------------------------------------------------------------------- +# Flattens a setting to make it passable to a script +def resolve_wildcards(command, params): + for param in params: + command = command.replace('{' + param[0] + '}', param[1]) + return command + +#------------------------------------------------------------------------------- +# Flattens a setting to make it passable to a script +def plugin_param_from_glob_set(globalSetting): + + setVal = globalSetting[6] + setTyp = globalSetting[3] + + + noConversion = ['text', 'integer', 'boolean', 'password', 'readonly', 'selectinteger', 'selecttext' ] + arrayConversion = ['multiselect', 'list'] + + if setTyp in noConversion: + return setVal + + if setTyp in arrayConversion: + tmp = '' + + tmp = setVal[:-1] # remove last bracket + tmp = tmp[1:] # remove first bracket + tmp = tmp.replace("'","").replace(' ','') + + return tmp + + +#------------------------------------------------------------------------------- +# Gets the whole setting object +def get_plugin_setting(plugin, function_key): + + result = None + + for set in plugin['settings']: + if set["function"] == function_key: + result = set + + if result == None: + file_print(' [Plugins] Setting with "function":"', function_key, '" is missing in plugin: ', get_plugin_string(plugin, 'display_name')) + + return result + + +#------------------------------------------------------------------------------- +# Get localized string value on the top JSON depth, not recursive def get_plugin_string(props, el): result = '' diff --git a/front/php/server/util.php b/front/php/server/util.php index 145f27ba..577d442c 100755 --- a/front/php/server/util.php +++ b/front/php/server/util.php @@ -276,7 +276,8 @@ function saveSettings() { if($setting[2] == 'text' or $setting[2] == 'password' or $setting[2] == 'readonly' or $setting[2] == 'selecttext') { - $txt = $txt.$setting[1]."='".$setting[3]."'\n" ; + $val = encode_single_quotes($setting[3]); + $txt = $txt.$setting[1]."='".$val."'\n" ; } elseif($setting[2] == 'integer' or $setting[2] == 'selectinteger') { $txt = $txt.$setting[1]."=".$setting[3]."\n" ; @@ -290,12 +291,18 @@ function saveSettings() $txt = $txt.$setting[1]."=".$val."\n" ; }elseif($setting[2] == 'multiselect' or $setting[2] == 'subnets' or $setting[2] == 'list') { - $temp = '['; - foreach($setting[3] as $val) - { - $temp = $temp."'". $val."',"; + $temp = '['; + + if (count($setting) > 3 && is_array( $setting[3]) == True){ + foreach($setting[3] as $val) + { + $temp = $temp."'". encode_single_quotes($val)."',"; + } + + $temp = substr_replace($temp, "", -1); // remove last comma ',' } - $temp = substr_replace($temp, "", -1).']'; // close brackets and remove last comma ',' + + $temp = $temp.']'; // close brackets $txt = $txt.$setting[1]."=".$temp."\n" ; } } @@ -321,8 +328,6 @@ function saveSettings() } // ------------------------------------------------------------------------------------------- - - function getString ($codeName, $default) { $result = lang($codeName); @@ -337,6 +342,16 @@ function getString ($codeName, $default) { // ------------------------------------------------------------------------------------------- + +function encode_single_quotes ($val) { + + $result = str_replace ('\'','_single_quote_',$val); + + return $result; +} + +// ------------------------------------------------------------------------------------------- + function getDateFromPeriod () { $period = $_REQUEST['period']; return '"'. date ('Y-m-d', strtotime ('+1 day -'. $period) ) .'"'; diff --git a/front/plugins/website_monitoring/README.md b/front/plugins/website_monitor/README.md similarity index 100% rename from front/plugins/website_monitoring/README.md rename to front/plugins/website_monitor/README.md diff --git a/front/plugins/website_monitoring/config.json b/front/plugins/website_monitor/config.json similarity index 79% rename from front/plugins/website_monitoring/config.json rename to front/plugins/website_monitor/config.json index 29ec2757..8a14d609 100755 --- a/front/plugins/website_monitoring/config.json +++ b/front/plugins/website_monitor/config.json @@ -20,9 +20,14 @@ "value" : "SELECT dev_MAC from DEVICES" }, { - "name" : "sites", + "name" : "urls", "type" : "setting", - "value" : "WEBMON_LIST" + "value" : "WEBMON_urls_to_check" + }, + { + "name" : "internet_ip", + "type" : "setting", + "value" : "WEBMON_SQL_internet_ip" }], "database_column_aliases":{ "Plugins_Events":{ @@ -50,7 +55,8 @@ }, "settings":[ { - "type": "RUN", + "function": "RUN", + "type": "selecttext", "default_value":"disabled", "options": ["disabled", "once", "schedule", "always_after_scan", "on_new_device"], "localized": ["name", "description"], @@ -64,7 +70,23 @@ }] }, { - "type": "FORCE_REPORT", + "function": "CMD", + "type": "text", + "default_value":"python3 /home/pi/pialert/front/plugins/website_monitor/script.py urls={urls}", + "options": [], + "localized": ["name", "description"], + "name" : [{ + "language_code":"en_us", + "string" : "Command" + }], + "description": [{ + "language_code":"en_us", + "string" : "Comamnd to run" + }] + }, + { + "function": "FORCE_REPORT", + "type": "boolean", "default_value": false, "options": [], "localized": ["name", "description"], @@ -83,7 +105,8 @@ }, { - "type": "RUN_SCHD", + "function": "RUN_SCHD", + "type": "text", "default_value":"0 2 * * *", "options": [], "localized": ["name", "description"], @@ -97,7 +120,8 @@ }] }, { - "type": "API_SQL", + "function": "API_SQL", + "type": "text", "default_value":"SELECT * FROM plugin_website_monitor", "options": [], "localized": ["name", "description"], @@ -111,7 +135,8 @@ }] }, { - "type": "RUN_TIMEOUT", + "function": "RUN_TIMEOUT", + "type": "integer", "default_value":5, "options": [], "localized": ["name", "description"], @@ -125,7 +150,8 @@ }] }, { - "type": "WATCH", + "function": "WATCH", + "type": "multiselect", "default_value":["Watched_Value1"], "options": ["Watched_Value1","Watched_Value2","Watched_Value3","Watched_Value4"], "localized": ["name", "description"], @@ -139,22 +165,9 @@ }] }, { - "type": "CMD", - "default_value":"python3 script.py", - "options": [], - "localized": ["name", "description"], - "name" : [{ - "language_code":"en_us", - "string" : "Command" - }], - "description": [{ - "language_code":"en_us", - "string" : "Change the dig utility arguments if you have issues resolving your Internet IP. Arguments are added at the end of the following command: dig +short ." - }] - }, - { - "type": "LIST", - "default_value":"", + "function": "urls_to_check", + "type": "list", + "default_value":[], "options": [], "localized": ["name", "description"], "name" : [{ @@ -163,7 +176,22 @@ }], "description": [{ "language_code":"en_us", - "string" : "Change the dig utility arguments if you have issues resolving your Internet IP. Arguments are added at the end of the following command: dig +short ." + "string" : "Services to watch. Enter full URL, e.g. https://google.com." + }] + }, + { + "function": "SQL_internet_ip", + "type": "readonly", + "default_value":"SELECT dev_LastIP FROM Devices WHERE dev_MAC = 'Internet'", + "options": [], + "localized": ["name", "description"], + "name" : [{ + "language_code":"en_us", + "string" : "Helper variable" + }], + "description": [{ + "language_code":"en_us", + "string" : "Getting the IP address of the Router / Internet" }] } diff --git a/front/plugins/website_monitoring/script.py b/front/plugins/website_monitor/script.py similarity index 98% rename from front/plugins/website_monitoring/script.py rename to front/plugins/website_monitor/script.py index d5f42de3..b0e7d5f0 100755 --- a/front/plugins/website_monitoring/script.py +++ b/front/plugins/website_monitor/script.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # Based on the work of https://github.com/leiweibau/Pi.Alert -# /home/pi/pialert/front/plugins/website_monitoring/script.py urls=http://google.com,http://bing.com +# /home/pi/pialert/front/plugins/website_monitor/script.py urls=http://google.com,http://bing.com from __future__ import unicode_literals from time import sleep, time, strftime import requests diff --git a/front/plugins/website_monitoring/last_result.log b/front/plugins/website_monitoring/last_result.log deleted file mode 100755 index d3e80925..00000000 --- a/front/plugins/website_monitoring/last_result.log +++ /dev/null @@ -1,2 +0,0 @@ -http://google.com|null|2023-02-05 15:23:04|200|0.407871|null|null|null -http://bing.com|null|2023-02-05 15:23:04|200|0.196052|null|null|null diff --git a/front/plugins/website_monitoring/script.log b/front/plugins/website_monitoring/script.log deleted file mode 100755 index 2f3baa62..00000000 --- a/front/plugins/website_monitoring/script.log +++ /dev/null @@ -1,13 +0,0 @@ -Pi.Alert [Prototype]: ---------------------------------------------------------- -Current User: root - -Monitor Web-Services -Timestamp: 2023-02-05 15:23:03 - -Start Services Monitoring - -| Timestamp | URL | StatusCode | ResponseTime | ------------------------------------------------ -2023-02-05 15:23:04 | http://google.com | 200 | 0.407871 -2023-02-05 15:23:04 | http://bing.com | 200 | 0.196052