diff --git a/.gitignore b/.gitignore
index 8535c235..b2804d3f 100755
--- a/.gitignore
+++ b/.gitignore
@@ -20,6 +20,7 @@ __pycache__/
**/script.log
**/pialert.conf_bak
**/pialert.db_bak
+.*.swp
front/img/account/*
**/account.php
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()