From 2c05f3f6634dbb330d55df7b3d104720fa59d940 Mon Sep 17 00:00:00 2001 From: BILLY Maxime Date: Mon, 5 Aug 2024 08:30:14 +0000 Subject: [PATCH 01/13] Translated using Weblate (French) Currently translated at 48.1% (333 of 691 strings) Translation: NetAlertX/core Translate-URL: https://hosted.weblate.org/projects/pialert/core/fr/ --- front/php/templates/language/fr_fr.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) mode change 100755 => 100644 front/php/templates/language/fr_fr.json diff --git a/front/php/templates/language/fr_fr.json b/front/php/templates/language/fr_fr.json old mode 100755 new mode 100644 index 58480fb5..cd561c11 --- a/front/php/templates/language/fr_fr.json +++ b/front/php/templates/language/fr_fr.json @@ -8,7 +8,7 @@ "About_Title": "Analyse de la sécurité du réseau et cadre de notification", "AppEvents_DateTimeCreated": "Journalisé", "AppEvents_Extra": "Extra", - "AppEvents_GUID": "", + "AppEvents_GUID": "GUID d’événement d’application", "AppEvents_Helper1": "Helper 1", "AppEvents_Helper2": "Helper 2", "AppEvents_Helper3": "Helper 3", @@ -35,7 +35,7 @@ "BackDevices_Arpscan_enabled": "Apr-Scan Activé", "BackDevices_Backup_CopError": "La base de donnée initiale n'a pas pu être sauvegardée.", "BackDevices_Backup_Failed": "La sauvegarde a été partiellement complétée. L'archive n'a pas pu être crée ou est vide.", - "BackDevices_Backup_okay": "", + "BackDevices_Backup_okay": "La sauvegarde s'est déroulée avec succès avec la nouvelle archive", "BackDevices_DBTools_DelDevError_a": "Erreur lors de la suppression de l'appareil", "BackDevices_DBTools_DelDevError_b": "Erreur lors de la suppression des appareils", "BackDevices_DBTools_DelDev_a": "Appareil supprimé", @@ -56,15 +56,15 @@ "BackDevices_Restore_okay": "Restauration exécutée avec succès.", "BackDevices_darkmode_disabled": "Mode sombre désactivé", "BackDevices_darkmode_enabled": "Mode sombre activé", - "DAYS_TO_KEEP_EVENTS_description": "", + "DAYS_TO_KEEP_EVENTS_description": "Il s'agit d'un paramètre de maintenance. Il indique le nombre de jours pendant lesquels les entrées d'événements seront conservées. Tous les événements plus anciens seront supprimés périodiquement. S'applique également à l'historique des événements du plugin.", "DAYS_TO_KEEP_EVENTS_name": "Supprimer les événements plus anciens que", - "DevDetail_Copy_Device_Title": "", - "DevDetail_Copy_Device_Tooltip": "", + "DevDetail_Copy_Device_Title": " Copier les détails de l'appareil", + "DevDetail_Copy_Device_Tooltip": "Copier les détails de l'appareil dans la liste déroulante. Tout ce qui se trouve sur cette page sera écrasé", "DevDetail_EveandAl_AlertAllEvents": "Alerter tous les événements", "DevDetail_EveandAl_AlertDown": "Alerte de panne", "DevDetail_EveandAl_Archived": "Archivés", "DevDetail_EveandAl_NewDevice": "Nouvel appareil", - "DevDetail_EveandAl_NewDevice_Tooltip": "", + "DevDetail_EveandAl_NewDevice_Tooltip": "Affiche le statut Nouveau pour l'appareil et l'inclut dans les listes lorsque le filtre Nouveaux Appareils est actif. N'affecte pas les notifications.", "DevDetail_EveandAl_RandomMAC": "MAC aléatoire", "DevDetail_EveandAl_ScanCycle": "", "DevDetail_EveandAl_ScanCycle_a": "", From 49450e4d1ff772042a51473a0b609adae6550b8c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 6 Aug 2024 11:53:57 +0000 Subject: [PATCH 02/13] =?UTF-8?q?[=F0=9F=A4=96Automation]=20Update=20READM?= =?UTF-8?q?E=20with=20sponsors=20information?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 2d0b5d3bddb3c9cb16c17ff87254a75826896fd6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2024 11:53:41 +0000 Subject: [PATCH 03/13] =?UTF-8?q?[=F0=9F=A4=96Automation]=20Update=20READM?= =?UTF-8?q?E=20with=20sponsors=20information?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 8a385a90d44b71e94fa73e7fb1f8515739a47db7 Mon Sep 17 00:00:00 2001 From: jokob-sk Date: Mon, 5 Aug 2024 09:58:18 +1000 Subject: [PATCH 04/13] =?UTF-8?q?=F0=9F=94=8CUNIFI=20work?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/PLUGINS_DEV.md | 26 +-- front/plugins/__template/rename_me.py | 7 +- front/plugins/plugin_helper.py | 37 +++- front/plugins/unifi_import/config.json | 34 ++- front/plugins/unifi_import/script.py | 280 ++++++++++++------------- server/database.py | 36 ++++ server/helper.py | 15 +- server/plugin.py | 231 +++++++++++++------- 8 files changed, 408 insertions(+), 258 deletions(-) diff --git a/docs/PLUGINS_DEV.md b/docs/PLUGINS_DEV.md index e7cf38c7..3fe9b014 100755 --- a/docs/PLUGINS_DEV.md +++ b/docs/PLUGINS_DEV.md @@ -33,24 +33,9 @@ Example use cases for plugins could be: If you wish to develop a plugin, please check the existing plugin structure. Once the settings are saved by the user they need to be removed from the `app.conf` file manually if you want to re-initialize them from the `config.json` of the plugin. -Again, please read the below carefully if you'd like to contribute with a plugin yourself. This documentation file might be outdated, so double-check the sample plugins as well. - ## ⚠ Disclaimer -Follow the below very carefully and check example plugin(s) if you'd like to write one yourself. Plugin UI is not my priority right now, happy to approve PRs if you are interested in extending/improving the UI experience (See [Frontend guidelines](/docs/FRONTEND_DEVELOPMENT.md)). Example improvements for the taking: - -* Making the tables sortable/filterable -* Using the same approach to display table data as in the Devices section (solves above) -* Adding form controls supported to display the data (Currently supported ones are listed in the section "UI settings in database_column_definitions" below) -* ... - -## ❗ Known limitations: - -These issues will be hopefully fixed with time, so please don't report them. Instead, if you know how, feel free to investigate and submit a PR to fix the below. Keep the PRs small as it's easier to approve them: - -* Existing plugin objects are sometimes not interpreted correctly and a new object is created instead, resulting in duplicate entries. (race condition?) -* Occasional (experienced twice) hanging of processing plugin script file. -* UI displays outdated values until the API endpoints get refreshed. +Please read the below carefully if you'd like to contribute with a plugin yourself. This documentation file might be outdated, so double-check the sample plugins as well. ## Plugin file structure overview @@ -67,10 +52,10 @@ These issues will be hopefully fixed with time, so please don't report them. Ins More on specifics below. -### Column order and values +### Column order and values (plugins interface contract) > [!IMPORTANT] -> Spend some time reading and trying to understand the below table. This is the interface between the Plugins and the core application. +> Spend some time reading and trying to understand the below table. This is the interface between the Plugins and the core application. The application expets 9 or 13 values The first 9 values are mandatory. The next 4 values (`HelpVal1` to `HelpVal4`) are optional. However, if you use any of these optional values (e.g., `HelpVal1`), you need to supply all optional values (e.g., `HelpVal2`, `HelpVal3`, and `HelpVal4`). If a value is not used, it should be padded with `null`. | Order | Represented Column | Value Required | Description | |----------------------|----------------------|----------------------|----------------------| @@ -83,6 +68,11 @@ More on specifics below. | 6 | `Watched_Value4` | no | As above | | 7 | `Extra` | no | Any other data you want to pass and display in NetAlertX and the notifications | | 8 | `ForeignKey` | no | A foreign key that can be used to link to the parent object (usually a MAC address) | + | 9 | `HelpVal1` | no | (optional) A helper value | + | 10 | `HelpVal2` | no | (optional) A helper value | + | 11 | `HelpVal3` | no | (optional) A helper value | + | 12 | `HelpVal4` | no | (optional) A helper value | + > [!NOTE] > De-duplication is run once an hour on the `Plugins_Objects` database table and duplicate entries with the same value in columns `Object_PrimaryID`, `Object_SecondaryID`, `Plugin` (auto-filled based on `unique_prefix` of the plugin), `UserData` (can be populated with the `"type": "textbox_save"` column type) are removed. diff --git a/front/plugins/__template/rename_me.py b/front/plugins/__template/rename_me.py index e996402e..21e81982 100755 --- a/front/plugins/__template/rename_me.py +++ b/front/plugins/__template/rename_me.py @@ -57,7 +57,12 @@ def main(): watched3 = device['device_type'], watched4 = device['last_seen'], extra = '', - foreignKey = device['mac_address']) + foreignKey = device['mac_address'] + # helpVal1 = "Something1", # Optional Helper values to be passed for mapping into the app + # helpVal2 = "Something1", # If you need to use even only 1, add the remaining ones too + # helpVal3 = "Something1", # and set them to 'null'. Check the the docs for details: + # helpVal4 = "Something1", # https://github.com/jokob-sk/NetAlertX/blob/main/docs/PLUGINS_DEV.md + ) mylog('verbose', [f'[{pluginName}] New entries: "{len(device_data)}"']) diff --git a/front/plugins/plugin_helper.py b/front/plugins/plugin_helper.py index 27f1f953..007bfc98 100755 --- a/front/plugins/plugin_helper.py +++ b/front/plugins/plugin_helper.py @@ -99,7 +99,7 @@ def normalize_mac(mac): # ------------------------------------------------------------------- class Plugin_Object: """ - Plugin_Object class to manage one object introduced by the plugin + Plugin_Object class to manage one object introduced by the plugin. An object typically is a device but could also be a website or something else that is monitored by the plugin. """ @@ -114,11 +114,15 @@ class Plugin_Object: watched4="", extra="", foreignKey="", + helpVal1="", + helpVal2="", + helpVal3="", + helpVal4="", ): self.pluginPref = "" self.primaryId = primaryId self.secondaryId = secondaryId - self.created = datetime.now(timeZone).strftime("%Y-%m-%d %H:%M:%S") + self.created = datetime.now().strftime("%Y-%m-%d %H:%M:%S") self.changed = "" self.watched1 = watched1 self.watched2 = watched2 @@ -128,13 +132,17 @@ class Plugin_Object: self.extra = extra self.userData = "" self.foreignKey = foreignKey + self.helpVal1 = helpVal1 or "" + self.helpVal2 = helpVal2 or "" + self.helpVal3 = helpVal3 or "" + self.helpVal4 = helpVal4 or "" def write(self): """ - write the object details as a string in the - format required to write the result file + Write the object details as a string in the + format required to write the result file. """ - line = "{}|{}|{}|{}|{}|{}|{}|{}|{}\n".format( + line = "{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}\n".format( self.primaryId, self.secondaryId, self.created, @@ -144,10 +152,12 @@ class Plugin_Object: self.watched4, self.extra, self.foreignKey, + self.helpVal1, + self.helpVal2, + self.helpVal3, + self.helpVal4 ) return line - - class Plugin_Objects: """ @@ -155,7 +165,7 @@ class Plugin_Objects: It contains a list of Plugin_Object instances. And can write the required result file. """ - + def __init__(self, result_file): self.result_file = result_file self.objects = [] @@ -170,6 +180,10 @@ class Plugin_Objects: watched4="", extra="", foreignKey="", + helpVal1="", + helpVal2="", + helpVal3="", + helpVal4="", ): self.objects.append( Plugin_Object( @@ -181,16 +195,17 @@ class Plugin_Objects: watched4, extra, foreignKey, + helpVal1, + helpVal2, + helpVal3, + helpVal4 ) ) def write_result_file(self): - # print ("writing file: "+self.result_file) with open(self.result_file, mode="w") as fp: for obj in self.objects: fp.write(obj.write()) - fp.close() - def __add__(self, other): if isinstance(other, Plugin_Objects): diff --git a/front/plugins/unifi_import/config.json b/front/plugins/unifi_import/config.json index 85d7763e..0c9f6d41 100755 --- a/front/plugins/unifi_import/config.json +++ b/front/plugins/unifi_import/config.json @@ -388,6 +388,38 @@ "show": true, "type": "label" }, + { + "column": "HelpVal1", + "mapped_to_column": "cur_NetworkNodeMAC", + "css_classes": "col-sm-2", + "default_value": "", + "localized": ["name"], + "name": [ + { + "language_code": "en_us", + "string": "Parent Network MAC" + } + ], + "options": [], + "show": true, + "type": "label" + }, + { + "column": "HelpVal2", + "mapped_to_column": "cur_PORT", + "css_classes": "col-sm-2", + "default_value": "", + "localized": ["name"], + "name": [ + { + "language_code": "en_us", + "string": "Port" + } + ], + "options": [], + "show": true, + "type": "label" + }, { "column": "Status", "css_classes": "col-sm-1", @@ -492,7 +524,7 @@ } }, { - "default_value": "python3 /app/front/plugins/unifi_import/script.py username={username} password={password} host={host} sites={sites} port={port} verifyssl={verifyssl} version={version} fullimport={fullimport}", + "default_value": "python3 /app/front/plugins/unifi_import/script.py", "description": [ { "language_code": "en_us", diff --git a/front/plugins/unifi_import/script.py b/front/plugins/unifi_import/script.py index 7729a3be..d1d9e6a5 100755 --- a/front/plugins/unifi_import/script.py +++ b/front/plugins/unifi_import/script.py @@ -35,52 +35,30 @@ pluginName = 'UNFIMP' def main(): - mylog('verbose', ['[UNFIMP] In script']) + mylog('verbose', [f'[{pluginName}] In script']) # init global variables global UNIFI_USERNAME, UNIFI_PASSWORD, UNIFI_HOST, UNIFI_SITES, PORT, VERIFYSSL, VERSION, FULL_IMPORT - - parser = argparse.ArgumentParser(description='Import devices from a UNIFI controller') - - parser.add_argument('username', action="store", help="Username used to login into the UNIFI controller") - parser.add_argument('password', action="store", help="Password used to login into the UNIFI controller") - parser.add_argument('host', action="store", help="Host url or IP address where the UNIFI controller is hosted (excluding http://)") - parser.add_argument('sites', action="store", help="Name of the sites (usually 'default', check the URL in your UniFi controller UI). Separated by comma (,) if passing multiple sites") - parser.add_argument('port', action="store", help="Usually 8443") - parser.add_argument('verifyssl', action="store", help="verify SSL certificate [true|false]") - parser.add_argument('version', action="store", help="The base version of the controller API [v4|v5|unifiOS|UDMP-unifiOS]") - parser.add_argument('fullimport', action="store", help="Defines if a full import or only online devices hould be imported [disabled|once|always]") - - values = parser.parse_args() - - - - # parse output - plugin_objects = Plugin_Objects(RESULT_FILE) - + plugin_objects = Plugin_Objects(RESULT_FILE) - mylog('verbose', [f'[UNFIMP] Check if all login information is available: {values}']) + UNIFI_USERNAME = get_setting_value("UNFIMP_username") + UNIFI_PASSWORD = get_setting_value("UNFIMP_password") + UNIFI_HOST = get_setting_value("UNFIMP_host") + UNIFI_SITES = get_setting_value("UNFIMP_sites") + PORT = get_setting_value("UNFIMP_port") + VERIFYSSL = get_setting_value("UNFIMP_verifyssl") + VERSION = get_setting_value("UNFIMP_version") + FULL_IMPORT = get_setting_value("UNFIMP_fullimport") - if values.username and values.password and values.host and values.sites: - - UNIFI_USERNAME = values.username.split('=')[1] - UNIFI_PASSWORD = values.password.split('=')[1] - UNIFI_HOST = values.host.split('=')[1] - UNIFI_SITES = values.sites.split('=')[1] - PORT = values.port.split('=')[1] - VERIFYSSL = values.verifyssl.split('=')[1] - VERSION = values.version.split('=')[1] - FULL_IMPORT = values.fullimport.split('=')[1] - - plugin_objects = get_entries(plugin_objects) + plugin_objects = get_entries(plugin_objects) plugin_objects.write_result_file() - mylog('verbose', [f'[UNFIMP] Scan finished, found {len(plugin_objects)} devices']) + mylog('verbose', [f'[{pluginName}] Scan finished, found {len(plugin_objects)} devices']) # ............................................. @@ -91,152 +69,166 @@ def get_entries(plugin_objects: Plugin_Objects) -> Plugin_Objects: lock_file_value = read_lock_file() perform_full_run = check_full_run_state(FULL_IMPORT, lock_file_value) + mylog('verbose', [f'[{pluginName}] sites: {UNIFI_SITES}']) - sites = [] - - if ',' in UNIFI_SITES: - sites = UNIFI_SITES.split(',') - - else: - sites.append(UNIFI_SITES) if (VERIFYSSL.upper() == "TRUE"): VERIFYSSL = True else: VERIFYSSL = False - - for site in sites: + + # mylog('verbose', [f'[{pluginName}] sites: {sites}']) + + for site in UNIFI_SITES: + + mylog('verbose', [f'[{pluginName}] site: {site}']) c = Controller(UNIFI_HOST, UNIFI_USERNAME, UNIFI_PASSWORD, port=PORT, version=VERSION, ssl_verify=VERIFYSSL, site_id=site) - mylog('verbose', [f'[UNFIMP] Identify Unifi Devices']) - # get all Unifi devices - for ap in c.get_aps(): - - # mylog('verbose', [f'{json.dumps(ap)}']) - - deviceType = '' - if (ap['type'] == 'udm'): - deviceType = 'Router' - elif (ap['type'] == 'usg'): - deviceType = 'Router' - elif (ap['type'] == 'usw'): - deviceType = 'Switch' - elif (ap['type'] == 'uap'): - deviceType = 'AP' - - name = get_unifi_val(ap, 'name') - hostName = get_unifi_val(ap, 'hostname') - - name = set_name(name, hostName) - - ipTmp = get_unifi_val(ap, 'ip') - - # if IP not found use a default value - if ipTmp == "null": - ipTmp = '0.0.0.0' - - plugin_objects.add_object( - primaryId=ap['mac'], - secondaryId=ipTmp, - watched1=name, - watched2='Ubiquiti Networks Inc.', - watched3=deviceType, - watched4=ap['state'], - extra=get_unifi_val(ap, 'connection_network_name') - ) - - - mylog('verbose', [f'[UNFIMP] Found {len(plugin_objects)} Unifi Devices']) - - online_macs = set() + processed_macs = [] - # get_clients() returns all clients which are currently online. - for cl in c.get_clients(): + mylog('verbose', [f'[{pluginName}] Get Online Devices']) - # mylog('verbose', [f'{json.dumps(cl)}']) - online_macs.add(cl['mac']) + # Collect details for online clients + collect_details( + device_type={'cl': ''}, + devices=c.get_clients(), + online_macs=online_macs, + processed_macs=processed_macs, + plugin_objects=plugin_objects, + device_label='client', + device_vendor="" + ) - - mylog('verbose', [f'[UNFIMP] Found {len(plugin_objects)} Online Devices']) + mylog('verbose', [f'[{pluginName}] Found {len(plugin_objects)} Online Devices']) - # get_users() returns all clients known by the controller - for user in c.get_users(): + mylog('verbose', [f'[{pluginName}] Identify Unifi Devices']) - #mylog('verbose', [f'{json.dumps(user)}']) + # Collect details for Unifi devices + collect_details( + device_type={ + 'udm': 'Router', + 'usg': 'Router', + 'usw': 'Switch', + 'uap': 'AP' + }, + devices=c.get_aps(), + online_macs=online_macs, + processed_macs=processed_macs, + plugin_objects=plugin_objects, + device_label='ap', + device_vendor="Ubiquiti Networks Inc." + ) - name = get_unifi_val(user, 'name') - hostName = get_unifi_val(user, 'hostname') + mylog('verbose', [f'[{pluginName}] Found {len(plugin_objects)} Unifi Devices']) - name = set_name(name, hostName) + # Collect details for users + collect_details( + device_type={'user': ''}, + devices=c.get_users(), + online_macs=online_macs, + processed_macs=processed_macs, + plugin_objects=plugin_objects, + device_label='user', + device_vendor="" + ) - status = 1 if user['mac'] in online_macs else 0 + mylog('verbose', [f'[{pluginName}] Found {len(plugin_objects)} Users']) - if status == 1 or perform_full_run is True: - - ipTmp = get_unifi_val(user, 'last_ip') - - if ipTmp == 'null': - ipTmp = get_unifi_val(user, 'fixed_ip') - - # if IP not found use a default value - if ipTmp == "null": - ipTmp = '0.0.0.0' - - plugin_objects.add_object( - primaryId=user['mac'], - secondaryId=ipTmp, - watched1=name, - watched2=get_unifi_val(user, 'oui'), - watched3='Other', - watched4=status, - extra=get_unifi_val(user, 'last_connection_network_name') - ) - - # check if the lockfile needs to be adapted - - mylog('verbose', [f'[UNFIMP] check if Lock file needs to be modified']) + + mylog('verbose', [f'[{pluginName}] check if Lock file needs to be modified']) set_lock_file_value(FULL_IMPORT, lock_file_value) - mylog('verbose', [f'[UNFIMP] Found {len(plugin_objects)} Clients overall']) + mylog('verbose', [f'[{pluginName}] Found {len(plugin_objects)} Clients overall']) return plugin_objects # ----------------------------------------------------------------------------- -def get_unifi_val(obj, key): +def collect_details(device_type, devices, online_macs, processed_macs, plugin_objects, device_label, device_vendor): + for device in devices: + mylog('verbose', [f'{json.dumps(device)}']) - res = '' + name = get_name(get_unifi_val(device, 'name'), get_unifi_val(device, 'hostname')) + ipTmp = get_ip(get_unifi_val(device, 'last_ip'), get_unifi_val(device, 'fixed_ip'), get_unifi_val(device, 'ip')) + macTmp = device['mac'] + status = 1 if macTmp in online_macs else device.get('state', 0) + deviceType = device_type.get(device.get('type'), '') - res = obj.get(key, None) - - if res not in ['','None', None]: - return res - - mylog('debug', [f'[{pluginName}] Value not found for key "{key}" in obj "{json.dumps(obj)}"']) - - return 'null' + # Add object only if not processed + if macTmp not in processed_macs: + plugin_objects.add_object( + primaryId=macTmp, + secondaryId=ipTmp, + watched1=name, + watched2=get_unifi_val(device, 'oui', device_vendor), + watched3=deviceType, + watched4=status, + extra=get_unifi_val(device, 'connection_network_name', ''), + foreignKey="", + helpVal1=get_parent_mac(get_unifi_val(device, 'uplink_mac'), get_unifi_val(device, 'ap_mac'), get_unifi_val(device, 'sw_mac')), + helpVal2=get_port(get_unifi_val(device, 'sw_port'), get_unifi_val(device, 'uplink_remote_port')), + helpVal3=device_label, + helpVal4="", + ) + processed_macs.append(macTmp) +# ----------------------------------------------------------------------------- +def get_unifi_val(obj, key, default='null'): + if isinstance(obj, dict): + if key in obj and obj[key] not in ['', 'None', None]: + return obj[key] + for k, v in obj.items(): + if isinstance(v, dict): + result = get_unifi_val(v, key, default) + if result not in ['','None', None, 'null']: + return result + + mylog('debug', [f'[{pluginName}] Value not found for key "{key}" in obj "{json.dumps(obj)}"']) + return default # ----------------------------------------------------------------------------- -def set_name(name: str, hostName: str) -> str: +def get_name(*names: str) -> str: + for name in names: + if name and name != 'null': + return name + return 'null' - if name != 'null': - return name +# ----------------------------------------------------------------------------- +def get_parent_mac(*macs: str) -> str: + for mac in macs: + if mac and mac != 'null': + return mac + return 'null' - elif name == 'null' and hostName != 'null': - return hostName +# ----------------------------------------------------------------------------- +def get_port(*ports: str) -> str: + for port in ports: + if port and port != 'null': + return port + return 'null' - else: - return 'null' +# ----------------------------------------------------------------------------- +def get_port(*macs: str) -> str: + for mac in macs: + if mac and mac != 'null': + return mac + return 'null' + +# ----------------------------------------------------------------------------- +def get_ip(*ips: str) -> str: + for ip in ips: + if ip and ip != 'null': + return ip + return '0:0:0:0' # ----------------------------------------------------------------------------- def set_lock_file_value(config_value: str, lock_file_value: bool) -> None: - mylog('verbose', [f'[UNFIMP] Lock Params: config_value={config_value}, lock_file_value={lock_file_value}']) + mylog('verbose', [f'[{pluginName}] Lock Params: config_value={config_value}, lock_file_value={lock_file_value}']) # set lock if 'once' is set and the lock is not set if config_value == 'once' and lock_file_value is False: out = 1 @@ -244,10 +236,10 @@ def set_lock_file_value(config_value: str, lock_file_value: bool) -> None: elif config_value != 'once' and lock_file_value is True: out = 0 else: - mylog('verbose', [f'[UNFIMP] No change on lock file needed']) + mylog('verbose', [f'[{pluginName}] No change on lock file needed']) return - mylog('verbose', [f'[UNFIMP] Setting lock value for "full import" to {out}']) + mylog('verbose', [f'[{pluginName}] Setting lock value for "full import" to {out}']) with open(LOCK_FILE, 'w') as lock_file: lock_file.write(str(out)) @@ -265,10 +257,10 @@ def read_lock_file() -> bool: # ----------------------------------------------------------------------------- def check_full_run_state(config_value: str, lock_file_value: bool) -> bool: if config_value == 'always' or (config_value == 'once' and lock_file_value == False): - mylog('verbose', [f'[UNFIMP] Full import needs to be done: config_value: {config_value} and lock_file_value: {lock_file_value}']) + mylog('verbose', [f'[{pluginName}] Full import needs to be done: config_value: {config_value} and lock_file_value: {lock_file_value}']) return True else: - mylog('verbose', [f'[UNFIMP] Full import NOT needed: config_value: {config_value} and lock_file_value: {lock_file_value}']) + mylog('verbose', [f'[{pluginName}] Full import NOT needed: config_value: {config_value} and lock_file_value: {lock_file_value}']) return False #=============================================================================== diff --git a/server/database.py b/server/database.py index 345db587..2a8d7186 100755 --- a/server/database.py +++ b/server/database.py @@ -385,6 +385,18 @@ class DB(): self.sql.execute(""" ALTER TABLE "Plugins_Objects" ADD "SyncHubNodeName" TEXT """) + + # helper columns HelpVal1-4 + plug_HelpValues_missing = self.sql.execute (""" + SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Plugins_Objects') WHERE name='HelpVal1' + """).fetchone()[0] == 0 + + if plug_HelpValues_missing : + mylog('verbose', ["[upgradeDB] Adding HelpVal1-4 to the Plugins_Objects table"]) + self.sql.execute('ALTER TABLE "Plugins_Objects" ADD COLUMN "HelpVal1" TEXT') + self.sql.execute('ALTER TABLE "Plugins_Objects" ADD COLUMN "HelpVal2" TEXT') + self.sql.execute('ALTER TABLE "Plugins_Objects" ADD COLUMN "HelpVal3" TEXT') + self.sql.execute('ALTER TABLE "Plugins_Objects" ADD COLUMN "HelpVal4" TEXT') # Plugin execution results sql_Plugins_Events = """ CREATE TABLE IF NOT EXISTS Plugins_Events( @@ -416,6 +428,18 @@ class DB(): self.sql.execute(""" ALTER TABLE "Plugins_Events" ADD "SyncHubNodeName" TEXT """) + + # helper columns HelpVal1-4 + plug_HelpValues_missing = self.sql.execute (""" + SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Plugins_Events') WHERE name='HelpVal1' + """).fetchone()[0] == 0 + + if plug_HelpValues_missing : + mylog('verbose', ["[upgradeDB] Adding HelpVal1-4 to the Plugins_Events table"]) + self.sql.execute('ALTER TABLE "Plugins_Events" ADD COLUMN "HelpVal1" TEXT') + self.sql.execute('ALTER TABLE "Plugins_Events" ADD COLUMN "HelpVal2" TEXT') + self.sql.execute('ALTER TABLE "Plugins_Events" ADD COLUMN "HelpVal3" TEXT') + self.sql.execute('ALTER TABLE "Plugins_Events" ADD COLUMN "HelpVal4" TEXT') # Plugin execution history @@ -448,6 +472,18 @@ class DB(): self.sql.execute(""" ALTER TABLE "Plugins_History" ADD "SyncHubNodeName" TEXT """) + + # helper columns HelpVal1-4 + plug_HelpValues_missing = self.sql.execute (""" + SELECT COUNT(*) AS CNTREC FROM pragma_table_info('Plugins_History') WHERE name='HelpVal1' + """).fetchone()[0] == 0 + + if plug_HelpValues_missing : + mylog('verbose', ["[upgradeDB] Adding HelpVal1-4 to the Plugins_History table"]) + self.sql.execute('ALTER TABLE "Plugins_History" ADD COLUMN "HelpVal1" TEXT') + self.sql.execute('ALTER TABLE "Plugins_History" ADD COLUMN "HelpVal2" TEXT') + self.sql.execute('ALTER TABLE "Plugins_History" ADD COLUMN "HelpVal3" TEXT') + self.sql.execute('ALTER TABLE "Plugins_History" ADD COLUMN "HelpVal4" TEXT') # ------------------------------------------------------------------------- diff --git a/server/helper.py b/server/helper.py index 53412ad0..7b86f47c 100755 --- a/server/helper.py +++ b/server/helper.py @@ -352,11 +352,16 @@ def setting_value_to_python_type(set_type, set_value): mylog('none', [f'[HELPER] No elements provided in set_type: {set_type} ']) return value - # Use the last element in the list - last_element = elements[len(elements)-1] - elementType = last_element.get('elementType', '') - elementOptions = last_element.get('elementOptions', []) - transformers = last_element.get('transformers', []) + # Find the first element where elementHasInputValue is 1 + element_with_input_value = next((elem for elem in elements if elem.get("elementHasInputValue") == 1), None) + + # If no such element is found, use the last element + if element_with_input_value is None: + element_with_input_value = elements[-1] + + elementType = element_with_input_value.get('elementType', '') + elementOptions = element_with_input_value.get('elementOptions', []) + transformers = element_with_input_value.get('transformers', []) # Convert value based on dataType and elementType if dataType == 'string' and elementType in ['input', 'select']: diff --git a/server/plugin.py b/server/plugin.py index 61ed752e..aace1d6a 100755 --- a/server/plugin.py +++ b/server/plugin.py @@ -248,32 +248,51 @@ def execute_plugin(db, all_plugins, plugin, pluginsState = plugins_state() ): for line in newLines: columns = line.split("|") - # There have to be always 9 columns - if len(columns) == 9: - # Create a tuple containing values to be inserted into the database. - # Each value corresponds to a column in the table in the order of the columns. - # must match the Plugins_Objects and Plugins_Events database tables and can be used as input for the plugin_object_class. - sqlParams.append( - ( - 0, # "Index" placeholder - plugin["unique_prefix"], # "Plugin" column value from the plugin dictionary - columns[0], # "Object_PrimaryID" value from columns list - columns[1], # "Object_SecondaryID" value from columns list - 'null', # Placeholder for "DateTimeCreated" column - columns[2], # "DateTimeChanged" value from columns list - columns[3], # "Watched_Value1" value from columns list - columns[4], # "Watched_Value2" value from columns list - columns[5], # "Watched_Value3" value from columns list - columns[6], # "Watched_Value4" value from columns list - 'not-processed', # "Status" column (placeholder) - columns[7], # "Extra" value from columns list - 'null', # Placeholder for "UserData" column - columns[8], # "ForeignKey" value from columns list - tmp_SyncHubNodeName # Sync Hub Node name - ) - ) + # There have to be 9 or 13 columns + # Common part of the SQL parameters + base_params = [ + 0, # "Index" placeholder + plugin["unique_prefix"], # "Plugin" column value from the plugin dictionary + columns[0], # "Object_PrimaryID" value from columns list + columns[1], # "Object_SecondaryID" value from columns list + 'null', # Placeholder for "DateTimeCreated" column + columns[2], # "DateTimeChanged" value from columns list + columns[3], # "Watched_Value1" value from columns list + columns[4], # "Watched_Value2" value from columns list + columns[5], # "Watched_Value3" value from columns list + columns[6], # "Watched_Value4" value from columns list + 'not-processed', # "Status" column (placeholder) + columns[7], # "Extra" value from columns list + 'null', # Placeholder for "UserData" column + columns[8], # "ForeignKey" value from columns list + tmp_SyncHubNodeName # Sync Hub Node name + ] + + # Extend the common part with the additional values if there are 13 columns + if len(columns) == 13: + base_params.extend([ + columns[9], # "HelpVal1" value from columns list + columns[10], # "HelpVal2" value from columns list + columns[11], # "HelpVal3" value from columns list + columns[12] # "HelpVal4" value from columns list + ]) + elif len(columns) == 9: + # add padding + base_params.extend([ + 'null', # "HelpVal1" + 'null', # "HelpVal2" + 'null', # "HelpVal3" + 'null' # "HelpVal4" + ]) else: - mylog('none', ['[Plugins] Skipped invalid line in the output: ', line]) + mylog('none', [f'[Plugins] Wrong number of input values, must be 9 or 13, got {len(columns)} from: {line} ']) + + # Create a tuple containing values to be inserted into the database. + # Each value corresponds to a column in the table in the order of the columns. + # must match the Plugins_Objects and Plugins_Events database tables and can be used as input for the plugin_object_class. + + # Append the final parameters to sqlParams + sqlParams.append(tuple(base_params)) # keep current instance log file, delete all from other nodes if filename != 'last_result.log' and os.path.exists(full_path): @@ -293,30 +312,48 @@ def execute_plugin(db, all_plugins, plugin, pluginsState = plugins_state() ): arr = db.get_sql_array (q) for row in arr: - # There has to be always 9 columns - if len(row) == 9 and (row[0] in ['','null']) == False : - # Create a tuple containing values to be inserted into the database. + # There has to be always 9 or 13 columns + if len(row) in [9, 13] and row[0] not in ['', 'null']: + # Create a base tuple containing values to be inserted into the database. # Each value corresponds to a column in the table in the order of the columns. - # must match the Plugins_Objects and Plugins_Events database tables and can be used as input for the plugin_object_class - sqlParams.append( - ( - 0, # "Index" placeholder - plugin["unique_prefix"], # "Plugin" plugin dictionary - row[0], # "Object_PrimaryID" row - handle_empty(row[1]), # "Object_SecondaryID" column after handling empty values - 'null', # Placeholder "DateTimeCreated" column - row[2], # "DateTimeChanged" row - row[3], # "Watched_Value1" row - row[4], # "Watched_Value2" row - handle_empty(row[5]), # "Watched_Value3" column after handling empty values - handle_empty(row[6]), # "Watched_Value4" column after handling empty values - 'not-processed', # "Status" column (placeholder) - row[7], # "Extra" row - 'null', # Placeholder "UserData" column - row[8], # "ForeignKey" row - 'null' # Sync Hub Node name - Only supported with scripts - ) - ) + # Must match the Plugins_Objects and Plugins_Events database tables and can be used as input for the plugin_object_class. + base_params = [ + 0, # "Index" placeholder + plugin["unique_prefix"], # "Plugin" plugin dictionary + row[0], # "Object_PrimaryID" row + handle_empty(row[1]), # "Object_SecondaryID" column after handling empty values + 'null', # Placeholder "DateTimeCreated" column + row[2], # "DateTimeChanged" row + row[3], # "Watched_Value1" row + row[4], # "Watched_Value2" row + handle_empty(row[5]), # "Watched_Value3" column after handling empty values + handle_empty(row[6]), # "Watched_Value4" column after handling empty values + 'not-processed', # "Status" column (placeholder) + row[7], # "Extra" row + 'null', # Placeholder "UserData" column + row[8], # "ForeignKey" row + 'null' # Sync Hub Node name - Only supported with scripts + ] + + # Extend the base tuple with additional values if there are 13 columns + if len(row) == 13: + base_params.extend([ + row[9], # "HelpVal1" row + row[10], # "HelpVal2" row + row[11], # "HelpVal3" row + row[12] # "HelpVal4" row + ]) + else: + # add padding + base_params.extend([ + 'null', # "HelpVal1" + 'null', # "HelpVal2" + 'null', # "HelpVal3" + 'null' # "HelpVal4" + ]) + + # Append the final parameters to sqlParams + sqlParams.append(tuple(base_params)) else: mylog('none', ['[Plugins] Skipped invalid sql result']) @@ -352,28 +389,48 @@ def execute_plugin(db, all_plugins, plugin, pluginsState = plugins_state() ): return pluginsState for row in arr: - # There has to be always 9 columns - if len(row) == 9 and (row[0] in ['','null']) == False : - # Create a tuple containing values to be inserted into the database. + # There has to be always 9 or 13 columns + if len(row) in [9, 13] and row[0] not in ['', 'null']: + # Create a base tuple containing values to be inserted into the database. # Each value corresponds to a column in the table in the order of the columns. - # must match the Plugins_Objects and Plugins_Events database tables and can be used as input for the plugin_object_class - sqlParams.append(( - 0, # "Index" placeholder - plugin["unique_prefix"], # "Plugin" - row[0], # "Object_PrimaryID" - handle_empty(row[1]), # "Object_SecondaryID" - 'null', # "DateTimeCreated" column (null placeholder) - row[2], # "DateTimeChanged" - row[3], # "Watched_Value1" - row[4], # "Watched_Value2" - handle_empty(row[5]), # "Watched_Value3" - handle_empty(row[6]), # "Watched_Value4" - 'not-processed', # "Status" column (placeholder) - row[7], # "Extra" - 'null', # "UserData" column (null placeholder) - row[8], # "ForeignKey" - 'null' # Sync Hub Node name - Only supported with scripts - )) + # Must match the Plugins_Objects and Plugins_Events database tables and can be used as input for the plugin_object_class. + base_params = [ + 0, # "Index" placeholder + plugin["unique_prefix"], # "Plugin" + row[0], # "Object_PrimaryID" + handle_empty(row[1]), # "Object_SecondaryID" + 'null', # "DateTimeCreated" column (null placeholder) + row[2], # "DateTimeChanged" + row[3], # "Watched_Value1" + row[4], # "Watched_Value2" + handle_empty(row[5]), # "Watched_Value3" + handle_empty(row[6]), # "Watched_Value4" + 'not-processed', # "Status" column (placeholder) + row[7], # "Extra" + 'null', # "UserData" column (null placeholder) + row[8], # "ForeignKey" + 'null' # Sync Hub Node name - Only supported with scripts + ] + + # Extend the base tuple with additional values if there are 13 columns + if len(row) == 13: + base_params.extend([ + row[9], # "HelpVal1" + row[10], # "HelpVal2" + row[11], # "HelpVal3" + row[12] # "HelpVal4" + ]) + else: + # add padding + base_params.extend([ + 'null', # "HelpVal1" + 'null', # "HelpVal2" + 'null', # "HelpVal3" + 'null' # "HelpVal4" + ]) + + # Append the final parameters to sqlParams + sqlParams.append(tuple(base_params)) else: mylog('none', ['[Plugins] Skipped invalid sql result']) @@ -509,12 +566,13 @@ def process_plugin_events(db, plugin, pluginsState, plugEventsArr): for plugObj in pluginObjects: # keep old createdTime time if the plugObj already was created before createdTime = plugObj.changed if plugObj.status == 'new' else plugObj.created - # 14 values without Index + # 18 values without Index values = ( plugObj.pluginPref, plugObj.primaryId, plugObj.secondaryId, createdTime, plugObj.changed, plugObj.watched1, plugObj.watched2, plugObj.watched3, plugObj.watched4, plugObj.status, plugObj.extra, plugObj.userData, - plugObj.foreignKey, plugObj.syncHubNodeName + plugObj.foreignKey, plugObj.syncHubNodeName, + plugObj.helpVal1, plugObj.helpVal2, plugObj.helpVal3, plugObj.helpVal4 ) if plugObj.status == 'new': @@ -547,8 +605,9 @@ def process_plugin_events(db, plugin, pluginsState, plugEventsArr): INSERT INTO Plugins_Objects ("Plugin", "Object_PrimaryID", "Object_SecondaryID", "DateTimeCreated", "DateTimeChanged", "Watched_Value1", "Watched_Value2", "Watched_Value3", - "Watched_Value4", "Status", "Extra", "UserData", "ForeignKey", "SyncHubNodeName") - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + "Watched_Value4", "Status", "Extra", "UserData", "ForeignKey", "SyncHubNodeName", + "HelpVal1", "HelpVal2", "HelpVal3", "HelpVal4") + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, objects_to_insert ) @@ -559,7 +618,7 @@ def process_plugin_events(db, plugin, pluginsState, plugEventsArr): UPDATE Plugins_Objects SET "Plugin" = ?, "Object_PrimaryID" = ?, "Object_SecondaryID" = ?, "DateTimeCreated" = ?, "DateTimeChanged" = ?, "Watched_Value1" = ?, "Watched_Value2" = ?, "Watched_Value3" = ?, - "Watched_Value4" = ?, "Status" = ?, "Extra" = ?, "UserData" = ?, "ForeignKey" = ?, "SyncHubNodeName" = ? + "Watched_Value4" = ?, "Status" = ?, "Extra" = ?, "UserData" = ?, "ForeignKey" = ?, "SyncHubNodeName" = ?, "HelpVal1" = ?, "HelpVal2" = ?, "HelpVal3" = ?, "HelpVal4" = ? WHERE "Index" = ? """, objects_to_update ) @@ -572,8 +631,9 @@ def process_plugin_events(db, plugin, pluginsState, plugEventsArr): INSERT INTO Plugins_Events ("Plugin", "Object_PrimaryID", "Object_SecondaryID", "DateTimeCreated", "DateTimeChanged", "Watched_Value1", "Watched_Value2", "Watched_Value3", - "Watched_Value4", "Status", "Extra", "UserData", "ForeignKey", "SyncHubNodeName") - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + "Watched_Value4", "Status", "Extra", "UserData", "ForeignKey", "SyncHubNodeName", + "HelpVal1", "HelpVal2", "HelpVal3", "HelpVal4") + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, events_to_insert ) @@ -585,8 +645,9 @@ def process_plugin_events(db, plugin, pluginsState, plugEventsArr): INSERT INTO Plugins_History ("Plugin", "Object_PrimaryID", "Object_SecondaryID", "DateTimeCreated", "DateTimeChanged", "Watched_Value1", "Watched_Value2", "Watched_Value3", - "Watched_Value4", "Status", "Extra", "UserData", "ForeignKey", "SyncHubNodeName") - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + "Watched_Value4", "Status", "Extra", "UserData", "ForeignKey", "SyncHubNodeName", + "HelpVal1", "HelpVal2", "HelpVal3", "HelpVal4") + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, history_to_insert ) @@ -665,6 +726,14 @@ def process_plugin_events(db, plugin, pluginsState, plugEventsArr): tmpList.append(plgEv.status) elif col['column'] == 'SyncHubNodeName': tmpList.append(plgEv.syncHubNodeName) + elif col['column'] == 'HelpVal1': + tmpList.append(plgEv.helpVal1) + elif col['column'] == 'HelpVal2': + tmpList.append(plgEv.helpVal2) + elif col['column'] == 'HelpVal3': + tmpList.append(plgEv.helpVal3) + elif col['column'] == 'HelpVal4': + tmpList.append(plgEv.helpVal4) # Check if there's a default value specified for this column in the JSON. if 'mapped_to_column_data' in col and 'value' in col['mapped_to_column_data']: @@ -714,6 +783,11 @@ class plugin_object_class: self.userData = objDbRow[12] self.foreignKey = objDbRow[13] self.syncHubNodeName = objDbRow[14] + self.helpVal1 = objDbRow[15] + self.helpVal2 = objDbRow[16] + self.helpVal3 = objDbRow[17] + self.helpVal4 = objDbRow[18] + # Check if self.status is valid if self.status not in ["exists", "watched-changed", "watched-not-changed", "new", "not-processed", "missing-in-last-scan"]: @@ -727,6 +801,7 @@ class plugin_object_class: setObj = get_plugin_setting_obj(plugin, 'WATCH') + # hash for comapring watched value changes indexNameColumnMapping = [(6, 'Watched_Value1' ), (7, 'Watched_Value2' ), (8, 'Watched_Value3' ), (9, 'Watched_Value4' )] if setObj is not None: From 8199bef55d2f2b3b3b16ba505c9705188ee786a4 Mon Sep 17 00:00:00 2001 From: lookflying Date: Wed, 7 Aug 2024 22:34:39 +0800 Subject: [PATCH 05/13] mtscan works --- .gitignore | 3 +- Dockerfile | 2 +- front/log/.gitignore | 0 front/plugins/mikrotik_scan/README.md | 7 + front/plugins/mikrotik_scan/config.json | 437 ++++++++++++++++++++++++ front/plugins/mikrotik_scan/mikrotik.py | 142 ++++++++ 6 files changed, 589 insertions(+), 2 deletions(-) mode change 100755 => 100644 front/log/.gitignore create mode 100755 front/plugins/mikrotik_scan/README.md create mode 100755 front/plugins/mikrotik_scan/config.json create mode 100755 front/plugins/mikrotik_scan/mikrotik.py diff --git a/.gitignore b/.gitignore index ff1ef480..933ebc9d 100755 --- a/.gitignore +++ b/.gitignore @@ -19,4 +19,5 @@ __pycache__/ **/last_result.log **/script.log **/pialert.conf_bak -**/pialert.db_bak \ No newline at end of file +**/pialert.db_bak +.*.swp diff --git a/Dockerfile b/Dockerfile index 9ce5e9e2..48eb5d6f 100755 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,7 @@ ENV PATH="/opt/venv/bin:$PATH" COPY . ${INSTALL_DIR}/ -RUN pip install netifaces tplink-omada-client pycryptodome requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet python-nmap dnspython \ +RUN pip install netifaces tplink-omada-client pycryptodome requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet python-nmap dnspython librouteros \ && bash -c "find ${INSTALL_DIR} -type d -exec chmod 750 {} \;" \ && bash -c "find ${INSTALL_DIR} -type f -exec chmod 640 {} \;" \ && bash -c "find ${INSTALL_DIR} -type f \( -name '*.sh' -o -name '*.py' -o -name 'speedtest-cli' \) -exec chmod 750 {} \;" diff --git a/front/log/.gitignore b/front/log/.gitignore old mode 100755 new mode 100644 diff --git a/front/plugins/mikrotik_scan/README.md b/front/plugins/mikrotik_scan/README.md new file mode 100755 index 00000000..3d743790 --- /dev/null +++ b/front/plugins/mikrotik_scan/README.md @@ -0,0 +1,7 @@ +## Overview + +Plugin for device name discovery via the Mikrotik dhcp-server leases + +### Usage + +- Check the Settings page for details. diff --git a/front/plugins/mikrotik_scan/config.json b/front/plugins/mikrotik_scan/config.json new file mode 100755 index 00000000..baca3a15 --- /dev/null +++ b/front/plugins/mikrotik_scan/config.json @@ -0,0 +1,437 @@ +{ + "code_name": "mikrotik_scan", + "unique_prefix": "MTSCAN", + "plugin_type": "other", + "execution_order" : "Layer_4", + "enabled": true, + "data_source": "script", + "mapped_to_table": "CurrentScan", + "show_ui": true, + "localized": ["display_name", "description", "icon"], + "display_name": [ + { + "language_code": "en_us", + "string": "Mikrotik (Name discovery)" + } + ], + "icon": [ + { + "language_code": "en_us", + "string": "" + } + ], + "description": [ + { + "language_code": "en_us", + "string": "A plugin to discover device names." + } + ], + "params": [ + { + "name": "ips", + "type": "sql", + "value": "SELECT dev_LastIP from DEVICES order by dev_MAC", + "timeoutMultiplier": true + }, + { + "name": "mt_host", + "type": "setting", + "value": "MTSCAN_MT_HOST" + }, + { + "name": "mt_port", + "type": "setting", + "value": "MTSCAN_MT_PORT" + }, + { + "name": "mt_user", + "type": "setting", + "value": "MTSCAN_MT_USER" + }, + { + "name": "mt_pass", + "type": "setting", + "value": "MTSCAN_MT_PASS" + } + ], + "settings": [ + { + "function": "RUN", + "events": ["run"], + "type": { + "dataType": "string", + "elements": [ + { "elementType": "select", "elementOptions": [], "transformers": [] } + ] + }, + "default_value": "disabled", + "options": [ + "disabled", + "before_name_updates", + "on_new_device", + "once", + "schedule", + "always_after_scan" + ], + "localized": ["name", "description"], + "name": [ + { + "language_code": "en_us", + "string": "When to run" + } + ], + "description": [ + { + "language_code": "en_us", + "string": "When the plugin should be executed. If enabled this will execute the scan until there are no (unknown) or (name not found) devices. Setting this to on_new_device or a daily schedule is recommended." + } + ] + }, + { + "function": "CMD", + "type": { + "dataType": "string", + "elements": [ + { + "elementType": "input", + "elementOptions": [{ "readonly": "true" }], + "transformers": [] + } + ] + }, + "default_value": "python3 /app/front/plugins/mikrotik_scan/mikrotik.py", + "options": [], + "localized": ["name", "description"], + "name": [ + { + "language_code": "en_us", + "string": "Command" + } + ], + "description": [ + { + "language_code": "en_us", + "string": "Command to run. This can not be changed" + } + ] + }, + { + "function": "RUN_SCHD", + "type": { + "dataType": "string", + "elements": [ + { "elementType": "input", "elementOptions": [], "transformers": [] } + ] + }, + "default_value": "*/30 * * * *", + "options": [], + "localized": ["name", "description"], + "name": [ + { + "language_code": "en_us", + "string": "Schedule" + } + ], + "description": [ + { + "language_code": "en_us", + "string": "Only enabled if you select schedule in the MKTSCAN_RUN setting. Make sure you enter the schedule in the correct cron-like format (e.g. validate at crontab.guru). For example entering 0 4 * * * will run the scan after 4 am in the TIMEZONE you set above. Will be run NEXT time the time passes." + } + ] + }, + { + "function": "MT_HOST", + "type": { + "dataType": "string", + "elements": [ + { + "elementType": "input", + "elementOptions": [], + "transformers": [] + } + ] + }, + "default_value": "192.168.88.1", + "options": [], + "localized": ["name", "description"], + "name": [ + { + "language_code": "en_us", + "string": "Mikrotik Host IP" + } + ], + "description": [ + { + "language_code": "en_us", + "string": "IP for Mikrotik Router" + } + ] + }, + { + "function": "MT_PORT", + "type": { + "dataType": "integer", + "elements": [ + { + "elementType": "input", + "elementOptions": [{ "type": "number" }], + "transformers": [] + } + ] + }, + "default_value": 8728, + "options": [], + "localized": ["name", "description"], + "name": [ + { + "language_code": "en_us", + "string": "Mikrotik API Port" + } + ], + "description": [ + { + "language_code": "en_us", + "string": "API Port for Mikrotik Router" + } + ] + }, + { + "function": "MT_USER", + "type": { + "dataType": "string", + "elements": [ + { + "elementType": "input", + "elementOptions": [], + "transformers": [] + } + ] + }, + "default_value": "admin", + "options": [], + "localized": ["name", "description"], + "name": [ + { + "language_code": "en_us", + "string": "Mikrotik User" + } + ], + "description": [ + { + "language_code": "en_us", + "string": "User for Mikrotik Router" + } + ] + }, + { + "function": "MT_PASS", + "type": { + "dataType": "string", + "elements": [ + { + "elementType": "input", + "elementOptions": [{ "type": "password" }], + "transformers": [] + } + ] + }, + "default_value": "", + "options": [], + "localized": ["name", "description"], + "name": [ + { + "language_code": "en_us", + "string": "Mikrotik Password" + } + ], + "description": [ + { + "language_code": "en_us", + "string": "Password for Mikrotik Router" + } + ] + } + ], + "database_column_definitions": [ + { + "column": "Object_PrimaryID", + "mapped_to_column": "cur_MAC", + "css_classes": "col-sm-2", + "show": true, + "type": "device_name_mac", + "default_value": "", + "options": [], + "localized": ["name"], + "name": [ + { + "language_code": "en_us", + "string": "Name" + } + ] + }, + { + "column": "ForeignKey", + "css_classes": "col-sm-2", + "show": true, + "type": "device_mac", + "default_value": "", + "options": [], + "localized": ["name"], + "name": [ + { + "language_code": "en_us", + "string": "MAC" + } + ] + }, + { + "column": "Object_SecondaryID", + "mapped_to_column": "cur_IP", + "css_classes": "col-sm-2", + "show": true, + "type": "device_ip", + "default_value": "", + "options": [], + "localized": ["name"], + "name": [ + { + "language_code": "en_us", + "string": "IP" + } + ] + }, + { + "column": "Watched_Value1", + "css_classes": "col-sm-2", + "show": true, + "type": "device_ip", + "default_value": "", + "options": [], + "localized": ["name"], + "name": [ + { + "language_code": "en_us", + "string": "Lease IP" + } + ] + }, + { + "column": "Watched_Value2", + "mapped_to_column": "cur_Name", + "css_classes": "col-sm-2", + "show": true, + "type": "label", + "default_value": "", + "options": [], + "localized": ["name"], + "name": [ + { + "language_code": "en_us", + "string": "Name" + } + ] + }, + { + "column": "Watched_Value3", + "css_classes": "col-sm-2", + "show": true, + "type": "label", + "default_value": "", + "options": [], + "localized": ["name"], + "name": [ + { + "language_code": "en_us", + "string": "Host Name" + } + ] + }, + { + "column": "Watched_Value4", + "css_classes": "col-sm-2", + "show": true, + "type": "label", + "default_value": "", + "options": [], + "localized": ["name"], + "name": [ + { + "language_code": "en_us", + "string": "Last Seen" + } + ] + }, + { + "column": "HelpVal1", + "css_classes": "col-sm-2", + "show": true, + "type": "label", + "default_value": "", + "options": [], + "localized": ["name"], + "name": [ + { + "language_code": "en_us", + "string": "Comment" + } + ] + }, + { + "column": "Dummy", + "mapped_to_column": "cur_ScanMethod", + "mapped_to_column_data": { + "value": "MTSCAN" + }, + "css_classes": "col-sm-2", + "show": false, + "type": "label", + "default_value": "", + "options": [], + "localized": ["name"], + "name": [ + { + "language_code": "en_us", + "string": "Scan method" + } + ] + }, + { + "column": "DateTimeCreated", + "css_classes": "col-sm-2", + "show": true, + "type": "label", + "default_value": "", + "options": [], + "localized": ["name"], + "name": [ + { + "language_code": "en_us", + "string": "Created" + }, + { + "language_code": "es_es", + "string": "Creado" + } + ] + }, + { + "column": "DateTimeChanged", + "css_classes": "col-sm-2", + "show": true, + "type": "label", + "default_value": "", + "options": [], + "localized": ["name"], + "name": [ + { + "language_code": "en_us", + "string": "Changed" + }, + { + "language_code": "es_es", + "string": "Cambiado" + } + ] + } + ] +} diff --git a/front/plugins/mikrotik_scan/mikrotik.py b/front/plugins/mikrotik_scan/mikrotik.py new file mode 100755 index 00000000..88c2f098 --- /dev/null +++ b/front/plugins/mikrotik_scan/mikrotik.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python +# test script by running: +# tbc + +import os +import pathlib +import argparse +import subprocess +import sys +import hashlib +import csv +import sqlite3 +import re +from io import StringIO +from datetime import datetime + +# Register NetAlertX directories +INSTALL_PATH="/app" +sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"]) + +from plugin_helper import Plugin_Object, Plugin_Objects, decodeBase64 +from logger import mylog, append_line_to_file +from helper import timeNowTZ, get_setting_value +from const import logPath, applicationPath, fullDbPath +from database import DB +from device import Device_obj +import conf +from pytz import timezone +from librouteros import connect +from librouteros.exceptions import TrapError + +# Make sure the TIMEZONE for logging is correct +conf.tz = timezone(get_setting_value('TIMEZONE')) + +CUR_PATH = str(pathlib.Path(__file__).parent.resolve()) +LOG_FILE = os.path.join(CUR_PATH, 'script.log') +RESULT_FILE = os.path.join(CUR_PATH, 'last_result.log') + +pluginName = 'NSLOOKUP' + +def main(): + + mylog('verbose', [f'[{pluginName}] In script']) + + mt_host = get_setting_value('MTSCAN_MT_HOST') + mt_port = get_setting_value('MTSCAN_MT_PORT') + mt_user = get_setting_value('MTSCAN_MT_USER') + mt_password = get_setting_value('MTSCAN_MT_PASS') + + #mylog('verbose', [f'[{pluginName}] Router: {mt_host}:{mt_port} user: {mt_user}, pass: {mt_password}']) + # Create a database connection + db = DB() # instance of class DB + db.open() + + # Initialize the Plugin obj output file + plugin_objects = Plugin_Objects(RESULT_FILE) + + # Create a Device_obj instance + device_handler = Device_obj(db) + + # Retrieve devices + #unknown_devices = device_handler.getUnknown() + #mylog('verbose', [f'[{pluginName}] Unknown devices count: {len(unknown_devices)}']) + + all_devices = device_handler.getAll() + + mylog('verbose', [f'[{pluginName}] all devices count: {len(all_devices)}']) + + device_map = {d['dev_MAC']:d['dev_LastIP'] for d in all_devices} + + try: + # connect router + api = connect(username=mt_user, password=mt_password, host=mt_host, port=mt_port) + + # get dhcp leases + leases = api('/ip/dhcp-server/lease/print') + + + + for lease in leases: + lease_id = lease.get('.id') + address = lease.get('address') + mac_address = lease.get('mac-address').lower() + host_name = lease.get('host-name') + comment = lease.get('comment') + last_seen = lease.get('last-seen') + + mylog('verbose', [f"ID: {lease_id}, Address: {address}, MAC Address: {mac_address}, Host Name: {host_name}, Comment: {comment}, Last Seen: {last_seen}"]) + if mac_address in device_map.keys(): + device_name = host_name + if comment != '': + device_name = comment + + plugin_objects.add_object( + # "Name-MAC", "LastIP", "IP", "Name","Host","LastSeen","Comment" + primaryId = mac_address, + secondaryId = device_map[mac_address], + watched1 = address, + watched2 = device_name, + watched3 = host_name, + watched4 = last_seen, + extra = '', + helpVal1 = comment, + foreignKey = mac_address) + + plugin_objects.write_result_file() + except TrapError as e: + mylog('error', [f"An error occurred: {e}"]) + except Exception as e: + mylog('error', [f"Failed to connect to MikroTik API: {e}"]) + + + #for device in unknown_devices: + # domain_name, dns_server = execute_nslookup(device['dev_LastIP'], timeout) + + # if domain_name != '': + # plugin_objects.add_object( + # # "MAC", "IP", "Server", "Name" + # primaryId = device['dev_MAC'], + # secondaryId = device['dev_LastIP'], + # watched1 = dns_server, + # watched2 = domain_name, + # watched3 = '', + # watched4 = '', + # extra = '', + # foreignKey = device['dev_MAC']) + + #plugin_objects.write_result_file() + + + mylog('verbose', [f'[{pluginName}] Script finished']) + + return 0 + + + + +#=============================================================================== +# BEGIN +#=============================================================================== +if __name__ == '__main__': + main() From cfa0b3c38702da4bb78746656ec05e43afad5b47 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 8 Aug 2024 11:53:43 +0000 Subject: [PATCH 06/13] =?UTF-8?q?[=F0=9F=A4=96Automation]=20Update=20READM?= =?UTF-8?q?E=20with=20sponsors=20information?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From b4f1e6a5daff5fba222ebad5cd25175432514f0d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 9 Aug 2024 11:53:43 +0000 Subject: [PATCH 07/13] =?UTF-8?q?[=F0=9F=A4=96Automation]=20Update=20READM?= =?UTF-8?q?E=20with=20sponsors=20information?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From c88afde5f89120df9f49d6341e55bebac794b931 Mon Sep 17 00:00:00 2001 From: elraro Date: Sat, 10 Aug 2024 12:48:25 +0200 Subject: [PATCH 08/13] fix: mtscan plugin Change the MTSCAN plugin with the correct name --- front/plugins/mikrotik_scan/mikrotik.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/plugins/mikrotik_scan/mikrotik.py b/front/plugins/mikrotik_scan/mikrotik.py index 88c2f098..d8594bf6 100755 --- a/front/plugins/mikrotik_scan/mikrotik.py +++ b/front/plugins/mikrotik_scan/mikrotik.py @@ -36,7 +36,7 @@ CUR_PATH = str(pathlib.Path(__file__).parent.resolve()) LOG_FILE = os.path.join(CUR_PATH, 'script.log') RESULT_FILE = os.path.join(CUR_PATH, 'last_result.log') -pluginName = 'NSLOOKUP' +pluginName = 'MTSCAN' def main(): From 552e861887172eb20b712578a0f281ae30a41eb2 Mon Sep 17 00:00:00 2001 From: Ramon Martinez <1026ramon@gmail.com> Date: Sat, 10 Aug 2024 11:10:28 +0000 Subject: [PATCH 09/13] Translated using Weblate (Portuguese (Brazil)) Currently translated at 34.4% (238 of 691 strings) Translation: NetAlertX/core Translate-URL: https://hosted.weblate.org/projects/pialert/core/pt_BR/ --- front/php/templates/language/pt_br.json | 134 ++++++++++++------------ 1 file changed, 67 insertions(+), 67 deletions(-) mode change 100755 => 100644 front/php/templates/language/pt_br.json diff --git a/front/php/templates/language/pt_br.json b/front/php/templates/language/pt_br.json old mode 100755 new mode 100644 index e32d9bc1..8b6f1b85 --- a/front/php/templates/language/pt_br.json +++ b/front/php/templates/language/pt_br.json @@ -3,7 +3,7 @@ "API_CUSTOM_SQL_name": "Endpoint customizado", "API_display_name": "API", "API_icon": "", - "About_Design": "Desenhado por:", + "About_Design": "Desenvolvido por:", "About_Exit": "Sair", "About_Title": "Analisador de segurança de rede & framework de notificação", "AppEvents_DateTimeCreated": "Registrado em", @@ -202,7 +202,7 @@ "Device_TableHead_Group": "Grupo", "Device_TableHead_Icon": "Ícone", "Device_TableHead_LastIP": "Último IP", - "Device_TableHead_LastIPOrder": "", + "Device_TableHead_LastIPOrder": "Último pedido de IP", "Device_TableHead_LastSession": "Último off-line", "Device_TableHead_Location": "Localização", "Device_TableHead_MAC": "MAC aleatório", @@ -242,7 +242,7 @@ "Events_Periodselect_today": "Hoje", "Events_Searchbox": "Procurar", "Events_Shortcut_AllEvents": "Todos os eventos", - "Events_Shortcut_DownAlerts": "", + "Events_Shortcut_DownAlerts": "Alertas de queda", "Events_Shortcut_Events": "Eventos", "Events_Shortcut_MissSessions": "Sessões ausentes", "Events_Shortcut_NewDevices": "Novos dispositivos", @@ -254,76 +254,76 @@ "Events_TableHead_Device": "Dispositivo", "Events_TableHead_Disconnection": "Desconexão", "Events_TableHead_Duration": "Duração", - "Events_TableHead_DurationOrder": "", + "Events_TableHead_DurationOrder": "Duração do pedido", "Events_TableHead_EventType": "Tipo de evento", "Events_TableHead_IP": "IP", - "Events_TableHead_IPOrder": "", + "Events_TableHead_IPOrder": "Pedido de IP", "Events_TableHead_Order": "Ordem", "Events_TableHead_Owner": "Proprietário", - "Events_TableHead_PendingAlert": "", - "Events_Table_info": "", - "Events_Table_nav_next": "", - "Events_Table_nav_prev": "", - "Events_Tablelenght": "", - "Events_Tablelenght_all": "", - "Events_Title": "", - "Gen_Action": "", - "Gen_Add": "", - "Gen_Add_All": "", - "Gen_All_Devices": "", - "Gen_AreYouSure": "", - "Gen_Backup": "", - "Gen_Cancel": "", - "Gen_Copy": "", - "Gen_DataUpdatedUITakesTime": "", - "Gen_Delete": "", - "Gen_DeleteAll": "", - "Gen_Error": "", - "Gen_Filter": "", - "Gen_LockedDB": "", - "Gen_Offline": "", - "Gen_Okay": "", - "Gen_Purge": "", - "Gen_ReadDocs": "", - "Gen_Remove_All": "", - "Gen_Remove_Last": "", - "Gen_Restore": "", - "Gen_Run": "", - "Gen_Save": "", - "Gen_Saved": "", - "Gen_Search": "", - "Gen_Selected_Devices": "", - "Gen_Switch": "", - "Gen_Upd": "", - "Gen_Upd_Fail": "", - "Gen_Update": "", - "Gen_Update_Value": "", - "Gen_Warning": "", - "Gen_Work_In_Progress": "", - "General_display_name": "", - "General_icon": "", - "HRS_TO_KEEP_NEWDEV_description": "", - "HRS_TO_KEEP_NEWDEV_name": "", - "HelpFAQ_Cat_Detail": "", - "HelpFAQ_Cat_Detail_300_head": "", - "HelpFAQ_Cat_Detail_300_text_a": "", - "HelpFAQ_Cat_Detail_300_text_b": "", - "HelpFAQ_Cat_Detail_301_head_a": "", + "Events_TableHead_PendingAlert": "Alerta Pendente", + "Events_Table_info": "Mostrando_START_ to _END_ of _TOTAL_ entradas", + "Events_Table_nav_next": "Próxima", + "Events_Table_nav_prev": "Anterior", + "Events_Tablelenght": "Mostrar entradas do _MENU_", + "Events_Tablelenght_all": "Todos", + "Events_Title": "Eventos", + "Gen_Action": "Ação", + "Gen_Add": "Adicionar", + "Gen_Add_All": "Adicionar todos", + "Gen_All_Devices": "Todos os Dispositivos", + "Gen_AreYouSure": "Tem certeza?", + "Gen_Backup": "Executar backup", + "Gen_Cancel": "Cancelar", + "Gen_Copy": "Executar", + "Gen_DataUpdatedUITakesTime": "OK - Pode levar um tempo para a interface do usuário ser atualizada se uma verificação estiver em execução.", + "Gen_Delete": "Excluir", + "Gen_DeleteAll": "Excluir todos", + "Gen_Error": "Erro", + "Gen_Filter": "Filtro", + "Gen_LockedDB": "ERRO - O banco de dados pode estar bloqueado - Verifique F12 Ferramentas de desenvolvimento -> Console ou tente mais tarde.", + "Gen_Offline": "Offline", + "Gen_Okay": "Ok", + "Gen_Purge": "Purge", + "Gen_ReadDocs": "Leia mais em documentos.", + "Gen_Remove_All": "Remover tudo", + "Gen_Remove_Last": "Remover o último", + "Gen_Restore": "Executar restauração", + "Gen_Run": "Executar", + "Gen_Save": "Salvar", + "Gen_Saved": "Salvo", + "Gen_Search": "Procurar", + "Gen_Selected_Devices": "Dispositivos selecionados:", + "Gen_Switch": "Trocar", + "Gen_Upd": "Atualizado com sucesso", + "Gen_Upd_Fail": "A atualização falhou", + "Gen_Update": "Atualizar", + "Gen_Update_Value": "Atualizar valor", + "Gen_Warning": "Aviso", + "Gen_Work_In_Progress": "Trabalho em andamento, um bom momento para enviar feedback em https://github.com/jokob-sk/NetAlertX/issues", + "General_display_name": "Geral", + "General_icon": "", + "HRS_TO_KEEP_NEWDEV_description": "Esta é uma configuração de manutenção. Se habilitada (0 is disabled), dispositivos marcados como Novo Dispositivo serão excluídos se o tempo de Primeira Sessão for mais antigo que as horas especificadas nesta configuração. Use esta configuração se quiser excluir automaticamente Novos Dispositivos após X horas.", + "HRS_TO_KEEP_NEWDEV_name": "Manter novos dispositivos por", + "HelpFAQ_Cat_Detail": "Detalhes", + "HelpFAQ_Cat_Detail_300_head": "O que significa ", + "HelpFAQ_Cat_Detail_300_text_a": "Significa um dispositivo de rede (um dispositivo do tipo AP, Gateway, Firewall, Hypervisor, Powerline, Switch, WLAN, PLC, Roteador, Adaptador LAN USB, Adaptador Wi-Fi USB ou Internet). Tipos personalizados podem ser adicionados através da configuração NETWORK_DEVICE_TYPES.", + "HelpFAQ_Cat_Detail_300_text_b": "Indica o número da porta onde o dispositivo atualmente em edição está conectado a este dispositivo de rede. Leia este guia para mais informações.", + "HelpFAQ_Cat_Detail_301_head_a": "Quando está escaneando agora? ", "HelpFAQ_Cat_Detail_301_head_b": "", - "HelpFAQ_Cat_Detail_301_text": "", + "HelpFAQ_Cat_Detail_301_text": "O intervalo de tempo entre as verificações é definido pelo 'Cronjob', que está configurado para 5min por padrão. A designação '1min' refere-se à duração esperada da verificação. Dependendo da configuração da rede, esse tempo pode variar. Para editar o cronjob, você pode usar o seguinte comando no terminal/console crontab -e e alterar o intervalo.", "HelpFAQ_Cat_Detail_302_head_a": "", - "HelpFAQ_Cat_Detail_302_head_b": "", - "HelpFAQ_Cat_Detail_302_text": "", - "HelpFAQ_Cat_Detail_303_head": "", - "HelpFAQ_Cat_Detail_303_text": "", - "HelpFAQ_Cat_Device_200_head": "", - "HelpFAQ_Cat_Device_200_text": "", - "HelpFAQ_Cat_General": "", - "HelpFAQ_Cat_General_100_head": "", - "HelpFAQ_Cat_General_100_text_a": "", - "HelpFAQ_Cat_General_100_text_b": "", - "HelpFAQ_Cat_General_100_text_c": "", - "HelpFAQ_Cat_General_101_head": "", + "HelpFAQ_Cat_Detail_302_head_b": "E por que não posso selecionar isso?", + "HelpFAQ_Cat_Detail_302_text": "Alguns dispositivos modernos geram endereços MAC aleatórios por razões de privacidade, que não podem mais ser associados a nenhum fabricante e que mudam a cada nova conexão. O NetAlertX detecta se é um endereço MAC aleatório e ativa este 'campo' automaticamente. Para desativar esse comportamento, você deve verificar no seu dispositivo como desativar a randomização do endereço MAC.", + "HelpFAQ_Cat_Detail_303_head": "O que é o Nmap e para que serve?", + "HelpFAQ_Cat_Detail_303_text": "Nmap é um scanner de rede com múltiplas capacidades.
Quando um novo dispositivo aparece na sua lista, você tem a possibilidade de obter informações mais detalhadas sobre o dispositivo através da varredura do Nmap.", + "HelpFAQ_Cat_Device_200_head": "Eu tenho dispositivos na minha lista dos quais não sei nada. Após excluí-los, eles sempre reaparecem.", + "HelpFAQ_Cat_Device_200_text": "Se você usar o Pi-hole, observe que o NetAlertX recupera informações do Pi-hole. Pause o NetAlertX, vá até a página de configurações no Pi-hole e exclua o lease DHCP, se necessário. Em seguida, também no Pi-hole, vá em Ferramentas -> Rede para ver se consegue encontrar os hosts recorrentes lá. Se encontrar, exclua-os também. Agora você pode iniciar o NetAlertX novamente. Os dispositivos não devem mais aparecer.", + "HelpFAQ_Cat_General": "Geral", + "HelpFAQ_Cat_General_100_head": "O relógio no canto superior direito e os horários dos eventos/presença não estão corretos (diferença de horário).", + "HelpFAQ_Cat_General_100_text_a": "No seu PC, o seguinte fuso horário está configurado para o ambiente PHP:", + "HelpFAQ_Cat_General_100_text_b": "Se este não for o fuso horário em que você está, você deve alterar o fuso horário no arquivo de configuração do PHP. Você pode encontrá-lo neste diretório:", + "HelpFAQ_Cat_General_100_text_c": "Pesquise neste arquivo pela entrada \"date.timezone\", remova o ponto e vírgula inicial \";\" se necessário, e insira o fuso horário desejado. Uma lista com os fusos horários suportados pode ser encontrada aqui (Link)", + "HelpFAQ_Cat_General_101_head": "Minha rede parece estar lenta, o streaming fica \"congelado\".", "HelpFAQ_Cat_General_101_text": "", "HelpFAQ_Cat_General_102_head": "", "HelpFAQ_Cat_General_102_text": "", From 4ef7f507ed620d5156b20915e290247864a706cb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 10 Aug 2024 11:53:49 +0000 Subject: [PATCH 10/13] =?UTF-8?q?[=F0=9F=A4=96Automation]=20Update=20READM?= =?UTF-8?q?E=20with=20sponsors=20information?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 6438165b14e866d79324b90a1d0a72ce45a6cd37 Mon Sep 17 00:00:00 2001 From: Ettore Atalan Date: Sat, 10 Aug 2024 19:14:22 +0000 Subject: [PATCH 11/13] Translated using Weblate (German) Currently translated at 94.6% (654 of 691 strings) Translation: NetAlertX/core Translate-URL: https://hosted.weblate.org/projects/pialert/core/de/ --- front/php/templates/language/de_de.json | 82 ++++++++++++------------- 1 file changed, 41 insertions(+), 41 deletions(-) mode change 100755 => 100644 front/php/templates/language/de_de.json diff --git a/front/php/templates/language/de_de.json b/front/php/templates/language/de_de.json old mode 100755 new mode 100644 index 29a0ae7d..af3f34d4 --- a/front/php/templates/language/de_de.json +++ b/front/php/templates/language/de_de.json @@ -28,7 +28,7 @@ "AppEvents_ObjectPrimaryID": "Primär ID", "AppEvents_ObjectSecondaryID": "Sekundär ID", "AppEvents_ObjectStatus": "", - "AppEvents_ObjectStatusColumn": "", + "AppEvents_ObjectStatusColumn": "Statusspalte", "AppEvents_ObjectType": "Objekttyp", "AppEvents_Plugin": "Plugin", "AppEvents_Type": "Typ", @@ -98,7 +98,7 @@ "DevDetail_MainInfo_Network_Site": "", "DevDetail_MainInfo_Network_Title": " Network", "DevDetail_MainInfo_Owner": "Eigen­tümer", - "DevDetail_MainInfo_SSID": "", + "DevDetail_MainInfo_SSID": "SSID", "DevDetail_MainInfo_Title": " Hauptinformation", "DevDetail_MainInfo_Type": "Typ", "DevDetail_MainInfo_Vendor": "Hersteller", @@ -126,7 +126,7 @@ "DevDetail_Run_Actions_Tooltip": "Eine Aktion aus der Dropdown-Liste auf dem aktuellen Gerät ausführen.", "DevDetail_SessionInfo_FirstSession": "Erste Sitzung", "DevDetail_SessionInfo_LastIP": "Letzte IP", - "DevDetail_SessionInfo_LastSession": "Letzte Sitzung", + "DevDetail_SessionInfo_LastSession": "Zuletzt offline", "DevDetail_SessionInfo_StaticIP": "Statische IP", "DevDetail_SessionInfo_Status": "Status", "DevDetail_SessionInfo_Title": " Sitzungsinformation", @@ -202,7 +202,7 @@ "Device_Shortcut_Archived": "Archiviert", "Device_Shortcut_Connected": "Verbunden", "Device_Shortcut_Devices": "Geräte", - "Device_Shortcut_DownAlerts": "Offline & Nicht erreichbar", + "Device_Shortcut_DownAlerts": "Nicht erreichbar & offline", "Device_Shortcut_DownOnly": "Offline", "Device_Shortcut_Favorites": "Favoriten", "Device_Shortcut_NewDevices": "Neue Geräte", @@ -210,12 +210,12 @@ "Device_TableHead_Connected_Devices": "Verbundene Geräte", "Device_TableHead_Favorite": "Favorit", "Device_TableHead_FirstSession": "Erste Sitzung", - "Device_TableHead_GUID": "", + "Device_TableHead_GUID": "GUID", "Device_TableHead_Group": "Gruppe", "Device_TableHead_Icon": "Icon", "Device_TableHead_LastIP": "Letzte IP", "Device_TableHead_LastIPOrder": "Last IP Order", - "Device_TableHead_LastSession": "Letzte Sitzung", + "Device_TableHead_LastSession": "Zuletzt offline", "Device_TableHead_Location": "Standort", "Device_TableHead_MAC": "MAC", "Device_TableHead_MAC_full": "Gesamte MAC", @@ -226,9 +226,9 @@ "Device_TableHead_Port": "Port", "Device_TableHead_RowID": "Zeilen ID", "Device_TableHead_Rowid": "Zeilennummer", - "Device_TableHead_SSID": "", + "Device_TableHead_SSID": "SSID", "Device_TableHead_Status": "Status", - "Device_TableHead_SyncHubNodeName": "", + "Device_TableHead_SyncHubNodeName": "Synchronisationsknoten", "Device_TableHead_Type": "Typ", "Device_TableHead_Vendor": "Hersteller", "Device_Table_Not_Network_Device": "Nicht konfiguriert als Netzwerkgerät", @@ -272,7 +272,7 @@ "Events_TableHead_IPOrder": "IP Order", "Events_TableHead_Order": "Order", "Events_TableHead_Owner": "Eigentümer", - "Events_TableHead_PendingAlert": "", + "Events_TableHead_PendingAlert": "Ausstehender Alarm", "Events_Table_info": "Zeige _START_ bis _END_ von _TOTAL_ Einträgen", "Events_Table_nav_next": "Nächste", "Events_Table_nav_prev": "Zurück", @@ -280,9 +280,9 @@ "Events_Tablelenght_all": "Alle", "Events_Title": "Ereignisse", "Gen_Action": "Action", - "Gen_Add": "", - "Gen_Add_All": "", - "Gen_All_Devices": "", + "Gen_Add": "Hinzufügen", + "Gen_Add_All": "Alle hinzufügen", + "Gen_All_Devices": "Alle Geräte", "Gen_AreYouSure": "Sind Sie sich sicher?", "Gen_Backup": "Sichern", "Gen_Cancel": "Abbrechen", @@ -291,25 +291,25 @@ "Gen_Delete": "Löschen", "Gen_DeleteAll": "Delete all", "Gen_Error": "Fehler", - "Gen_Filter": "", + "Gen_Filter": "Filter", "Gen_LockedDB": "ERROR - DB eventuell gesperrt - Nutze die Konsole in den Entwickler Werkzeugen (F12) zur Überprüfung oder probiere es später erneut.", - "Gen_Offline": "", + "Gen_Offline": "Offline", "Gen_Okay": "Ok", "Gen_Purge": "Aufräumen", "Gen_ReadDocs": "Mehr in der Dokumentation", - "Gen_Remove_All": "", - "Gen_Remove_Last": "", + "Gen_Remove_All": "Alle entfernen", + "Gen_Remove_Last": "Letzte entfernen", "Gen_Restore": "Wiederherstellen", "Gen_Run": "Run", "Gen_Save": "Speichern", "Gen_Saved": "Gespeichert", - "Gen_Search": "", + "Gen_Search": "Suchen", "Gen_Selected_Devices": "Ausgewählte Geräte:", "Gen_Switch": "Umschalten", "Gen_Upd": "Aktualisierung erfolgreich", "Gen_Upd_Fail": "Aktualisierung fehlgeschlagen", - "Gen_Update": "", - "Gen_Update_Value": "", + "Gen_Update": "Aktualisieren", + "Gen_Update_Value": "Wert aktualisieren", "Gen_Warning": "Warnung", "Gen_Work_In_Progress": "Keine Finalversion, feedback bitte unter: https://github.com/jokob-sk/NetAlertX/issues", "General_display_name": "Allgemein", @@ -353,7 +353,7 @@ "HelpFAQ_Cat_Presence_401_text": "Wenn dies geschieht hast du die Möglickeit, bei dem betreffenden Gerät (Detailsansicht) die Events zu löschen. Eine andere Möglichkeit wäre, das Gerät einzuschalten und zu warten, bis NetAlertX mit dem nächsten Scan das Gerät als \"Online\" erkennt und anschließend das Gerät einfach wieder ausschalten. Nun sollte NetAlertX mit dem nächsten Scan den Zustand des Gerätes ordentlich in der Datenbank vermerken.", "HelpFAQ_Title": "Hilfe / FAQ", "LOADED_PLUGINS_description": "", - "LOADED_PLUGINS_name": "", + "LOADED_PLUGINS_name": "Geladene Plugins", "LOG_LEVEL_description": "Diese Einstellung aktiviert die erweiterte Protokollierung. Nützlich fürs Debuggen von in die Datenbank geschriebenen Events.", "LOG_LEVEL_name": "Erweiterte Protokollierung", "Loading": "Laden...", @@ -384,8 +384,8 @@ "MQTT_USER_name": "MQTT user", "MQTT_display_name": "MQTT", "MQTT_icon": "", - "Maint_PurgeLog": "", - "Maint_RestartServer": "", + "Maint_PurgeLog": "Protokoll bereinigen", + "Maint_RestartServer": "Server neu starten", "Maint_Restart_Server_noti_text": "", "Maintenance_Running_Version": "Installierte Version", "Maintenance_Status": "Status", @@ -398,7 +398,7 @@ "Maintenance_Tool_ImportCSV_noti": "CSV Import", "Maintenance_Tool_ImportCSV_noti_text": "Sind Sie sich sicher, dass Sie die CSV-Datei importieren wollen? Dies wird alle Geräte in der Datenbank überschreiben.", "Maintenance_Tool_ImportCSV_text": "Machen Sie ein Backup, bevor Sie diese Funk­tion nutzen. Importiere eine CSV-Datei (comma separated values) mit einer Liste aller Geräte und deren Beziehungen zwischen Netzwerkknoten und verbundenen Geräten. Um dies zu tun platziere die devices.csv benannte CSV-Datei in deinen /config Ordner.", - "Maintenance_Tool_ImportPastedCSV": "", + "Maintenance_Tool_ImportPastedCSV": "CSV-Import (Einfügen)", "Maintenance_Tool_ImportPastedCSV_noti_text": "", "Maintenance_Tool_ImportPastedCSV_text": "", "Maintenance_Tool_arpscansw": "arp-Scan stoppen/starten", @@ -493,7 +493,7 @@ "NTFY_USER_name": "NTFY user", "NTFY_display_name": "NTFY", "NTFY_icon": "", - "Navigation_About": "", + "Navigation_About": "Über", "Navigation_Devices": "Geräte", "Navigation_Donations": "Donations", "Navigation_Events": "Ereignisse", @@ -503,10 +503,10 @@ "Navigation_Maintenance": "Wartung", "Navigation_Monitoring": "Überwachung", "Navigation_Network": "Netzwerk", - "Navigation_Notifications": "", + "Navigation_Notifications": "Benachrichtigungen", "Navigation_Plugins": "Plugins", "Navigation_Presence": "Anwesenheit", - "Navigation_Report": "Bericht", + "Navigation_Report": "Gesendete Berichte", "Navigation_Settings": "Einstellungen", "Navigation_SystemInfo": "Systeminfo", "Navigation_Workflows": "Arbeitsabläufe", @@ -545,7 +545,7 @@ "Network_Node": "Netzwerkknoten", "Network_Node_Name": "Knotenname", "Network_Parent": "Übergeordnetes Netzwerkgerät", - "Network_Root": "", + "Network_Root": "Wurzelknoten", "Network_Root_Not_Configured": "", "Network_Root_Unconfigurable": "Nicht konfigurierbare Wurzel", "Network_Table_Hostname": "Gerätename", @@ -553,8 +553,8 @@ "Network_Table_State": "Status", "Network_Title": "Netzwerkübersicht", "Network_UnassignedDevices": "Nicht zugewiesene Geräte", - "Notifications_All": "", - "Notifications_Mark_All_Read": "", + "Notifications_All": "Alle Benachrichtigungen", + "Notifications_Mark_All_Read": "Alle als gelesen markieren", "PIALERT_WEB_PASSWORD_description": "Das Standardpasswort ist 123456. Um das Passwort zu ändern, entweder /app/back/pialert-cli im Container starten oder SETPWD_RUN Set password plugin nutzen.", "PIALERT_WEB_PASSWORD_name": "Login-Passwort", "PIALERT_WEB_PROTECTION_description": "Ein Loginfenster wird angezeigt wenn aktiviert. Untere Beschreibung genau durchlesen falls Sie sich aus Ihrer Instanz aussperren.", @@ -609,8 +609,8 @@ "REPORT_WEBHOOK_description": "Enable webhooks for notifications. Webhooks help you to connect to a lot of 3rd party tools, such as IFTTT, Zapier or n8n to name a few. Check out this simple n8n guide here to get started. If enabled, configure related settings below.", "REPORT_WEBHOOK_name": "Enable Webhooks", "RandomMAC_hover": "Autodetected - indicates if the device randomizes it's MAC address.", - "Reports_Sent_Log": "", - "SCAN_SUBNETS_description": "Arp-scan is a command-line tool that uses the ARP protocol to discover and fingerprint IP hosts on the local network. An alternative to ARP scan is to enable the PIHOLE_RUNPiHole integration settings. The arp-scan time itself depends on the number of IP addresses to check so set this up carefully with the appropriate network mask and interface. Check the subnets documentation for help on setting up VLANs, what VLANs are supported, or how to figure out the network mask and your interface.", + "Reports_Sent_Log": "Protokoll gesendeter Berichte", + "SCAN_SUBNETS_description": "", "SMTP_FORCE_SSL_description": "Force SSL when connecting to your SMTP server.", "SMTP_FORCE_SSL_name": "Force SSL", "SMTP_PASS_description": "The SMTP server password. ", @@ -629,7 +629,7 @@ "Setting_Override": "Override value", "Setting_Override_Description": "Enabling this option will override an App supplied default value with the value specified above.", "Settings_Metadata_Toggle": "Show/hide metadata for the given setting.", - "Settings_device_Scanners_desync": "", + "Settings_device_Scanners_desync": "⚠ Die Zeitpläne des Gerätescanners sind nicht synchronisiert.", "Settings_device_Scanners_desync_popup": "", "Speedtest_Results": "Ergebnisse des Geschwindigkeitstests", "Systeminfo_CPU": "CPU", @@ -744,29 +744,29 @@ "report_time": "Benachrichtigungszeit:", "run_event_icon": "fa-play", "run_event_tooltip": "Enable the setting and save your changes at first before you run it.", - "settings_core_icon": "fa-solid fa-gem", - "settings_core_label": "", + "settings_core_icon": "", + "settings_core_label": "Kern", "settings_device_scanners": "", - "settings_device_scanners_icon": "fa-solid fa-magnifying-glass-plus", + "settings_device_scanners_icon": "", "settings_device_scanners_info": "", "settings_device_scanners_label": "Gerätescanner", "settings_enabled": "Aktive Einstellungen", - "settings_enabled_icon": "fa-solid fa-toggle-on", + "settings_enabled_icon": "", "settings_expand_all": "Expand all", "settings_imported": "Last time settings were imported from the app.conf file:", "settings_imported_label": "Einstellungen importiert", - "settings_missing": "Not all settings loaded, refresh the page! This is probably caused by a high load on the database or app startup sequence.", - "settings_missing_block": "You can not save your settings without specifying all setting keys. Refresh the page. This is probably caused by a high load on the database.", + "settings_missing": "", + "settings_missing_block": "", "settings_old": "Importing settings and re-initializing...", "settings_other_scanners": "", - "settings_other_scanners_icon": "fa-solid fa-recycle", + "settings_other_scanners_icon": "", "settings_other_scanners_label": "Andere Scanner", "settings_publishers": "", "settings_publishers_icon": "fa-solid fa-paper-plane", "settings_publishers_info": "", "settings_publishers_label": "Veröffentlicher", - "settings_saved": "
Settings saved to the app.conf file.

A time-stamped backup of the previous file created.

Reloading...
", - "settings_system_icon": "fa-solid fa-gear", + "settings_saved": "", + "settings_system_icon": "", "settings_system_label": "System", "settings_update_item_warning": "", "test_event_icon": "fa-vial-circle-check", From 5c0e9a8af8fca7218304220c6063f413753eb14a Mon Sep 17 00:00:00 2001 From: Ramon Martinez <1026ramon@gmail.com> Date: Sat, 10 Aug 2024 11:49:30 +0000 Subject: [PATCH 12/13] Translated using Weblate (Portuguese (Brazil)) Currently translated at 35.4% (245 of 691 strings) Translation: NetAlertX/core Translate-URL: https://hosted.weblate.org/projects/pialert/core/pt_BR/ --- front/php/templates/language/pt_br.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/front/php/templates/language/pt_br.json b/front/php/templates/language/pt_br.json index 8b6f1b85..0f910b5f 100644 --- a/front/php/templates/language/pt_br.json +++ b/front/php/templates/language/pt_br.json @@ -145,7 +145,7 @@ "DevDetail_Tab_NmapTableText": "Configure uma programação em Configurações", "DevDetail_Tab_NmapTableTime": "Tempo", "DevDetail_Tab_Plugins": " Plugins", - "DevDetail_Tab_Presence": " Presença", + "DevDetail_Tab_Presence": " Presença", "DevDetail_Tab_Sessions": " Sessões", "DevDetail_Tab_Tools": " Ferramentas", "DevDetail_Tab_Tools_Internet_Info_Description": "A ferramenta info Internet exibe informações sobre a conexão com a Internet, como endereço IP, cidade, país, código de área e fuso horário.", @@ -233,7 +233,7 @@ "ENABLE_PLUGINS_description": "Ativa a funcionalidade de plugins. Carregar plug-ins requer mais recursos de hardware, então você pode querer desativá-los em sistemas de baixa potência.", "ENABLE_PLUGINS_name": "Habilitar plug-ins", "Email_display_name": "Email", - "Email_icon": "", + "Email_icon": "", "Events_Loading": "Carregando...", "Events_Periodselect_All": "Todas as informações", "Events_Periodselect_LastMonth": "Mês passado", From 8199e5e714dd941851c939f37aa04e2f34fd10ed Mon Sep 17 00:00:00 2001 From: Ptsa Daniel Date: Sat, 10 Aug 2024 14:19:14 +0000 Subject: [PATCH 13/13] Translated using Weblate (Chinese (Simplified)) Currently translated at 8.2% (57 of 691 strings) Translation: NetAlertX/core Translate-URL: https://hosted.weblate.org/projects/pialert/core/zh_Hans/ --- front/php/templates/language/zh_cn.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) mode change 100755 => 100644 front/php/templates/language/zh_cn.json diff --git a/front/php/templates/language/zh_cn.json b/front/php/templates/language/zh_cn.json old mode 100755 new mode 100644 index e5ca909b..1c435dd3 --- a/front/php/templates/language/zh_cn.json +++ b/front/php/templates/language/zh_cn.json @@ -1,7 +1,7 @@ { "API_CUSTOM_SQL_description": "", - "API_CUSTOM_SQL_name": "", - "API_display_name": "", + "API_CUSTOM_SQL_name": "自定义终点", + "API_display_name": "API", "API_icon": "", "About_Design": "", "About_Exit": "", @@ -690,4 +690,4 @@ "settings_update_item_warning": "", "test_event_icon": "", "test_event_tooltip": "" -} \ No newline at end of file +}