From 7221b4ba96b90c96a7817e9c76c4f7421b4231cc Mon Sep 17 00:00:00 2001 From: "Jokob @NetAlertX" <96159884+jokob-sk@users.noreply.github.com> Date: Sun, 15 Mar 2026 01:19:34 +0000 Subject: [PATCH 001/189] Keep all local changes while resolving conflicts --- docs/NOTIFICATION_TEMPLATES.md | 100 ++++++ .../notification_processing/config.json | 147 +++++++++ server/messaging/notification_sections.py | 133 ++++++++ server/messaging/reporting.py | 51 +-- server/models/notification_instance.py | 56 +--- test/backend/test_notification_templates.py | 299 ++++++++++++++++++ test/integration/integration_test.py | 14 +- 7 files changed, 723 insertions(+), 77 deletions(-) create mode 100644 docs/NOTIFICATION_TEMPLATES.md create mode 100644 server/messaging/notification_sections.py create mode 100644 test/backend/test_notification_templates.py diff --git a/docs/NOTIFICATION_TEMPLATES.md b/docs/NOTIFICATION_TEMPLATES.md new file mode 100644 index 00000000..2956bd32 --- /dev/null +++ b/docs/NOTIFICATION_TEMPLATES.md @@ -0,0 +1,100 @@ +# Notification Text Templates + +> Customize how devices and events appear in **text** notifications (email previews, push notifications, Apprise messages). + +By default, NetAlertX formats each device as a vertical list of `Header: Value` pairs. Text templates let you define a **single-line format per device** using `{FieldName}` placeholders — ideal for mobile notification previews and high-volume alerts. + +HTML email tables are **not affected** by these templates. + +## Quick Start + +1. Go to **Settings → Notification Processing**. +2. Set a template string for the section you want to customize, e.g.: + - **Text Template: New Devices** → `{devName} ({eve_MAC}) - {eve_IP}` +3. Save. The next notification will use your format. + +**Before (default):** +``` +🆕 New devices +--------- +devName: MyPhone +eve_MAC: aa:bb:cc:dd:ee:ff +devVendor: Apple +eve_IP: 192.168.1.42 +eve_DateTime: 2025-01-15 10:30:00 +eve_EventType: New Device +devComments: +``` + +**After (with template `{devName} ({eve_MAC}) - {eve_IP}`):** +``` +🆕 New devices +--------- +MyPhone (aa:bb:cc:dd:ee:ff) - 192.168.1.42 +``` + +## Settings Reference + +| Setting | Type | Default | Description | +|---------|------|---------|-------------| +| `NTFPRCS_TEXT_SECTION_HEADERS` | Boolean | `true` | Show/hide section titles (e.g. `🆕 New devices \n---------`). | +| `NTFPRCS_TEXT_TEMPLATE_new_devices` | String | *(empty)* | Template for new device rows. | +| `NTFPRCS_TEXT_TEMPLATE_down_devices` | String | *(empty)* | Template for down device rows. | +| `NTFPRCS_TEXT_TEMPLATE_down_reconnected` | String | *(empty)* | Template for reconnected device rows. | +| `NTFPRCS_TEXT_TEMPLATE_events` | String | *(empty)* | Template for event rows. | +| `NTFPRCS_TEXT_TEMPLATE_plugins` | String | *(empty)* | Template for plugin event rows. | + +When a template is **empty**, the section uses the original vertical `Header: Value` format (full backward compatibility). + +## Template Syntax + +Use `{FieldName}` to insert a value from the notification data. Field names are **case-sensitive** and must match the column names exactly. + +``` +{devName} ({eve_MAC}) connected at {eve_DateTime} +``` + +- No loops, conditionals, or nesting — just simple string replacement. +- If a `{FieldName}` does not exist in the data, it is left as-is in the output (safe failure). For example, `{NonExistent}` renders literally as `{NonExistent}`. + +## Variable Availability by Section + +All four device sections (`new_devices`, `down_devices`, `down_reconnected`, `events`) share the same unified field names. + +### `new_devices`, `down_devices`, `down_reconnected`, and `events` + +| Variable | Description | +|----------|-------------| +| `{devName}` | Device display name | +| `{eve_MAC}` | Device MAC address | +| `{devVendor}` | Device vendor/manufacturer | +| `{eve_IP}` | Device IP address | +| `{eve_DateTime}` | Event timestamp | +| `{eve_EventType}` | Type of event (e.g. `New Device`, `Connected`, `Device Down`) | +| `{devComments}` | Device comments | + +**Example (new_devices/events):** `{devName} ({eve_MAC}) - {eve_IP} [{eve_EventType}]` + +**Example (down_devices):** `{devName} ({eve_MAC}) {devVendor} - went down at {eve_DateTime}` + +**Example (down_reconnected):** `{devName} ({eve_MAC}) reconnected at {eve_DateTime}` + +### `plugins` + +| Variable | Description | +|----------|-------------| +| `{Plugin}` | Plugin code name | +| `{Object_PrimaryId}` | Primary identifier of the object | +| `{Object_SecondaryId}` | Secondary identifier | +| `{DateTimeChanged}` | Timestamp of change | +| `{Watched_Value1}` | First watched value | +| `{Watched_Value2}` | Second watched value | +| `{Watched_Value3}` | Third watched value | +| `{Watched_Value4}` | Fourth watched value | +| `{Status}` | Plugin event status | + +**Example:** `{Plugin}: {Object_PrimaryId} - {Status}` + +## Section Headers Toggle + +Set **Text Section Headers** (`NTFPRCS_TEXT_SECTION_HEADERS`) to `false` to remove the section title separators from text notifications. This is useful when you want compact output without the `🆕 New devices \n---------` banners. diff --git a/front/plugins/notification_processing/config.json b/front/plugins/notification_processing/config.json index acb4fbbf..bf2c9e2a 100755 --- a/front/plugins/notification_processing/config.json +++ b/front/plugins/notification_processing/config.json @@ -152,6 +152,153 @@ "string": "You can specify a SQL where condition to filter out Events from notifications. For example AND devLastIP NOT LIKE '192.168.3.%' will always exclude any Event notifications for all devices with the IP starting with 192.168.3.%." } ] +<<<<<<< Updated upstream +======= + }, + { + "function": "TEXT_SECTION_HEADERS", + "type": { + "dataType": "boolean", + "elements": [ + { "elementType": "input", "elementOptions": [{ "type": "checkbox" }], "transformers": [] } + ] + }, + "default_value": true, + "options": [], + "localized": ["name", "description"], + "name": [ + { + "language_code": "en_us", + "string": "Text Section Headers" + } + ], + "description": [ + { + "language_code": "en_us", + "string": "Enable or disable section titles (e.g. 🆕 New devices \\n---------) in text notifications. Enabled by default for backward compatibility." + } + ] + }, + { + "function": "TEXT_TEMPLATE_new_devices", + "type": { + "dataType": "string", + "elements": [ + { "elementType": "input", "elementOptions": [], "transformers": [] } + ] + }, + "default_value": "", + "options": [], + "localized": ["name", "description"], + "name": [ + { + "language_code": "en_us", + "string": "Text Template: New Devices" + } + ], + "description": [ + { + "language_code": "en_us", + "string": "Custom text template for new device notifications. Use {FieldName} placeholders, e.g. {devName} ({eve_MAC}) - {eve_IP}. Leave empty for default formatting. Available fields: {devName}, {eve_MAC}, {devVendor}, {eve_IP}, {eve_DateTime}, {eve_EventType}, {devComments}." + } + ] + }, + { + "function": "TEXT_TEMPLATE_down_devices", + "type": { + "dataType": "string", + "elements": [ + { "elementType": "input", "elementOptions": [], "transformers": [] } + ] + }, + "default_value": "", + "options": [], + "localized": ["name", "description"], + "name": [ + { + "language_code": "en_us", + "string": "Text Template: Down Devices" + } + ], + "description": [ + { + "language_code": "en_us", + "string": "Custom text template for down device notifications. Use {FieldName} placeholders, e.g. {devName} ({eve_MAC}) - {eve_IP}. Leave empty for default formatting. Available fields: {devName}, {eve_MAC}, {devVendor}, {eve_IP}, {eve_DateTime}, {eve_EventType}, {devComments}." + } + ] + }, + { + "function": "TEXT_TEMPLATE_down_reconnected", + "type": { + "dataType": "string", + "elements": [ + { "elementType": "input", "elementOptions": [], "transformers": [] } + ] + }, + "default_value": "", + "options": [], + "localized": ["name", "description"], + "name": [ + { + "language_code": "en_us", + "string": "Text Template: Reconnected" + } + ], + "description": [ + { + "language_code": "en_us", + "string": "Custom text template for reconnected device notifications. Use {FieldName} placeholders, e.g. {devName} ({eve_MAC}) reconnected at {eve_DateTime}. Leave empty for default formatting. Available fields: {devName}, {eve_MAC}, {devVendor}, {eve_IP}, {eve_DateTime}, {eve_EventType}, {devComments}." + } + ] + }, + { + "function": "TEXT_TEMPLATE_events", + "type": { + "dataType": "string", + "elements": [ + { "elementType": "input", "elementOptions": [], "transformers": [] } + ] + }, + "default_value": "", + "options": [], + "localized": ["name", "description"], + "name": [ + { + "language_code": "en_us", + "string": "Text Template: Events" + } + ], + "description": [ + { + "language_code": "en_us", + "string": "Custom text template for event notifications. Use {FieldName} placeholders, e.g. {devName} ({eve_MAC}) {eve_EventType} at {eve_DateTime}. Leave empty for default formatting. Available fields: {devName}, {eve_MAC}, {devVendor}, {eve_IP}, {eve_DateTime}, {eve_EventType}, {devComments}." + } + ] + }, + { + "function": "TEXT_TEMPLATE_plugins", + "type": { + "dataType": "string", + "elements": [ + { "elementType": "input", "elementOptions": [], "transformers": [] } + ] + }, + "default_value": "", + "options": [], + "localized": ["name", "description"], + "name": [ + { + "language_code": "en_us", + "string": "Text Template: Plugins" + } + ], + "description": [ + { + "language_code": "en_us", + "string": "Custom text template for plugin event notifications. Use {FieldName} placeholders, e.g. {Plugin}: {Object_PrimaryId} - {Status}. Leave empty for default formatting. Available fields: {Plugin}, {Object_PrimaryId}, {Object_SecondaryId}, {DateTimeChanged}, {Watched_Value1}, {Watched_Value2}, {Watched_Value3}, {Watched_Value4}, {Status}." + } + ] +>>>>>>> Stashed changes } ], diff --git a/server/messaging/notification_sections.py b/server/messaging/notification_sections.py new file mode 100644 index 00000000..a31fb204 --- /dev/null +++ b/server/messaging/notification_sections.py @@ -0,0 +1,133 @@ +# ------------------------------------------------------------------------------- +# notification_sections.py — Single source of truth for notification section +# metadata: titles, SQL templates, datetime fields, and section ordering. +# +# Both reporting.py and notification_instance.py import from here. +# ------------------------------------------------------------------------------- + +# Canonical processing order +SECTION_ORDER = [ + "new_devices", + "down_devices", + "down_reconnected", + "events", + "plugins", +] + +# Section display titles (used in text + HTML notifications) +SECTION_TITLES = { + "new_devices": "🆕 New devices", + "down_devices": "🔴 Down devices", + "down_reconnected": "🔁 Reconnected down devices", + "events": "⚡ Events", + "plugins": "🔌 Plugins", +} + +# Which column(s) contain datetime values per section (for timezone conversion) +DATETIME_FIELDS = { + "new_devices": ["eve_DateTime"], + "down_devices": ["eve_DateTime"], + "down_reconnected": ["eve_DateTime"], + "events": ["eve_DateTime"], + "plugins": ["DateTimeChanged"], +} + +# --------------------------------------------------------------------------- +# SQL templates +# +# All device sections use unified DB column names so the JSON output +# has consistent field names across new_devices, down_devices, +# down_reconnected, and events. +# +# Placeholders: +# {condition} — optional WHERE clause appended by condition builder +# {alert_down_minutes} — runtime value, only used by down_devices +# --------------------------------------------------------------------------- +SQL_TEMPLATES = { + "new_devices": """ + SELECT + devName, + eve_MAC, + devVendor, + devLastIP as eve_IP, + eve_DateTime, + eve_EventType, + devComments + FROM Events_Devices + WHERE eve_PendingAlertEmail = 1 + AND eve_EventType = 'New Device' {condition} + ORDER BY eve_DateTime + """, + "down_devices": """ + SELECT + devName, + eve_MAC, + devVendor, + eve_IP, + eve_DateTime, + eve_EventType, + devComments + FROM Events_Devices AS down_events + WHERE eve_PendingAlertEmail = 1 + AND down_events.eve_EventType = 'Device Down' + AND eve_DateTime < datetime('now', '-{alert_down_minutes} minutes') + AND NOT EXISTS ( + SELECT 1 + FROM Events AS connected_events + WHERE connected_events.eve_MAC = down_events.eve_MAC + AND connected_events.eve_EventType = 'Connected' + AND connected_events.eve_DateTime > down_events.eve_DateTime + ) + ORDER BY down_events.eve_DateTime + """, + "down_reconnected": """ + SELECT + devName, + eve_MAC, + devVendor, + eve_IP, + eve_DateTime, + eve_EventType, + devComments + FROM Events_Devices AS reconnected_devices + WHERE reconnected_devices.eve_EventType = 'Down Reconnected' + AND reconnected_devices.eve_PendingAlertEmail = 1 + ORDER BY reconnected_devices.eve_DateTime + """, + "events": """ + SELECT + devName, + eve_MAC, + devVendor, + devLastIP as eve_IP, + eve_DateTime, + eve_EventType, + devComments + FROM Events_Devices + WHERE eve_PendingAlertEmail = 1 + AND eve_EventType IN ('Connected', 'Down Reconnected', 'Disconnected','IP Changed') {condition} + ORDER BY eve_DateTime + """, + "plugins": """ + SELECT + Plugin, + Object_PrimaryId, + Object_SecondaryId, + DateTimeChanged, + Watched_Value1, + Watched_Value2, + Watched_Value3, + Watched_Value4, + Status + FROM Plugins_Events + """, +} + +# Sections that support user-defined condition filters +SECTIONS_WITH_CONDITIONS = {"new_devices", "events"} + +# Legacy setting key mapping for condition filters +SECTION_CONDITION_MAP = { + "new_devices": "NTFPRCS_new_dev_condition", + "events": "NTFPRCS_event_condition", +} diff --git a/server/messaging/reporting.py b/server/messaging/reporting.py index 21c0d19e..d222529d 100755 --- a/server/messaging/reporting.py +++ b/server/messaging/reporting.py @@ -25,20 +25,20 @@ from helper import ( # noqa: E402 [flake8 lint suppression] from logger import mylog # noqa: E402 [flake8 lint suppression] from db.sql_safe_builder import create_safe_condition_builder # noqa: E402 [flake8 lint suppression] from utils.datetime_utils import format_date_iso # noqa: E402 [flake8 lint suppression] +from messaging.notification_sections import ( # noqa: E402 [flake8 lint suppression] + SECTION_ORDER, + SECTION_TITLES, + DATETIME_FIELDS, + SQL_TEMPLATES, + SECTIONS_WITH_CONDITIONS, + SECTION_CONDITION_MAP, +) import conf # noqa: E402 [flake8 lint suppression] # =============================================================================== # Timezone conversion # =============================================================================== -DATETIME_FIELDS = { - "new_devices": ["Datetime"], - "down_devices": ["eve_DateTime"], - "down_reconnected": ["eve_DateTime"], - "events": ["Datetime"], - "plugins": ["DateTimeChanged"], -} - def get_datetime_fields_from_columns(column_names): return [ @@ -155,6 +155,7 @@ def get_notifications(db): return "" +<<<<<<< Updated upstream # ------------------------- # SQL templates # ------------------------- @@ -245,13 +246,17 @@ def get_notifications(db): # Sections that support dynamic conditions sections_with_conditions = {"new_devices", "events"} +======= + # SQL templates with placeholders for runtime values + # {condition} and {alert_down_minutes} are formatted at query time +>>>>>>> Stashed changes # Initialize final structure final_json = {} - for section in ["new_devices", "down_devices", "down_reconnected", "events", "plugins"]: + for section in SECTION_ORDER: final_json[section] = [] final_json[f"{section}_meta"] = { - "title": section_titles.get(section, section), + "title": SECTION_TITLES.get(section, section), "columnNames": [] } @@ -260,17 +265,8 @@ def get_notifications(db): # ------------------------- # Main loop # ------------------------- - condition_builder = create_safe_condition_builder() - - SECTION_CONDITION_MAP = { - "new_devices": "NTFPRCS_new_dev_condition", - "events": "NTFPRCS_event_condition", - } - - sections_with_conditions = set(SECTION_CONDITION_MAP.keys()) - for section in sections: - template = sql_templates.get(section) + template = SQL_TEMPLATES.get(section) if not template: mylog("verbose", ["[Notification] Unknown section: ", section]) @@ -280,7 +276,7 @@ def get_notifications(db): parameters = {} try: - if section in sections_with_conditions: + if section in SECTIONS_WITH_CONDITIONS: condition_key = SECTION_CONDITION_MAP.get(section) condition_setting = get_setting_value(condition_key) @@ -289,11 +285,18 @@ def get_notifications(db): condition_setting ) - sqlQuery = template.format(condition=safe_condition) + # Format template with runtime placeholders + format_vars = {"condition": safe_condition} + if section == "down_devices": + format_vars["alert_down_minutes"] = alert_down_minutes + sqlQuery = template.format(**format_vars) except Exception as e: mylog("verbose", [f"[Notification] Error building condition for {section}: ", e]) - sqlQuery = template.format(condition="") + fallback_vars = {"condition": ""} + if section == "down_devices": + fallback_vars["alert_down_minutes"] = alert_down_minutes + sqlQuery = template.format(**fallback_vars) parameters = {} mylog("debug", [f"[Notification] {section} SQL query: ", sqlQuery]) @@ -307,7 +310,7 @@ def get_notifications(db): final_json[section] = json_obj.json.get("data", []) final_json[f"{section}_meta"] = { - "title": section_titles.get(section, section), + "title": SECTION_TITLES.get(section, section), "columnNames": getattr(json_obj, "columnNames", []) } diff --git a/server/models/notification_instance.py b/server/models/notification_instance.py index e45f97d3..686685d2 100755 --- a/server/models/notification_instance.py +++ b/server/models/notification_instance.py @@ -16,6 +16,7 @@ from helper import ( getBuildTimeStampAndVersion, ) from messaging.in_app import write_notification +from messaging.notification_sections import SECTION_ORDER from utils.datetime_utils import timeNowUTC, timeNowTZ, get_timezone_offset @@ -60,12 +61,7 @@ class NotificationInstance: write_file(logPath + "/report_output.json", json.dumps(JSON)) # Check if nothing to report, end - if ( - JSON["new_devices"] == [] and JSON["down_devices"] == [] and JSON["events"] == [] and JSON["plugins"] == [] and JSON["down_reconnected"] == [] - ): - self.HasNotifications = False - else: - self.HasNotifications = True + self.HasNotifications = any(JSON.get(s, []) for s in SECTION_ORDER) self.GUID = str(uuid.uuid4()) self.DateTimeCreated = timeNowUTC() @@ -129,47 +125,13 @@ class NotificationInstance: mail_text = mail_text.replace("REPORT_DASHBOARD_URL", self.serverUrl) mail_html = mail_html.replace("REPORT_DASHBOARD_URL", self.serverUrl) - # Start generating the TEXT & HTML notification messages - # new_devices - # --- - html, text = construct_notifications(self.JSON, "new_devices") - - mail_text = mail_text.replace("NEW_DEVICES_TABLE", text + "\n") - mail_html = mail_html.replace("NEW_DEVICES_TABLE", html) - mylog("verbose", ["[Notification] New Devices sections done."]) - - # down_devices - # --- - html, text = construct_notifications(self.JSON, "down_devices") - - mail_text = mail_text.replace("DOWN_DEVICES_TABLE", text + "\n") - mail_html = mail_html.replace("DOWN_DEVICES_TABLE", html) - mylog("verbose", ["[Notification] Down Devices sections done."]) - - # down_reconnected - # --- - html, text = construct_notifications(self.JSON, "down_reconnected") - - mail_text = mail_text.replace("DOWN_RECONNECTED_TABLE", text + "\n") - mail_html = mail_html.replace("DOWN_RECONNECTED_TABLE", html) - mylog("verbose", ["[Notification] Reconnected Down Devices sections done."]) - - # events - # --- - html, text = construct_notifications(self.JSON, "events") - - mail_text = mail_text.replace("EVENTS_TABLE", text + "\n") - mail_html = mail_html.replace("EVENTS_TABLE", html) - mylog("verbose", ["[Notification] Events sections done."]) - - # plugins - # --- - html, text = construct_notifications(self.JSON, "plugins") - - mail_text = mail_text.replace("PLUGINS_TABLE", text + "\n") - mail_html = mail_html.replace("PLUGINS_TABLE", html) - - mylog("verbose", ["[Notification] Plugins sections done."]) + # Generate TEXT & HTML for each notification section + for section in SECTION_ORDER: + html, text = construct_notifications(self.JSON, section) + placeholder = f"{section.upper()}_TABLE" + mail_text = mail_text.replace(placeholder, text + "\n") + mail_html = mail_html.replace(placeholder, html) + mylog("verbose", [f"[Notification] {section} section done."]) final_text = removeDuplicateNewLines(mail_text) diff --git a/test/backend/test_notification_templates.py b/test/backend/test_notification_templates.py new file mode 100644 index 00000000..0653493d --- /dev/null +++ b/test/backend/test_notification_templates.py @@ -0,0 +1,299 @@ +""" +NetAlertX Notification Text Template Tests + +Tests the template substitution and section header toggle in +construct_notifications(). All tests mock get_setting_value to avoid +database/config dependencies. + +License: GNU GPLv3 +""" + +import sys +import os +import unittest +from unittest.mock import patch + +# Add the server directory to the path for imports +INSTALL_PATH = os.getenv("NETALERTX_APP", "/app") +sys.path.extend([f"{INSTALL_PATH}/server"]) + + +def _make_json(section, devices, column_names, title="Test Section"): + """Helper to build the JSON structure expected by construct_notifications.""" + return { + section: devices, + f"{section}_meta": { + "title": title, + "columnNames": column_names, + }, + } + + +SAMPLE_NEW_DEVICES = [ + { + "devName": "MyPhone", + "eve_MAC": "aa:bb:cc:dd:ee:ff", + "devVendor": "", + "eve_IP": "192.168.1.42", + "eve_DateTime": "2025-01-15 10:30:00", + "eve_EventType": "New Device", + "devComments": "", + }, + { + "devName": "Laptop", + "eve_MAC": "11:22:33:44:55:66", + "devVendor": "Dell", + "eve_IP": "192.168.1.99", + "eve_DateTime": "2025-01-15 11:00:00", + "eve_EventType": "New Device", + "devComments": "Office", + }, +] + +NEW_DEVICE_COLUMNS = ["devName", "eve_MAC", "devVendor", "eve_IP", "eve_DateTime", "eve_EventType", "devComments"] + + +class TestConstructNotificationsTemplates(unittest.TestCase): + """Tests for template substitution in construct_notifications.""" + + def _setting_factory(self, overrides=None): + """Return a mock get_setting_value that resolves from overrides dict.""" + settings = overrides or {} + + def mock_get(key): + return settings.get(key, "") + + return mock_get + + # ----------------------------------------------------------------- + # Empty section should always return ("", "") regardless of settings + # ----------------------------------------------------------------- + @patch("models.notification_instance.get_setting_value") + def test_empty_section_returns_empty(self, mock_setting): + from models.notification_instance import construct_notifications + + mock_setting.return_value = "" + json_data = _make_json("new_devices", [], []) + html, text = construct_notifications(json_data, "new_devices") + self.assertEqual(html, "") + self.assertEqual(text, "") + + # ----------------------------------------------------------------- + # Legacy fallback: no template → vertical Header: Value per device + # ----------------------------------------------------------------- + @patch("models.notification_instance.get_setting_value") + def test_legacy_fallback_no_template(self, mock_setting): + from models.notification_instance import construct_notifications + + mock_setting.side_effect = self._setting_factory({ + "NTFPRCS_TEXT_SECTION_HEADERS": True, + "NTFPRCS_TEXT_TEMPLATE_new_devices": "", + }) + + json_data = _make_json( + "new_devices", SAMPLE_NEW_DEVICES, NEW_DEVICE_COLUMNS, "🆕 New devices" + ) + html, text = construct_notifications(json_data, "new_devices") + + # Section header must be present + self.assertIn("🆕 New devices", text) + self.assertIn("---------", text) + + # Legacy format: each header appears as "Header: \tValue" + self.assertIn("eve_MAC:", text) + self.assertIn("aa:bb:cc:dd:ee:ff", text) + self.assertIn("devName:", text) + self.assertIn("MyPhone", text) + + # HTML must still be generated + self.assertNotEqual(html, "") + + # ----------------------------------------------------------------- + # Template substitution: single-line format per device + # ----------------------------------------------------------------- + @patch("models.notification_instance.get_setting_value") + def test_template_substitution(self, mock_setting): + from models.notification_instance import construct_notifications + + mock_setting.side_effect = self._setting_factory({ + "NTFPRCS_TEXT_SECTION_HEADERS": True, + "NTFPRCS_TEXT_TEMPLATE_new_devices": "{devName} ({eve_MAC}) - {eve_IP}", + }) + + json_data = _make_json( + "new_devices", SAMPLE_NEW_DEVICES, NEW_DEVICE_COLUMNS, "🆕 New devices" + ) + _, text = construct_notifications(json_data, "new_devices") + + self.assertIn("MyPhone (aa:bb:cc:dd:ee:ff) - 192.168.1.42", text) + self.assertIn("Laptop (11:22:33:44:55:66) - 192.168.1.99", text) + + # ----------------------------------------------------------------- + # Missing field: {NonExistent} left as-is (safe failure) + # ----------------------------------------------------------------- + @patch("models.notification_instance.get_setting_value") + def test_missing_field_safe_failure(self, mock_setting): + from models.notification_instance import construct_notifications + + mock_setting.side_effect = self._setting_factory({ + "NTFPRCS_TEXT_SECTION_HEADERS": True, + "NTFPRCS_TEXT_TEMPLATE_new_devices": "{devName} - {NonExistent}", + }) + + json_data = _make_json( + "new_devices", SAMPLE_NEW_DEVICES, NEW_DEVICE_COLUMNS, "🆕 New devices" + ) + _, text = construct_notifications(json_data, "new_devices") + + self.assertIn("MyPhone - {NonExistent}", text) + self.assertIn("Laptop - {NonExistent}", text) + + # ----------------------------------------------------------------- + # Section headers disabled: no title/separator in text output + # ----------------------------------------------------------------- + @patch("models.notification_instance.get_setting_value") + def test_section_headers_disabled(self, mock_setting): + from models.notification_instance import construct_notifications + + mock_setting.side_effect = self._setting_factory({ + "NTFPRCS_TEXT_SECTION_HEADERS": False, + "NTFPRCS_TEXT_TEMPLATE_new_devices": "{devName} ({eve_MAC})", + }) + + json_data = _make_json( + "new_devices", SAMPLE_NEW_DEVICES, NEW_DEVICE_COLUMNS, "🆕 New devices" + ) + _, text = construct_notifications(json_data, "new_devices") + + self.assertNotIn("🆕 New devices", text) + self.assertNotIn("---------", text) + # Template output still present + self.assertIn("MyPhone (aa:bb:cc:dd:ee:ff)", text) + + # ----------------------------------------------------------------- + # Section headers enabled (default when setting absent/empty) + # ----------------------------------------------------------------- + @patch("models.notification_instance.get_setting_value") + def test_section_headers_default_enabled(self, mock_setting): + from models.notification_instance import construct_notifications + + # Simulate setting not configured (returns empty string) + mock_setting.side_effect = self._setting_factory({}) + + json_data = _make_json( + "new_devices", SAMPLE_NEW_DEVICES, NEW_DEVICE_COLUMNS, "🆕 New devices" + ) + _, text = construct_notifications(json_data, "new_devices") + + # Headers should be shown by default + self.assertIn("🆕 New devices", text) + self.assertIn("---------", text) + + # ----------------------------------------------------------------- + # Mixed valid and invalid fields in same template + # ----------------------------------------------------------------- + @patch("models.notification_instance.get_setting_value") + def test_mixed_valid_and_invalid_fields(self, mock_setting): + from models.notification_instance import construct_notifications + + mock_setting.side_effect = self._setting_factory({ + "NTFPRCS_TEXT_SECTION_HEADERS": True, + "NTFPRCS_TEXT_TEMPLATE_new_devices": "{devName} ({BadField}) - {eve_IP}", + }) + + json_data = _make_json( + "new_devices", SAMPLE_NEW_DEVICES, NEW_DEVICE_COLUMNS, "🆕 New devices" + ) + _, text = construct_notifications(json_data, "new_devices") + + self.assertIn("MyPhone ({BadField}) - 192.168.1.42", text) + + # ----------------------------------------------------------------- + # Down devices section uses same column names as all other sections + # ----------------------------------------------------------------- + @patch("models.notification_instance.get_setting_value") + def test_down_devices_template(self, mock_setting): + from models.notification_instance import construct_notifications + + mock_setting.side_effect = self._setting_factory({ + "NTFPRCS_TEXT_SECTION_HEADERS": True, + "NTFPRCS_TEXT_TEMPLATE_down_devices": "{devName} ({eve_MAC}) down since {eve_DateTime}", + }) + + down_devices = [ + { + "devName": "Router", + "eve_MAC": "ff:ee:dd:cc:bb:aa", + "devVendor": "Cisco", + "eve_IP": "10.0.0.1", + "eve_DateTime": "2025-01-15 08:00:00", + "eve_EventType": "Device Down", + "devComments": "", + } + ] + columns = ["devName", "eve_MAC", "devVendor", "eve_IP", "eve_DateTime", "eve_EventType", "devComments"] + + json_data = _make_json("down_devices", down_devices, columns, "🔴 Down devices") + _, text = construct_notifications(json_data, "down_devices") + + self.assertIn("Router (ff:ee:dd:cc:bb:aa) down since 2025-01-15 08:00:00", text) + + # ----------------------------------------------------------------- + # Down reconnected section uses same unified column names + # ----------------------------------------------------------------- + @patch("models.notification_instance.get_setting_value") + def test_down_reconnected_template(self, mock_setting): + from models.notification_instance import construct_notifications + + mock_setting.side_effect = self._setting_factory({ + "NTFPRCS_TEXT_SECTION_HEADERS": True, + "NTFPRCS_TEXT_TEMPLATE_down_reconnected": "{devName} ({eve_MAC}) reconnected at {eve_DateTime}", + }) + + reconnected = [ + { + "devName": "Switch", + "eve_MAC": "aa:11:bb:22:cc:33", + "devVendor": "Netgear", + "eve_IP": "10.0.0.2", + "eve_DateTime": "2025-01-15 09:30:00", + "eve_EventType": "Down Reconnected", + "devComments": "", + } + ] + columns = ["devName", "eve_MAC", "devVendor", "eve_IP", "eve_DateTime", "eve_EventType", "devComments"] + + json_data = _make_json("down_reconnected", reconnected, columns, "🔁 Reconnected down devices") + _, text = construct_notifications(json_data, "down_reconnected") + + self.assertIn("Switch (aa:11:bb:22:cc:33) reconnected at 2025-01-15 09:30:00", text) + + # ----------------------------------------------------------------- + # HTML output is unchanged regardless of template config + # ----------------------------------------------------------------- + @patch("models.notification_instance.get_setting_value") + def test_html_unchanged_with_template(self, mock_setting): + from models.notification_instance import construct_notifications + + # Get HTML without template + mock_setting.side_effect = self._setting_factory({ + "NTFPRCS_TEXT_SECTION_HEADERS": True, + "NTFPRCS_TEXT_TEMPLATE_new_devices": "", + }) + json_data = _make_json( + "new_devices", SAMPLE_NEW_DEVICES, NEW_DEVICE_COLUMNS, "🆕 New devices" + ) + html_without, _ = construct_notifications(json_data, "new_devices") + + # Get HTML with template + mock_setting.side_effect = self._setting_factory({ + "NTFPRCS_TEXT_SECTION_HEADERS": True, + "NTFPRCS_TEXT_TEMPLATE_new_devices": "{devName} ({eve_MAC})", + }) + html_with, _ = construct_notifications(json_data, "new_devices") + + self.assertEqual(html_without, html_with) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/integration/integration_test.py b/test/integration/integration_test.py index 200e0f49..b4e28f06 100755 --- a/test/integration/integration_test.py +++ b/test/integration/integration_test.py @@ -42,8 +42,10 @@ def test_db(test_db_path): eve_MAC TEXT, eve_DateTime TEXT, devLastIP TEXT, + eve_IP TEXT, eve_EventType TEXT, devName TEXT, + devVendor TEXT, devComments TEXT, eve_PendingAlertEmail INTEGER ) @@ -84,13 +86,13 @@ def test_db(test_db_path): # Insert test data test_data = [ - ('aa:bb:cc:dd:ee:ff', '2024-01-01 12:00:00', '192.168.1.100', 'New Device', 'Test Device', 'Test Comment', 1), - ('11:22:33:44:55:66', '2024-01-01 12:01:00', '192.168.1.101', 'Connected', 'Test Device 2', 'Another Comment', 1), - ('77:88:99:aa:bb:cc', '2024-01-01 12:02:00', '192.168.1.102', 'Disconnected', 'Test Device 3', 'Third Comment', 1), + ('aa:bb:cc:dd:ee:ff', '2024-01-01 12:00:00', '192.168.1.100', '192.168.1.100', 'New Device', 'Test Device', 'Apple', 'Test Comment', 1), + ('11:22:33:44:55:66', '2024-01-01 12:01:00', '192.168.1.101', '192.168.1.101', 'Connected', 'Test Device 2', 'Dell', 'Another Comment', 1), + ('77:88:99:aa:bb:cc', '2024-01-01 12:02:00', '192.168.1.102', '192.168.1.102', 'Disconnected', 'Test Device 3', 'Cisco', 'Third Comment', 1), ] cur.executemany(''' - INSERT INTO Events_Devices (eve_MAC, eve_DateTime, devLastIP, eve_EventType, devName, devComments, eve_PendingAlertEmail) - VALUES (?, ?, ?, ?, ?, ?, ?) + INSERT INTO Events_Devices (eve_MAC, eve_DateTime, devLastIP, eve_IP, eve_EventType, devName, devVendor, devComments, eve_PendingAlertEmail) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ''', test_data) conn.commit() @@ -115,7 +117,7 @@ def test_fresh_install_compatibility(builder): def test_existing_db_compatibility(): mock_db = Mock() mock_result = Mock() - mock_result.columnNames = ['MAC', 'Datetime', 'IP', 'Event Type', 'Device name', 'Comments'] + mock_result.columnNames = ['devName', 'eve_MAC', 'devVendor', 'eve_IP', 'eve_DateTime', 'eve_EventType', 'devComments'] mock_result.json = {'data': []} mock_db.get_table_as_json.return_value = mock_result From 23e16ae4fa1b06857b7b5f087942a2969bd988cf Mon Sep 17 00:00:00 2001 From: jokob-sk Date: Mon, 16 Mar 2026 21:03:47 +1100 Subject: [PATCH 002/189] PLG: sync handle mixed case Signed-off-by: jokob-sk --- front/plugins/sync/sync.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/front/plugins/sync/sync.py b/front/plugins/sync/sync.py index f6b0a3a7..514659b3 100755 --- a/front/plugins/sync/sync.py +++ b/front/plugins/sync/sync.py @@ -187,9 +187,10 @@ def main(): with open(file_path, 'r') as f: data = json.load(f) for device in data['data']: - if device['devMac'] not in unique_mac_addresses: + device['devMac'] = str(device['devMac']).lower() + if device['devMac'].lower() not in unique_mac_addresses: device['devSyncHubNode'] = syncHubNodeName - unique_mac_addresses.add(device['devMac']) + unique_mac_addresses.add(device['devMac'].lower()) device_data.append(device) # Rename the file to "processed_" + current name @@ -206,7 +207,7 @@ def main(): # Retrieve existing devMac values from the Devices table placeholders = ', '.join('?' for _ in unique_mac_addresses) cursor.execute(f'SELECT devMac FROM Devices WHERE devMac IN ({placeholders})', tuple(unique_mac_addresses)) - existing_mac_addresses = set(row[0] for row in cursor.fetchall()) + existing_mac_addresses = set(row[0].lower() for row in cursor.fetchall()) # insert devices into the last_result.log and thus CurrentScan table to manage state for device in device_data: @@ -229,7 +230,10 @@ def main(): db_columns = {row[1] for row in cursor.fetchall()} # Filter out existing devices - new_devices = [device for device in device_data if device['devMac'] not in existing_mac_addresses] + new_devices = [ + device for device in device_data + if device['devMac'].lower() not in existing_mac_addresses + ] mylog('verbose', [f'[{pluginName}] All devices: "{len(device_data)}"']) mylog('verbose', [f'[{pluginName}] New devices: "{len(new_devices)}"']) From c7399215ecd76126156b4c35f7e16262bdea5598 Mon Sep 17 00:00:00 2001 From: "Jokob @NetAlertX" <96159884+jokob-sk@users.noreply.github.com> Date: Mon, 16 Mar 2026 10:11:22 +0000 Subject: [PATCH 003/189] Refactor event and session column names to camelCase - Updated test cases to reflect new column names (eve_MAC -> eveMac, eve_DateTime -> eveDateTime, etc.) across various test files. - Modified SQL table definitions in the database cleanup and migration tests to use camelCase naming conventions. - Implemented migration tests to ensure legacy column names are correctly renamed to camelCase equivalents. - Ensured that existing data is preserved during the migration process and that views referencing old column names are dropped before renaming. - Verified that the migration function is idempotent, allowing for safe re-execution without data loss. --- .github/skills/code-standards/SKILL.md | 1 + docs/API_EVENTS.md | 36 +- docs/API_SESSIONS.md | 24 +- docs/DEBUG_PLUGINS.md | 4 +- docs/NOTIFICATIONS.md | 2 +- docs/NOTIFICATION_TEMPLATES.md | 48 +- docs/PLUGINS_DEV.md | 18 +- docs/PLUGINS_DEV_CONFIG.md | 8 +- docs/PLUGINS_DEV_DATASOURCES.md | 16 +- docs/PLUGINS_DEV_DATA_CONTRACT.md | 62 +-- docs/PLUGINS_DEV_QUICK_START.md | 12 +- docs/PLUGINS_DEV_UI_COMPONENTS.md | 44 +- front/appEventsCore.php | 40 +- front/deviceDetailsEvents.php | 18 +- front/deviceDetailsSessions.php | 12 +- front/js/app-init.js | 2 +- front/js/cache.js | 6 +- front/multiEditCore.php | 1 + front/php/components/graph_online_history.php | 10 +- front/php/templates/language/lang.php | 2 +- front/plugins/__template/config.json | 22 +- front/plugins/__template/rename_me.py | 2 +- front/plugins/_publisher_apprise/config.json | 26 +- front/plugins/_publisher_email/config.json | 26 +- front/plugins/_publisher_mqtt/config.json | 28 +- front/plugins/_publisher_mqtt/mqtt.py | 4 +- front/plugins/_publisher_ntfy/config.json | 22 +- front/plugins/_publisher_pushover/config.json | 22 +- .../plugins/_publisher_pushsafer/config.json | 22 +- front/plugins/_publisher_telegram/config.json | 26 +- front/plugins/_publisher_webhook/config.json | 22 +- front/plugins/adguard_import/config.json | 22 +- front/plugins/arp_scan/config.json | 32 +- front/plugins/asuswrt_import/config.json | 18 +- front/plugins/avahi_scan/config.json | 18 +- front/plugins/db_cleanup/script.py | 24 +- front/plugins/ddns_update/config.json | 36 +- front/plugins/dhcp_leases/ASUS_ROUTERS.md | 6 +- front/plugins/dhcp_leases/config.json | 50 +- front/plugins/dhcp_servers/config.json | 44 +- front/plugins/dig_scan/config.json | 18 +- front/plugins/freebox/config.json | 22 +- front/plugins/icmp_scan/config.json | 22 +- front/plugins/internet_ip/config.json | 42 +- front/plugins/internet_speedtest/README.md | 8 +- front/plugins/internet_speedtest/config.json | 46 +- front/plugins/ipneigh/config.json | 22 +- front/plugins/luci_import/config.json | 18 +- front/plugins/mikrotik_scan/config.json | 20 +- front/plugins/nbtscan_scan/config.json | 18 +- front/plugins/nmap_dev_scan/config.json | 34 +- front/plugins/nmap_scan/config.json | 44 +- .../plugins/notification_processing/README.md | 4 +- .../notification_processing/config.json | 10 +- front/plugins/nslookup_scan/config.json | 18 +- front/plugins/omada_sdn_imp/config.json | 36 +- front/plugins/omada_sdn_imp/omada_sdn.py | 2 +- front/plugins/omada_sdn_openapi/config.json | 40 +- front/plugins/pihole_api_scan/config.json | 22 +- front/plugins/pihole_scan/config.json | 36 +- front/plugins/snmp_discovery/config.json | 44 +- front/plugins/sync/config.json | 22 +- front/plugins/unifi_api_import/config.json | 22 +- front/plugins/unifi_import/config.json | 50 +- front/plugins/vendor_update/config.json | 46 +- front/plugins/wake_on_lan/config.json | 22 +- front/plugins/website_monitor/config.json | 44 +- front/pluginsCore.php | 16 +- front/report.php | 18 +- .../report_templates/webhook_json_sample.json | 60 +-- scripts/db_cleanup/db_cleanup.py | 32 +- server/api_server/graphql_endpoint.py | 70 +-- server/api_server/sessions_endpoint.py | 196 +++---- server/const.py | 28 +- server/database.py | 5 + server/db/db_upgrade.py | 446 +++++++++++----- server/db/schema/app.sql | 494 ++++++------------ server/db/sql_safe_builder.py | 30 +- server/initialise.py | 82 ++- server/messaging/notification_sections.py | 92 ++-- server/messaging/reporting.py | 22 +- server/models/device_instance.py | 42 +- server/models/event_instance.py | 60 +-- server/models/notification_instance.py | 50 +- server/models/plugin_object_instance.py | 20 +- server/plugin.py | 204 ++++---- server/scan/device_handling.py | 18 +- server/scan/name_resolution.py | 8 +- server/scan/session_events.py | 66 +-- server/utils/plugin_utils.py | 2 +- server/workflows/actions.py | 8 +- server/workflows/app_events.py | 132 ++--- server/workflows/manager.py | 12 +- server/workflows/triggers.py | 6 +- test/api_endpoints/test_events_endpoints.py | 10 +- .../api_endpoints/test_mcp_tools_endpoints.py | 6 +- test/api_endpoints/test_sessions_endpoints.py | 6 +- test/backend/sql_safe_builder.py | 30 +- test/backend/test_compound_conditions.py | 2 +- test/backend/test_notification_templates.py | 38 +- test/backend/test_safe_builder_unit.py | 2 +- test/backend/test_sql_injection_prevention.py | 4 +- test/backend/test_sql_security.py | 14 +- test/db/test_camelcase_migration.py | 307 +++++++++++ test/db/test_db_cleanup.py | 52 +- test/db_test_helpers.py | 20 +- test/integration/integration_test.py | 56 +- test/scan/test_down_sleep_events.py | 6 +- test/scan/test_field_lock_scan_integration.py | 28 +- 109 files changed, 2403 insertions(+), 1967 deletions(-) create mode 100644 test/db/test_camelcase_migration.py diff --git a/.github/skills/code-standards/SKILL.md b/.github/skills/code-standards/SKILL.md index 98c7516b..00128188 100644 --- a/.github/skills/code-standards/SKILL.md +++ b/.github/skills/code-standards/SKILL.md @@ -12,6 +12,7 @@ description: NetAlertX coding standards and conventions. Use this when writing c - code has to be maintainable, no duplicate code - follow DRY principle - maintainability of code is more important than speed of implementation - code files should be less than 500 LOC for better maintainability +- DB columns must not contain underscores, use camelCase instead (e.g., deviceInstanceId, not device_instance_id) ## File Length diff --git a/docs/API_EVENTS.md b/docs/API_EVENTS.md index ff423c4f..d933658b 100755 --- a/docs/API_EVENTS.md +++ b/docs/API_EVENTS.md @@ -58,12 +58,12 @@ The Events API provides access to **device event logs**, allowing creation, retr "success": true, "events": [ { - "eve_MAC": "00:11:22:33:44:55", - "eve_IP": "192.168.1.10", - "eve_DateTime": "2025-08-24T12:00:00Z", - "eve_EventType": "Device Down", - "eve_AdditionalInfo": "", - "eve_PendingAlertEmail": 1 + "eveMac": "00:11:22:33:44:55", + "eveIp": "192.168.1.10", + "eveDateTime": "2025-08-24T12:00:00Z", + "eveEventType": "Device Down", + "eveAdditionalInfo": "", + "evePendingAlertEmail": 1 } ] } @@ -102,11 +102,11 @@ The Events API provides access to **device event logs**, allowing creation, retr "count": 5, "events": [ { - "eve_DateTime": "2025-12-07 12:00:00", - "eve_EventType": "New Device", - "eve_MAC": "AA:BB:CC:DD:EE:FF", - "eve_IP": "192.168.1.100", - "eve_AdditionalInfo": "Device detected" + "eveDateTime": "2025-12-07 12:00:00", + "eveEventType": "New Device", + "eveMac": "AA:BB:CC:DD:EE:FF", + "eveIp": "192.168.1.100", + "eveAdditionalInfo": "Device detected" } ] } @@ -127,9 +127,9 @@ The Events API provides access to **device event logs**, allowing creation, retr "count": 10, "events": [ { - "eve_DateTime": "2025-12-07 12:00:00", - "eve_EventType": "Device Down", - "eve_MAC": "AA:BB:CC:DD:EE:FF" + "eveDateTime": "2025-12-07 12:00:00", + "eveEventType": "Device Down", + "eveMac": "AA:BB:CC:DD:EE:FF" } ] } @@ -159,9 +159,9 @@ The Events API provides access to **device event logs**, allowing creation, retr 1. Total events in the period 2. Total sessions 3. Missing sessions -4. Voided events (`eve_EventType LIKE 'VOIDED%'`) -5. New device events (`eve_EventType LIKE 'New Device'`) -6. Device down events (`eve_EventType LIKE 'Device Down'`) +4. Voided events (`eveEventType LIKE 'VOIDED%'`) +5. New device events (`eveEventType LIKE 'New Device'`) +6. Device down events (`eveEventType LIKE 'Device Down'`) --- @@ -187,7 +187,7 @@ Event endpoints are available as **MCP Tools** for AI assistant integration: ``` * Events are stored in the **Events table** with the following fields: - `eve_MAC`, `eve_IP`, `eve_DateTime`, `eve_EventType`, `eve_AdditionalInfo`, `eve_PendingAlertEmail`. + `eveMac`, `eveIp`, `eveDateTime`, `eveEventType`, `eveAdditionalInfo`, `evePendingAlertEmail`. * Event creation automatically logs activity for debugging. diff --git a/docs/API_SESSIONS.md b/docs/API_SESSIONS.md index 94224aa4..d5d19eed 100755 --- a/docs/API_SESSIONS.md +++ b/docs/API_SESSIONS.md @@ -106,12 +106,12 @@ curl -X DELETE "http://:/sessions/delete" \ "success": true, "sessions": [ { - "ses_MAC": "AA:BB:CC:DD:EE:FF", - "ses_Connection": "2025-08-01 10:00", - "ses_Disconnection": "2025-08-01 12:00", - "ses_Duration": "2h 0m", - "ses_IP": "192.168.1.10", - "ses_Info": "" + "sesMac": "AA:BB:CC:DD:EE:FF", + "sesDateTimeConnection": "2025-08-01 10:00", + "sesDateTimeDisconnection": "2025-08-01 12:00", + "sesDuration": "2h 0m", + "sesIp": "192.168.1.10", + "sesAdditionalInfo": "" } ] } @@ -194,12 +194,12 @@ curl -X GET "http://:/sessions/calendar?start=2025-08-0 "success": true, "sessions": [ { - "ses_MAC": "AA:BB:CC:DD:EE:FF", - "ses_Connection": "2025-08-01 10:00", - "ses_Disconnection": "2025-08-01 12:00", - "ses_Duration": "2h 0m", - "ses_IP": "192.168.1.10", - "ses_Info": "" + "sesMac": "AA:BB:CC:DD:EE:FF", + "sesDateTimeConnection": "2025-08-01 10:00", + "sesDateTimeDisconnection": "2025-08-01 12:00", + "sesDuration": "2h 0m", + "sesIp": "192.168.1.10", + "sesAdditionalInfo": "" } ] } diff --git a/docs/DEBUG_PLUGINS.md b/docs/DEBUG_PLUGINS.md index 947eee0d..f84e752d 100755 --- a/docs/DEBUG_PLUGINS.md +++ b/docs/DEBUG_PLUGINS.md @@ -43,7 +43,7 @@ Input data from the plugin might cause mapping issues in specific edge cases. Lo 17:31:05 [Scheduler] run for PIHOLE: YES 17:31:05 [Plugin utils] --------------------------------------------- 17:31:05 [Plugin utils] display_name: PiHole (Device sync) -17:31:05 [Plugins] CMD: SELECT n.hwaddr AS Object_PrimaryID, {s-quote}null{s-quote} AS Object_SecondaryID, datetime() AS DateTime, na.ip AS Watched_Value1, n.lastQuery AS Watched_Value2, na.name AS Watched_Value3, n.macVendor AS Watched_Value4, {s-quote}null{s-quote} AS Extra, n.hwaddr AS ForeignKey FROM EXTERNAL_PIHOLE.Network AS n LEFT JOIN EXTERNAL_PIHOLE.Network_Addresses AS na ON na.network_id = n.id WHERE n.hwaddr NOT LIKE {s-quote}ip-%{s-quote} AND n.hwaddr is not {s-quote}00:00:00:00:00:00{s-quote} AND na.ip is not null +17:31:05 [Plugins] CMD: SELECT n.hwaddr AS objectPrimaryId, {s-quote}null{s-quote} AS objectSecondaryId, datetime() AS DateTime, na.ip AS watchedValue1, n.lastQuery AS watchedValue2, na.name AS watchedValue3, n.macVendor AS watchedValue4, {s-quote}null{s-quote} AS Extra, n.hwaddr AS ForeignKey FROM EXTERNAL_PIHOLE.Network AS n LEFT JOIN EXTERNAL_PIHOLE.Network_Addresses AS na ON na.network_id = n.id WHERE n.hwaddr NOT LIKE {s-quote}ip-%{s-quote} AND n.hwaddr is not {s-quote}00:00:00:00:00:00{s-quote} AND na.ip is not null 17:31:05 [Plugins] setTyp: subnets 17:31:05 [Plugin utils] Flattening the below array 17:31:05 ['192.168.1.0/24 --interface=eth1'] @@ -52,7 +52,7 @@ Input data from the plugin might cause mapping issues in specific edge cases. Lo 17:31:05 [Plugins] Convert to Base64: True 17:31:05 [Plugins] base64 value: b'MTkyLjE2OC4xLjAvMjQgLS1pbnRlcmZhY2U9ZXRoMQ==' 17:31:05 [Plugins] Timeout: 10 -17:31:05 [Plugins] Executing: SELECT n.hwaddr AS Object_PrimaryID, 'null' AS Object_SecondaryID, datetime() AS DateTime, na.ip AS Watched_Value1, n.lastQuery AS Watched_Value2, na.name AS Watched_Value3, n.macVendor AS Watched_Value4, 'null' AS Extra, n.hwaddr AS ForeignKey FROM EXTERNAL_PIHOLE.Network AS n LEFT JOIN EXTERNAL_PIHOLE.Network_Addresses AS na ON na.network_id = n.id WHERE n.hwaddr NOT LIKE 'ip-%' AND n.hwaddr is not '00:00:00:00:00:00' AND na.ip is not null +17:31:05 [Plugins] Executing: SELECT n.hwaddr AS objectPrimaryId, 'null' AS objectSecondaryId, datetime() AS DateTime, na.ip AS watchedValue1, n.lastQuery AS watchedValue2, na.name AS watchedValue3, n.macVendor AS watchedValue4, 'null' AS Extra, n.hwaddr AS ForeignKey FROM EXTERNAL_PIHOLE.Network AS n LEFT JOIN EXTERNAL_PIHOLE.Network_Addresses AS na ON na.network_id = n.id WHERE n.hwaddr NOT LIKE 'ip-%' AND n.hwaddr is not '00:00:00:00:00:00' AND na.ip is not null 🔻 17:31:05 [Plugins] SUCCESS, received 2 entries 17:31:05 [Plugins] sqlParam entries: [(0, 'PIHOLE', '01:01:01:01:01:01', 'null', 'null', '2023-12-25 06:31:05', '172.30.0.1', 0, 'aaaa', 'vvvvvvvvv', 'not-processed', 'null', 'null', '01:01:01:01:01:01'), (0, 'PIHOLE', '02:42:ac:1e:00:02', 'null', 'null', '2023-12-25 06:31:05', '172.30.0.2', 0, 'dddd', 'vvvvv2222', 'not-processed', 'null', 'null', '02:42:ac:1e:00:02')] diff --git a/docs/NOTIFICATIONS.md b/docs/NOTIFICATIONS.md index 3bd12d5a..eb29b1a2 100755 --- a/docs/NOTIFICATIONS.md +++ b/docs/NOTIFICATIONS.md @@ -36,7 +36,7 @@ The following device properties influence notifications. You can: On almost all plugins there are 2 core settings, `_WATCH` and `_REPORT_ON`. 1. `_WATCH` specifies the columns which the app should watch. If watched columns change the device state is considered changed. This changed status is then used to decide to send out notifications based on the `_REPORT_ON` setting. -2. `_REPORT_ON` let's you specify on which events the app should notify you. This is related to the `_WATCH` setting. So if you select `watched-changed` and in `_WATCH` you only select `Watched_Value1`, then a notification is triggered if `Watched_Value1` is changed from the previous value, but no notification is send if `Watched_Value2` changes. +2. `_REPORT_ON` let's you specify on which events the app should notify you. This is related to the `_WATCH` setting. So if you select `watched-changed` and in `_WATCH` you only select `watchedValue1`, then a notification is triggered if `watchedValue1` is changed from the previous value, but no notification is send if `watchedValue2` changes. Click the **Read more in the docs.** Link at the top of each plugin to get more details on how the given plugin works. diff --git a/docs/NOTIFICATION_TEMPLATES.md b/docs/NOTIFICATION_TEMPLATES.md index 2956bd32..63828703 100644 --- a/docs/NOTIFICATION_TEMPLATES.md +++ b/docs/NOTIFICATION_TEMPLATES.md @@ -10,7 +10,7 @@ HTML email tables are **not affected** by these templates. 1. Go to **Settings → Notification Processing**. 2. Set a template string for the section you want to customize, e.g.: - - **Text Template: New Devices** → `{devName} ({eve_MAC}) - {eve_IP}` + - **Text Template: New Devices** → `{devName} ({eveMac}) - {eveIp}` 3. Save. The next notification will use your format. **Before (default):** @@ -18,15 +18,15 @@ HTML email tables are **not affected** by these templates. 🆕 New devices --------- devName: MyPhone -eve_MAC: aa:bb:cc:dd:ee:ff +eveMac: aa:bb:cc:dd:ee:ff devVendor: Apple -eve_IP: 192.168.1.42 -eve_DateTime: 2025-01-15 10:30:00 -eve_EventType: New Device +eveIp: 192.168.1.42 +eveDateTime: 2025-01-15 10:30:00 +eveEventType: New Device devComments: ``` -**After (with template `{devName} ({eve_MAC}) - {eve_IP}`):** +**After (with template `{devName} ({eveMac}) - {eveIp}`):** ``` 🆕 New devices --------- @@ -51,7 +51,7 @@ When a template is **empty**, the section uses the original vertical `Header: Va Use `{FieldName}` to insert a value from the notification data. Field names are **case-sensitive** and must match the column names exactly. ``` -{devName} ({eve_MAC}) connected at {eve_DateTime} +{devName} ({eveMac}) connected at {eveDateTime} ``` - No loops, conditionals, or nesting — just simple string replacement. @@ -66,34 +66,34 @@ All four device sections (`new_devices`, `down_devices`, `down_reconnected`, `ev | Variable | Description | |----------|-------------| | `{devName}` | Device display name | -| `{eve_MAC}` | Device MAC address | +| `{eveMac}` | Device MAC address | | `{devVendor}` | Device vendor/manufacturer | -| `{eve_IP}` | Device IP address | -| `{eve_DateTime}` | Event timestamp | -| `{eve_EventType}` | Type of event (e.g. `New Device`, `Connected`, `Device Down`) | +| `{eveIp}` | Device IP address | +| `{eveDateTime}` | Event timestamp | +| `{eveEventType}` | Type of event (e.g. `New Device`, `Connected`, `Device Down`) | | `{devComments}` | Device comments | -**Example (new_devices/events):** `{devName} ({eve_MAC}) - {eve_IP} [{eve_EventType}]` +**Example (new_devices/events):** `{devName} ({eveMac}) - {eveIp} [{eveEventType}]` -**Example (down_devices):** `{devName} ({eve_MAC}) {devVendor} - went down at {eve_DateTime}` +**Example (down_devices):** `{devName} ({eveMac}) {devVendor} - went down at {eveDateTime}` -**Example (down_reconnected):** `{devName} ({eve_MAC}) reconnected at {eve_DateTime}` +**Example (down_reconnected):** `{devName} ({eveMac}) reconnected at {eveDateTime}` ### `plugins` | Variable | Description | |----------|-------------| -| `{Plugin}` | Plugin code name | -| `{Object_PrimaryId}` | Primary identifier of the object | -| `{Object_SecondaryId}` | Secondary identifier | -| `{DateTimeChanged}` | Timestamp of change | -| `{Watched_Value1}` | First watched value | -| `{Watched_Value2}` | Second watched value | -| `{Watched_Value3}` | Third watched value | -| `{Watched_Value4}` | Fourth watched value | -| `{Status}` | Plugin event status | +| `{plugin}` | Plugin code name | +| `{objectPrimaryId}` | Primary identifier of the object | +| `{objectSecondaryId}` | Secondary identifier | +| `{dateTimeChanged}` | Timestamp of change | +| `{watchedValue1}` | First watched value | +| `{watchedValue2}` | Second watched value | +| `{watchedValue3}` | Third watched value | +| `{watchedValue4}` | Fourth watched value | +| `{status}` | Plugin event status | -**Example:** `{Plugin}: {Object_PrimaryId} - {Status}` +**Example:** `{plugin}: {objectPrimaryId} - {status}` ## Section Headers Toggle diff --git a/docs/PLUGINS_DEV.md b/docs/PLUGINS_DEV.md index 1bfaf854..04780588 100755 --- a/docs/PLUGINS_DEV.md +++ b/docs/PLUGINS_DEV.md @@ -179,13 +179,13 @@ Quick reference: | Column | Name | Required | Example | |--------|------|----------|---------| -| 0 | Object_PrimaryID | **YES** | `"device_name"` or `"192.168.1.1"` | -| 1 | Object_SecondaryID | no | `"secondary_id"` or `null` | +| 0 | objectPrimaryId | **YES** | `"device_name"` or `"192.168.1.1"` | +| 1 | objectSecondaryId | no | `"secondary_id"` or `null` | | 2 | DateTime | **YES** | `"2023-01-02 15:56:30"` | -| 3 | Watched_Value1 | **YES** | `"online"` or `"200"` | -| 4 | Watched_Value2 | no | `"ip_address"` or `null` | -| 5 | Watched_Value3 | no | `null` | -| 6 | Watched_Value4 | no | `null` | +| 3 | watchedValue1 | **YES** | `"online"` or `"200"` | +| 4 | watchedValue2 | no | `"ip_address"` or `null` | +| 5 | watchedValue3 | no | `null` | +| 6 | watchedValue4 | no | `null` | | 7 | Extra | no | `"additional data"` or `null` | | 8 | ForeignKey | no | `"aa:bb:cc:dd:ee:ff"` or `null` | @@ -243,7 +243,7 @@ Control which rows display in the UI: { "data_filters": [ { - "compare_column": "Object_PrimaryID", + "compare_column": "objectPrimaryId", "compare_operator": "==", "compare_field_id": "txtMacFilter", "compare_js_template": "'{value}'.toString()", @@ -267,7 +267,7 @@ To import plugin data into NetAlertX tables for device discovery or notification "mapped_to_table": "CurrentScan", "database_column_definitions": [ { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "mapped_to_column": "scanMac", "show": true, "type": "device_mac", @@ -345,7 +345,7 @@ See [PLUGINS_DEV_SETTINGS.md](PLUGINS_DEV_SETTINGS.md) for complete settings doc ### Plugin Output Format ``` -Object_PrimaryID|Object_SecondaryID|DateTime|Watched_Value1|Watched_Value2|Watched_Value3|Watched_Value4|Extra|ForeignKey +objectPrimaryId|objectSecondaryId|DateTime|watchedValue1|watchedValue2|watchedValue3|watchedValue4|Extra|ForeignKey ``` 9 required columns, 4 optional helpers = 13 max diff --git a/docs/PLUGINS_DEV_CONFIG.md b/docs/PLUGINS_DEV_CONFIG.md index bdc7efcc..69e95bdb 100755 --- a/docs/PLUGINS_DEV_CONFIG.md +++ b/docs/PLUGINS_DEV_CONFIG.md @@ -77,7 +77,7 @@ It also describes plugin output expectations and the main plugin categories. * `database_column_definitions` * `mapped_to_table` -**Example:** `Object_PrimaryID → devMAC` +**Example:** `objectPrimaryId → devMAC` --- @@ -88,9 +88,9 @@ Output values are pipe-delimited in a fixed order. #### Identifiers -* `Object_PrimaryID` and `Object_SecondaryID` uniquely identify records (for example, `MAC|IP`). +* `objectPrimaryId` and `objectSecondaryId` uniquely identify records (for example, `MAC|IP`). -#### Watched Values (`Watched_Value1–4`) +#### Watched Values (`watchedValue1–4`) * Used by the core to detect changes between runs. * Changes in these fields can trigger notifications. @@ -114,7 +114,7 @@ Output values are pipe-delimited in a fixed order. ### 7. Persistence * Parsed data is **upserted** into the database. -* Conflicts are resolved using the combined key: `Object_PrimaryID + Object_SecondaryID`. +* Conflicts are resolved using the combined key: `objectPrimaryId + objectSecondaryId`. --- diff --git a/docs/PLUGINS_DEV_DATASOURCES.md b/docs/PLUGINS_DEV_DATASOURCES.md index f6d547cd..cc3b7700 100644 --- a/docs/PLUGINS_DEV_DATASOURCES.md +++ b/docs/PLUGINS_DEV_DATASOURCES.md @@ -107,7 +107,7 @@ Query the NetAlertX SQLite database and display results. { "function": "CMD", "type": {"dataType": "string", "elements": [{"elementType": "input", "elementOptions": [], "transformers": []}]}, - "default_value": "SELECT dv.devName as Object_PrimaryID, cast(dv.devLastIP as VARCHAR(100)) || ':' || cast(SUBSTR(ns.Port, 0, INSTR(ns.Port, '/')) as VARCHAR(100)) as Object_SecondaryID, datetime() as DateTime, ns.Service as Watched_Value1, ns.State as Watched_Value2, null as Watched_Value3, null as Watched_Value4, ns.Extra as Extra, dv.devMac as ForeignKey FROM (SELECT * FROM Nmap_Scan) ns LEFT JOIN (SELECT devName, devMac, devLastIP FROM Devices) dv ON ns.MAC = dv.devMac", + "default_value": "SELECT dv.devName as objectPrimaryId, cast(dv.devLastIP as VARCHAR(100)) || ':' || cast(SUBSTR(ns.Port, 0, INSTR(ns.Port, '/')) as VARCHAR(100)) as objectSecondaryId, datetime() as DateTime, ns.Service as watchedValue1, ns.State as watchedValue2, null as watchedValue3, null as watchedValue4, ns.Extra as Extra, dv.devMac as ForeignKey FROM (SELECT * FROM Nmap_Scan) ns LEFT JOIN (SELECT devName, devMac, devLastIP FROM Devices) dv ON ns.MAC = dv.devMac", "localized": ["name"], "name": [{"language_code": "en_us", "string": "SQL to run"}], "description": [{"language_code": "en_us", "string": "This SQL query populates the plugin table"}] @@ -118,13 +118,13 @@ Query the NetAlertX SQLite database and display results. ```sql SELECT - e.EventValue as Object_PrimaryID, - d.devName as Object_SecondaryID, + e.EventValue as objectPrimaryId, + d.devName as objectSecondaryId, e.EventDateTime as DateTime, - e.EventType as Watched_Value1, - d.devLastIP as Watched_Value2, - null as Watched_Value3, - null as Watched_Value4, + e.EventType as watchedValue1, + d.devLastIP as watchedValue2, + null as watchedValue3, + null as watchedValue4, e.EventDetails as Extra, d.devMac as ForeignKey FROM @@ -181,7 +181,7 @@ Then set data source and query: ```json { "function": "CMD", - "default_value": "SELECT hwaddr as Object_PrimaryID, cast('http://' || (SELECT ip FROM EXTERNAL_PIHOLE.network_addresses WHERE network_id = id ORDER BY lastseen DESC LIMIT 1) as VARCHAR(100)) || ':' || cast(SUBSTR((SELECT name FROM EXTERNAL_PIHOLE.network_addresses WHERE network_id = id ORDER BY lastseen DESC LIMIT 1), 0, INSTR((SELECT name FROM EXTERNAL_PIHOLE.network_addresses WHERE network_id = id ORDER BY lastseen DESC LIMIT 1), '/')) as VARCHAR(100)) as Object_SecondaryID, datetime() as DateTime, macVendor as Watched_Value1, lastQuery as Watched_Value2, (SELECT name FROM EXTERNAL_PIHOLE.network_addresses WHERE network_id = id ORDER BY lastseen DESC LIMIT 1) as Watched_Value3, null as Watched_Value4, '' as Extra, hwaddr as ForeignKey FROM EXTERNAL_PIHOLE.network WHERE hwaddr NOT LIKE 'ip-%' AND hwaddr <> '00:00:00:00:00:00'", + "default_value": "SELECT hwaddr as objectPrimaryId, cast('http://' || (SELECT ip FROM EXTERNAL_PIHOLE.network_addresses WHERE network_id = id ORDER BY lastseen DESC LIMIT 1) as VARCHAR(100)) || ':' || cast(SUBSTR((SELECT name FROM EXTERNAL_PIHOLE.network_addresses WHERE network_id = id ORDER BY lastseen DESC LIMIT 1), 0, INSTR((SELECT name FROM EXTERNAL_PIHOLE.network_addresses WHERE network_id = id ORDER BY lastseen DESC LIMIT 1), '/')) as VARCHAR(100)) as objectSecondaryId, datetime() as DateTime, macVendor as watchedValue1, lastQuery as watchedValue2, (SELECT name FROM EXTERNAL_PIHOLE.network_addresses WHERE network_id = id ORDER BY lastseen DESC LIMIT 1) as watchedValue3, null as watchedValue4, '' as Extra, hwaddr as ForeignKey FROM EXTERNAL_PIHOLE.network WHERE hwaddr NOT LIKE 'ip-%' AND hwaddr <> '00:00:00:00:00:00'", "localized": ["name"], "name": [{"language_code": "en_us", "string": "SQL to run"}] } diff --git a/docs/PLUGINS_DEV_DATA_CONTRACT.md b/docs/PLUGINS_DEV_DATA_CONTRACT.md index f0c5390b..e0a0a469 100644 --- a/docs/PLUGINS_DEV_DATA_CONTRACT.md +++ b/docs/PLUGINS_DEV_DATA_CONTRACT.md @@ -18,19 +18,19 @@ Plugins communicate with NetAlertX by writing results to a **pipe-delimited log ## Column Specification > [!NOTE] -> The order of columns is **FIXED** and cannot be changed. All 9 mandatory columns must be provided. If you use any optional column (`HelpVal1`), you must supply all optional columns (`HelpVal1` through `HelpVal4`). +> The order of columns is **FIXED** and cannot be changed. All 9 mandatory columns must be provided. If you use any optional column (`helpVal1`), you must supply all optional columns (`helpVal1` through `helpVal4`). ### Mandatory Columns (0–8) | Order | Column Name | Type | Required | Description | |-------|-------------|------|----------|-------------| -| 0 | `Object_PrimaryID` | string | **YES** | The primary identifier for grouping. Examples: device MAC, hostname, service name, or any unique ID | -| 1 | `Object_SecondaryID` | string | no | Secondary identifier for relationships (e.g., IP address, port, sub-ID). Use `null` if not needed | +| 0 | `objectPrimaryId` | string | **YES** | The primary identifier for grouping. Examples: device MAC, hostname, service name, or any unique ID | +| 1 | `objectSecondaryId` | string | no | Secondary identifier for relationships (e.g., IP address, port, sub-ID). Use `null` if not needed | | 2 | `DateTime` | string | **YES** | Timestamp when the event/data was collected. Format: `YYYY-MM-DD HH:MM:SS` | -| 3 | `Watched_Value1` | string | **YES** | Primary watched value. Changes trigger notifications. Examples: IP address, status, version | -| 4 | `Watched_Value2` | string | no | Secondary watched value. Use `null` if not needed | -| 5 | `Watched_Value3` | string | no | Tertiary watched value. Use `null` if not needed | -| 6 | `Watched_Value4` | string | no | Quaternary watched value. Use `null` if not needed | +| 3 | `watchedValue1` | string | **YES** | Primary watched value. Changes trigger notifications. Examples: IP address, status, version | +| 4 | `watchedValue2` | string | no | Secondary watched value. Use `null` if not needed | +| 5 | `watchedValue3` | string | no | Tertiary watched value. Use `null` if not needed | +| 6 | `watchedValue4` | string | no | Quaternary watched value. Use `null` if not needed | | 7 | `Extra` | string | no | Any additional metadata to display in UI and notifications. Use `null` if not needed | | 8 | `ForeignKey` | string | no | Foreign key linking to parent object (usually MAC address for device relationship). Use `null` if not needed | @@ -38,10 +38,10 @@ Plugins communicate with NetAlertX by writing results to a **pipe-delimited log | Order | Column Name | Type | Required | Description | |-------|-------------|------|----------|-------------| -| 9 | `HelpVal1` | string | *conditional* | Helper value 1. If used, all help values must be supplied | -| 10 | `HelpVal2` | string | *conditional* | Helper value 2. If used, all help values must be supplied | -| 11 | `HelpVal3` | string | *conditional* | Helper value 3. If used, all help values must be supplied | -| 12 | `HelpVal4` | string | *conditional* | Helper value 4. If used, all help values must be supplied | +| 9 | `helpVal1` | string | *conditional* | Helper value 1. If used, all help values must be supplied | +| 10 | `helpVal2` | string | *conditional* | Helper value 2. If used, all help values must be supplied | +| 11 | `helpVal3` | string | *conditional* | Helper value 3. If used, all help values must be supplied | +| 12 | `helpVal4` | string | *conditional* | Helper value 4. If used, all help values must be supplied | ## Usage Guide @@ -58,15 +58,15 @@ Watched values are fields that the NetAlertX core monitors for **changes between **How to use them:** -- `Watched_Value1`: Always required; primary indicator of status/state -- `Watched_Value2–4`: Optional; use for secondary/tertiary state information +- `watchedValue1`: Always required; primary indicator of status/state +- `watchedValue2–4`: Optional; use for secondary/tertiary state information - Leave unused ones as `null` **Example:** -- Device scanner: `Watched_Value1 = "online"` or `"offline"` -- Port scanner: `Watched_Value1 = "80"` (port number), `Watched_Value2 = "open"` (state) -- Service monitor: `Watched_Value1 = "200"` (HTTP status), `Watched_Value2 = "0.45"` (response time) +- Device scanner: `watchedValue1 = "online"` or `"offline"` +- Port scanner: `watchedValue1 = "80"` (port number), `watchedValue2 = "open"` (state) +- Service monitor: `watchedValue1 = "200"` (HTTP status), `watchedValue2 = "0.45"` (response time) ### Foreign Key @@ -110,14 +110,14 @@ https://google.com|null|2023-01-02 15:56:30|200|0.7898||null|null Missing pipe ``` -❌ **Missing mandatory Watched_Value1** (column 3): +❌ **Missing mandatory watchedValue1** (column 3): ```csv https://duckduckgo.com|192.168.1.1|2023-01-02 15:56:30|null|0.9898|null|null|Best|null ↑ Must not be null ``` -❌ **Incomplete optional columns** (has HelpVal1 but missing HelpVal2–4): +❌ **Incomplete optional columns** (has helpVal1 but missing helpVal2–4): ```csv device|null|2023-01-02 15:56:30|status|null|null|null|null|null|helper1 ↑ @@ -146,19 +146,19 @@ plugin_objects = Plugin_Objects("YOURPREFIX") # Add objects plugin_objects.add_object( - Object_PrimaryID="device_id", - Object_SecondaryID="192.168.1.1", + objectPrimaryId="device_id", + objectSecondaryId="192.168.1.1", DateTime="2023-01-02 15:56:30", - Watched_Value1="online", - Watched_Value2=None, - Watched_Value3=None, - Watched_Value4=None, + watchedValue1="online", + watchedValue2=None, + watchedValue3=None, + watchedValue4=None, Extra="Additional data", ForeignKey="aa:bb:cc:dd:ee:ff", - HelpVal1=None, - HelpVal2=None, - HelpVal3=None, - HelpVal4=None + helpVal1=None, + helpVal2=None, + helpVal3=None, + helpVal4=None ) # Write results (handles formatting, sanitization, and file creation) @@ -177,7 +177,7 @@ The library automatically: The core runs **de-duplication once per hour** on the `Plugins_Objects` table: -- **Duplicate Detection Key:** Combination of `Object_PrimaryID`, `Object_SecondaryID`, `Plugin` (auto-filled from `unique_prefix`), and `UserData` +- **Duplicate Detection Key:** Combination of `objectPrimaryId`, `objectSecondaryId`, `Plugin` (auto-filled from `unique_prefix`), and `UserData` - **Resolution:** Oldest duplicate entries are removed, newest are kept - **Use Case:** Prevents duplicate notifications when the same object is detected multiple times @@ -213,9 +213,9 @@ Before writing your plugin's `script.py`, ensure: - [ ] **9 or 13 columns** in each output line (8 or 12 pipe separators) - [ ] **Mandatory columns filled:** - - Column 0: `Object_PrimaryID` (not null) + - Column 0: `objectPrimaryId` (not null) - Column 2: `DateTime` in `YYYY-MM-DD HH:MM:SS` format - - Column 3: `Watched_Value1` (not null) + - Column 3: `watchedValue1` (not null) - [ ] **Null values as literal string** `null` (not empty string or special chars) - [ ] **No extra pipes or misaligned columns** - [ ] **If using optional helpers** (columns 9–12), all 4 must be present diff --git a/docs/PLUGINS_DEV_QUICK_START.md b/docs/PLUGINS_DEV_QUICK_START.md index 933b6886..6bac4ae5 100644 --- a/docs/PLUGINS_DEV_QUICK_START.md +++ b/docs/PLUGINS_DEV_QUICK_START.md @@ -68,13 +68,13 @@ try: # Add an object to results plugin_objects.add_object( - Object_PrimaryID="example_id", - Object_SecondaryID=None, + objectPrimaryId="example_id", + objectSecondaryId=None, DateTime="2023-01-02 15:56:30", - Watched_Value1="value1", - Watched_Value2=None, - Watched_Value3=None, - Watched_Value4=None, + watchedValue1="value1", + watchedValue2=None, + watchedValue3=None, + watchedValue4=None, Extra="additional_data", ForeignKey=None ) diff --git a/docs/PLUGINS_DEV_UI_COMPONENTS.md b/docs/PLUGINS_DEV_UI_COMPONENTS.md index 776bd40e..663f52e0 100644 --- a/docs/PLUGINS_DEV_UI_COMPONENTS.md +++ b/docs/PLUGINS_DEV_UI_COMPONENTS.md @@ -16,7 +16,7 @@ Each column definition specifies: ```json { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "mapped_to_column": "devMac", "mapped_to_column_data": null, "css_classes": "col-sm-2", @@ -39,7 +39,7 @@ Each column definition specifies: | Property | Type | Required | Description | |----------|------|----------|-------------| -| `column` | string | **YES** | Source column name from data contract (e.g., `Object_PrimaryID`, `Watched_Value1`) | +| `column` | string | **YES** | Source column name from data contract (e.g., `objectPrimaryId`, `watchedValue1`) | | `mapped_to_column` | string | no | Target database column if mapping to a table like `CurrentScan` | | `mapped_to_column_data` | object | no | Static value to map instead of using column data | | `css_classes` | string | no | Bootstrap CSS classes for width/spacing (e.g., `"col-sm-2"`, `"col-sm-6"`) | @@ -64,7 +64,7 @@ Plain text display (read-only). ```json { - "column": "Watched_Value1", + "column": "watchedValue1", "show": true, "type": "label", "localized": ["name"], @@ -99,7 +99,7 @@ Resolves an IP address to a MAC address and creates a device link. ```json { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "show": true, "type": "device_ip", "localized": ["name"], @@ -117,7 +117,7 @@ Creates a device link with the target device's name as the link label. ```json { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "show": true, "type": "device_name_mac", "localized": ["name"], @@ -135,7 +135,7 @@ Renders as a clickable HTTP/HTTPS link. ```json { - "column": "Watched_Value1", + "column": "watchedValue1", "show": true, "type": "url", "localized": ["name"], @@ -153,7 +153,7 @@ Creates two links (HTTP and HTTPS) as lock icons for the given IP/hostname. ```json { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "show": true, "type": "url_http_https", "localized": ["name"], @@ -207,7 +207,7 @@ Color-codes values based on ranges. Useful for status codes, latency, capacity p ```json { - "column": "Watched_Value1", + "column": "watchedValue1", "show": true, "type": "threshold", "options": [ @@ -252,7 +252,7 @@ Replaces specific values with display strings or HTML. ```json { - "column": "Watched_Value2", + "column": "watchedValue2", "show": true, "type": "replace", "options": [ @@ -286,7 +286,7 @@ Applies a regular expression to extract/transform values. ```json { - "column": "Watched_Value1", + "column": "watchedValue1", "show": true, "type": "regex", "options": [ @@ -310,7 +310,7 @@ Evaluates JavaScript code with access to the column value (use `${value}` or `{v ```json { - "column": "Watched_Value1", + "column": "watchedValue1", "show": true, "type": "eval", "default_value": "", @@ -322,7 +322,7 @@ Evaluates JavaScript code with access to the column value (use `${value}` or `{v **Example with custom formatting:** ```json { - "column": "Watched_Value1", + "column": "watchedValue1", "show": true, "type": "eval", "options": [ @@ -347,7 +347,7 @@ You can chain multiple transformations with dot notation: ```json { - "column": "Watched_Value3", + "column": "watchedValue3", "show": true, "type": "regex.url_http_https", "options": [ @@ -376,7 +376,7 @@ Use SQL query results to populate dropdown options: ```json { - "column": "Watched_Value2", + "column": "watchedValue2", "show": true, "type": "select", "options": ["{value}"], @@ -405,7 +405,7 @@ Use plugin settings to populate options: ```json { - "column": "Watched_Value1", + "column": "watchedValue1", "show": true, "type": "select", "options": ["{value}"], @@ -439,7 +439,7 @@ To import plugin data into the device scan pipeline (for notifications, heuristi "mapped_to_table": "CurrentScan", "database_column_definitions": [ { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "mapped_to_column": "scanMac", "show": true, "type": "device_mac", @@ -447,7 +447,7 @@ To import plugin data into the device scan pipeline (for notifications, heuristi "name": [{"language_code": "en_us", "string": "MAC Address"}] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "mapped_to_column": "scanLastIP", "show": true, "type": "device_ip", @@ -501,7 +501,7 @@ Control which rows are displayed based on filter conditions. Filters are applied { "data_filters": [ { - "compare_column": "Object_PrimaryID", + "compare_column": "objectPrimaryId", "compare_operator": "==", "compare_field_id": "txtMacFilter", "compare_js_template": "'{value}'.toString()", @@ -545,7 +545,7 @@ When viewing a device detail page, the `txtMacFilter` field is populated with th { "database_column_definitions": [ { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "mapped_to_column": "scanMac", "css_classes": "col-sm-2", "show": true, @@ -555,7 +555,7 @@ When viewing a device detail page, the `txtMacFilter` field is populated with th "name": [{"language_code": "en_us", "string": "MAC Address"}] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "mapped_to_column": "scanLastIP", "css_classes": "col-sm-2", "show": true, @@ -574,7 +574,7 @@ When viewing a device detail page, the `txtMacFilter` field is populated with th "name": [{"language_code": "en_us", "string": "Last Seen"}] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "css_classes": "col-sm-2", "show": true, "type": "threshold", @@ -589,7 +589,7 @@ When viewing a device detail page, the `txtMacFilter` field is populated with th "name": [{"language_code": "en_us", "string": "HTTP Status"}] }, { - "column": "Watched_Value2", + "column": "watchedValue2", "css_classes": "col-sm-1", "show": true, "type": "label", diff --git a/front/appEventsCore.php b/front/appEventsCore.php index 22c4ca23..47febbbe 100755 --- a/front/appEventsCore.php +++ b/front/appEventsCore.php @@ -61,16 +61,16 @@ $(document).ready(function () { appEvents(options: $options) { count appEvents { - DateTimeCreated - AppEventProcessed - AppEventType - ObjectType - ObjectPrimaryID - ObjectSecondaryID - ObjectStatus - ObjectPlugin - ObjectGUID - GUID + dateTimeCreated + appEventProcessed + appEventType + objectType + objectPrimaryId + objectSecondaryId + objectStatus + objectPlugin + objectGuid + guid } } } @@ -128,16 +128,16 @@ $(document).ready(function () { }, columns: [ - { data: 'DateTimeCreated', title: getString('AppEvents_DateTimeCreated') }, - { data: 'AppEventProcessed', title: getString('AppEvents_AppEventProcessed') }, - { data: 'AppEventType', title: getString('AppEvents_Type') }, - { data: 'ObjectType', title: getString('AppEvents_ObjectType') }, - { data: 'ObjectPrimaryID', title: getString('AppEvents_ObjectPrimaryID') }, - { data: 'ObjectSecondaryID', title: getString('AppEvents_ObjectSecondaryID') }, - { data: 'ObjectStatus', title: getString('AppEvents_ObjectStatus') }, - { data: 'ObjectPlugin', title: getString('AppEvents_Plugin') }, - { data: 'ObjectGUID', title: 'Object GUID' }, - { data: 'GUID', title: 'Event GUID' } + { data: 'dateTimeCreated', title: getString('AppEvents_DateTimeCreated') }, + { data: 'appEventProcessed', title: getString('AppEvents_AppEventProcessed') }, + { data: 'appEventType', title: getString('AppEvents_Type') }, + { data: 'objectType', title: getString('AppEvents_ObjectType') }, + { data: 'objectPrimaryId', title: getString('AppEvents_ObjectPrimaryID') }, + { data: 'objectSecondaryId', title: getString('AppEvents_ObjectSecondaryID') }, + { data: 'objectStatus', title: getString('AppEvents_ObjectStatus') }, + { data: 'objectPlugin', title: getString('AppEvents_Plugin') }, + { data: 'objectGuid', title: 'Object GUID' }, + { data: 'guid', title: 'Event GUID' } ], columnDefs: [ diff --git a/front/deviceDetailsEvents.php b/front/deviceDetailsEvents.php index a592d8ce..c7b7fdfe 100755 --- a/front/deviceDetailsEvents.php +++ b/front/deviceDetailsEvents.php @@ -38,12 +38,12 @@ function loadEventsData() { let { start, end } = getPeriodStartEnd(period); const rawSql = ` - SELECT eve_DateTime, eve_EventType, eve_IP, eve_AdditionalInfo + SELECT eveDateTime, eveEventType, eveIp, eveAdditionalInfo FROM Events - WHERE eve_MAC = "${mac}" - AND eve_DateTime BETWEEN "${start}" AND "${end}" + WHERE eveMac = "${mac}" + AND eveDateTime BETWEEN "${start}" AND "${end}" AND ( - (eve_EventType NOT IN ("Connected", "Disconnected", "VOIDED - Connected", "VOIDED - Disconnected")) + (eveEventType NOT IN ("Connected", "Disconnected", "VOIDED - Connected", "VOIDED - Disconnected")) OR "${hideConnectionsStr}" = "false" ) `; @@ -66,15 +66,15 @@ function loadEventsData() { success: function (data) { // assuming read_query returns rows directly const rows = data["results"].map(row => { - const rawDate = row.eve_DateTime; + const rawDate = row.eveDateTime; const formattedDate = rawDate ? localizeTimestamp(rawDate) : '-'; return [ formattedDate, - row.eve_DateTime, - row.eve_EventType, - row.eve_IP, - row.eve_AdditionalInfo + row.eveDateTime, + row.eveEventType, + row.eveIp, + row.eveAdditionalInfo ]; }); diff --git a/front/deviceDetailsSessions.php b/front/deviceDetailsSessions.php index ed9a0487..837a8971 100755 --- a/front/deviceDetailsSessions.php +++ b/front/deviceDetailsSessions.php @@ -121,12 +121,12 @@ function loadSessionsData() { if (data.success && data.sessions.length) { data.sessions.forEach(session => { table.row.add([ - session.ses_DateTimeOrder, - session.ses_Connection, - session.ses_Disconnection, - session.ses_Duration, - session.ses_IP, - session.ses_Info + session.sesDateTimeOrder, + session.sesConnection, + session.sesDisconnection, + session.sesDuration, + session.sesIp, + session.sesInfo ]); }); } diff --git a/front/js/app-init.js b/front/js/app-init.js index 5da09560..96bd2f4e 100644 --- a/front/js/app-init.js +++ b/front/js/app-init.js @@ -16,7 +16,7 @@ // ----------------------------------------------------------------------------- var completedCalls = [] -var completedCalls_final = ['cacheApiConfig', 'cacheSettings', 'cacheStrings', 'cacheDevices']; +var completedCalls_final = ['cacheApiConfig', 'cacheSettings', 'cacheStrings_v2', 'cacheDevices']; var lang_completedCalls = 0; diff --git a/front/js/cache.js b/front/js/cache.js index f224f6f2..12c881d3 100644 --- a/front/js/cache.js +++ b/front/js/cache.js @@ -304,7 +304,7 @@ function cacheStrings() { .then((data) => { if (!Array.isArray(data)) { data = []; } data.forEach((langString) => { - setCache(CACHE_KEYS.langString(langString.String_Key, langString.Language_Code), langString.String_Value); + setCache(CACHE_KEYS.langString(langString.stringKey, langString.languageCode), langString.stringValue); }); resolve(); }); @@ -347,11 +347,11 @@ function cacheStrings() { if (!Array.isArray(data)) { data = []; } // Store plugin translations data.forEach((langString) => { - setCache(CACHE_KEYS.langString(langString.String_Key, langString.Language_Code), langString.String_Value); + setCache(CACHE_KEYS.langString(langString.stringKey, langString.languageCode), langString.stringValue); }); // Handle successful completion of language processing - handleSuccess('cacheStrings'); + handleSuccess('cacheStrings_v2'); resolveLang(); }); }) diff --git a/front/multiEditCore.php b/front/multiEditCore.php index accf5e2e..4995460f 100755 --- a/front/multiEditCore.php +++ b/front/multiEditCore.php @@ -2,6 +2,7 @@ //------------------------------------------------------------------------------ // check if authenticated require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php'; + require_once $_SERVER['DOCUMENT_ROOT'] . '/php/server/db.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/language/lang.php'; ?> diff --git a/front/php/components/graph_online_history.php b/front/php/components/graph_online_history.php index 46275870..752143dc 100755 --- a/front/php/components/graph_online_history.php +++ b/front/php/components/graph_online_history.php @@ -28,13 +28,13 @@ function initOnlineHistoryGraph() { res.data.forEach(function(entry) { - var formattedTime = localizeTimestamp(entry.Scan_Date).slice(11, 17); + var formattedTime = localizeTimestamp(entry.scanDate).slice(11, 17); timeStamps.push(formattedTime); - onlineCounts.push(entry.Online_Devices); - downCounts.push(entry.Down_Devices); - offlineCounts.push(entry.Offline_Devices); - archivedCounts.push(entry.Archived_Devices); + onlineCounts.push(entry.onlineDevices); + downCounts.push(entry.downDevices); + offlineCounts.push(entry.offlineDevices); + archivedCounts.push(entry.archivedDevices); }); // Call your presenceOverTime function after data is ready diff --git a/front/php/templates/language/lang.php b/front/php/templates/language/lang.php index 6c4c3dbc..62264c89 100755 --- a/front/php/templates/language/lang.php +++ b/front/php/templates/language/lang.php @@ -24,7 +24,7 @@ $pia_lang_selected = isset($_langMatch[1]) ? strtolower($_langMatch[1]) : $defau $result = $db->query("SELECT * FROM Plugins_Language_Strings"); $strings = array(); while ($row = $result->fetchArray(SQLITE3_ASSOC)) { - $strings[$row['String_Key']] = $row['String_Value']; + $strings[$row['stringKey']] = $row['stringValue']; } diff --git a/front/plugins/__template/config.json b/front/plugins/__template/config.json index 12617d70..620aa408 100755 --- a/front/plugins/__template/config.json +++ b/front/plugins/__template/config.json @@ -8,7 +8,7 @@ "mapped_to_table": "CurrentScan", "data_filters": [ { - "compare_column": "Object_PrimaryID", + "compare_column": "objectPrimaryId", "compare_operator": "==", "compare_field_id": "txtMacFilter", "compare_js_template": "'{value}'.toString()", @@ -403,7 +403,7 @@ ], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -418,7 +418,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "mapped_to_column": "scanMac", "css_classes": "col-sm-3", "show": true, @@ -434,7 +434,7 @@ ] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "mapped_to_column": "scanLastIP", "css_classes": "col-sm-2", "show": true, @@ -450,7 +450,7 @@ ] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "mapped_to_column": "scanName", "css_classes": "col-sm-2", "show": true, @@ -466,7 +466,7 @@ ] }, { - "column": "Watched_Value2", + "column": "watchedValue2", "mapped_to_column": "scanVendor", "css_classes": "col-sm-2", "show": true, @@ -482,7 +482,7 @@ ] }, { - "column": "Watched_Value3", + "column": "watchedValue3", "mapped_to_column": "scanType", "css_classes": "col-sm-2", "show": true, @@ -498,7 +498,7 @@ ] }, { - "column": "Watched_Value4", + "column": "watchedValue4", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -532,7 +532,7 @@ ] }, { - "column": "DateTimeCreated", + "column": "dateTimeCreated", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -547,7 +547,7 @@ ] }, { - "column": "DateTimeChanged", + "column": "dateTimeChanged", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -562,7 +562,7 @@ ] }, { - "column": "Status", + "column": "status", "css_classes": "col-sm-1", "show": true, "type": "replace", diff --git a/front/plugins/__template/rename_me.py b/front/plugins/__template/rename_me.py index b9f43e56..95cd1f62 100755 --- a/front/plugins/__template/rename_me.py +++ b/front/plugins/__template/rename_me.py @@ -50,7 +50,7 @@ def main(): # make sure the below mapping is mapped in config.json, for example: # "database_column_definitions": [ # { - # "column": "Object_PrimaryID", <--------- the value I save into primaryId + # "column": "objectPrimaryId", <--------- the value I save into primaryId # "mapped_to_column": "scanMac", <--------- gets inserted into the CurrentScan DB # table column scanMac # diff --git a/front/plugins/_publisher_apprise/config.json b/front/plugins/_publisher_apprise/config.json index 8980cd86..2dfc1520 100755 --- a/front/plugins/_publisher_apprise/config.json +++ b/front/plugins/_publisher_apprise/config.json @@ -31,7 +31,7 @@ "params": [], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -46,7 +46,7 @@ ] }, { - "column": "Plugin", + "column": "plugin", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -65,7 +65,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "css_classes": "col-sm-2", "show": false, "type": "url", @@ -80,7 +80,7 @@ ] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -99,7 +99,7 @@ ] }, { - "column": "DateTimeCreated", + "column": "dateTimeCreated", "css_classes": "col-sm-3", "show": true, "type": "label", @@ -114,7 +114,7 @@ ] }, { - "column": "DateTimeChanged", + "column": "dateTimeChanged", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -133,7 +133,7 @@ ] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "css_classes": "col-sm-1", "show": true, "type": "eval", @@ -153,7 +153,7 @@ ] }, { - "column": "Watched_Value2", + "column": "watchedValue2", "css_classes": "col-sm-8", "show": true, "type": "textarea_readonly", @@ -168,7 +168,7 @@ ] }, { - "column": "Watched_Value3", + "column": "watchedValue3", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -187,7 +187,7 @@ ] }, { - "column": "Watched_Value4", + "column": "watchedValue4", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -206,7 +206,7 @@ ] }, { - "column": "UserData", + "column": "userData", "css_classes": "col-sm-2", "show": false, "type": "textbox_save", @@ -225,7 +225,7 @@ ] }, { - "column": "Status", + "column": "status", "css_classes": "col-sm-1", "show": false, "type": "replace", @@ -261,7 +261,7 @@ ] }, { - "column": "Extra", + "column": "extra", "css_classes": "col-sm-3", "show": false, "type": "label", diff --git a/front/plugins/_publisher_email/config.json b/front/plugins/_publisher_email/config.json index f86c4ac5..a1b2de1b 100755 --- a/front/plugins/_publisher_email/config.json +++ b/front/plugins/_publisher_email/config.json @@ -31,7 +31,7 @@ "params": [], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -46,7 +46,7 @@ ] }, { - "column": "Plugin", + "column": "plugin", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -65,7 +65,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "css_classes": "col-sm-2", "show": false, "type": "url", @@ -80,7 +80,7 @@ ] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -99,7 +99,7 @@ ] }, { - "column": "DateTimeCreated", + "column": "dateTimeCreated", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -114,7 +114,7 @@ ] }, { - "column": "DateTimeChanged", + "column": "dateTimeChanged", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -133,7 +133,7 @@ ] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "css_classes": "col-sm-1", "show": true, "type": "eval", @@ -153,7 +153,7 @@ ] }, { - "column": "Watched_Value2", + "column": "watchedValue2", "css_classes": "col-sm-8", "show": true, "type": "textarea_readonly", @@ -168,7 +168,7 @@ ] }, { - "column": "Watched_Value3", + "column": "watchedValue3", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -187,7 +187,7 @@ ] }, { - "column": "Watched_Value4", + "column": "watchedValue4", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -206,7 +206,7 @@ ] }, { - "column": "UserData", + "column": "userData", "css_classes": "col-sm-2", "show": false, "type": "textbox_save", @@ -225,7 +225,7 @@ ] }, { - "column": "Status", + "column": "status", "css_classes": "col-sm-1", "show": false, "type": "replace", @@ -261,7 +261,7 @@ ] }, { - "column": "Extra", + "column": "extra", "css_classes": "col-sm-3", "show": false, "type": "label", diff --git a/front/plugins/_publisher_mqtt/config.json b/front/plugins/_publisher_mqtt/config.json index 2b1316ea..105041f1 100755 --- a/front/plugins/_publisher_mqtt/config.json +++ b/front/plugins/_publisher_mqtt/config.json @@ -7,7 +7,7 @@ "show_ui": true, "data_filters": [ { - "compare_column": "Watched_Value4", + "compare_column": "watchedValue4", "compare_operator": "==", "compare_field_id": "txtMacFilter", "compare_js_template": "'{value}'.toString()", @@ -47,7 +47,7 @@ ], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -62,7 +62,7 @@ ] }, { - "column": "Plugin", + "column": "plugin", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -81,7 +81,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -96,7 +96,7 @@ ] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -111,7 +111,7 @@ ] }, { - "column": "DateTimeCreated", + "column": "dateTimeCreated", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -126,7 +126,7 @@ ] }, { - "column": "DateTimeChanged", + "column": "dateTimeChanged", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -145,7 +145,7 @@ ] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "css_classes": "col-sm-3", "show": false, "type": "label", @@ -160,7 +160,7 @@ ] }, { - "column": "Watched_Value2", + "column": "watchedValue2", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -175,7 +175,7 @@ ] }, { - "column": "Watched_Value3", + "column": "watchedValue3", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -190,7 +190,7 @@ ] }, { - "column": "Watched_Value4", + "column": "watchedValue4", "css_classes": "col-sm-2", "show": true, "type": "device_name_mac", @@ -205,7 +205,7 @@ ] }, { - "column": "UserData", + "column": "userData", "css_classes": "col-sm-2", "show": false, "type": "textbox_save", @@ -224,7 +224,7 @@ ] }, { - "column": "Status", + "column": "status", "css_classes": "col-sm-1", "show": true, "type": "replace", @@ -260,7 +260,7 @@ ] }, { - "column": "Extra", + "column": "extra", "css_classes": "col-sm-3", "show": false, "type": "label", diff --git a/front/plugins/_publisher_mqtt/mqtt.py b/front/plugins/_publisher_mqtt/mqtt.py index 8f5e88b0..7727ca4f 100755 --- a/front/plugins/_publisher_mqtt/mqtt.py +++ b/front/plugins/_publisher_mqtt/mqtt.py @@ -212,14 +212,14 @@ class sensor_config: already known. If not, it marks the sensor as new and logs relevant information. """ # Retrieve the plugin object based on the sensor's hash - plugObj = getPluginObject({"Plugin": "MQTT", "Watched_Value3": self.hash}) + plugObj = getPluginObject({"plugin": "MQTT", "watchedValue3": self.hash}) # Check if the plugin object is new if not plugObj: self.isNew = True mylog('verbose', [f"[{pluginName}] New sensor entry (name|mac|hash) : ({self.deviceName}|{self.mac}|{self.hash}"]) else: - device_name = plugObj.get("Watched_Value1", "Unknown") + device_name = plugObj.get("watchedValue1", "Unknown") mylog('verbose', [f"[{pluginName}] Existing, skip Device Name: {device_name}"]) self.isNew = False diff --git a/front/plugins/_publisher_ntfy/config.json b/front/plugins/_publisher_ntfy/config.json index 7afb93e6..16a37048 100755 --- a/front/plugins/_publisher_ntfy/config.json +++ b/front/plugins/_publisher_ntfy/config.json @@ -31,7 +31,7 @@ "params": [], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -46,7 +46,7 @@ ] }, { - "column": "Plugin", + "column": "plugin", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -65,7 +65,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -80,7 +80,7 @@ ] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -95,7 +95,7 @@ ] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "css_classes": "col-sm-1", "show": true, "type": "eval", @@ -115,7 +115,7 @@ ] }, { - "column": "Watched_Value2", + "column": "watchedValue2", "css_classes": "col-sm-2", "show": true, "type": "textarea_readonly", @@ -130,7 +130,7 @@ ] }, { - "column": "Watched_Value3", + "column": "watchedValue3", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -145,7 +145,7 @@ ] }, { - "column": "Watched_Value4", + "column": "watchedValue4", "css_classes": "col-sm-2", "show": false, "type": "device_mac", @@ -160,7 +160,7 @@ ] }, { - "column": "UserData", + "column": "userData", "css_classes": "col-sm-2", "show": false, "type": "textbox_save", @@ -179,7 +179,7 @@ ] }, { - "column": "Status", + "column": "status", "css_classes": "col-sm-1", "show": false, "type": "replace", @@ -215,7 +215,7 @@ ] }, { - "column": "Extra", + "column": "extra", "css_classes": "col-sm-3", "show": false, "type": "label", diff --git a/front/plugins/_publisher_pushover/config.json b/front/plugins/_publisher_pushover/config.json index 7fbc2b2a..aed7d530 100755 --- a/front/plugins/_publisher_pushover/config.json +++ b/front/plugins/_publisher_pushover/config.json @@ -31,7 +31,7 @@ "params": [], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -46,7 +46,7 @@ ] }, { - "column": "Plugin", + "column": "plugin", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -65,7 +65,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -80,7 +80,7 @@ ] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -95,7 +95,7 @@ ] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "css_classes": "col-sm-1", "show": true, "type": "eval", @@ -115,7 +115,7 @@ ] }, { - "column": "Watched_Value2", + "column": "watchedValue2", "css_classes": "col-sm-2", "show": true, "type": "textarea_readonly", @@ -130,7 +130,7 @@ ] }, { - "column": "Watched_Value3", + "column": "watchedValue3", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -145,7 +145,7 @@ ] }, { - "column": "Watched_Value4", + "column": "watchedValue4", "css_classes": "col-sm-2", "show": false, "type": "device_mac", @@ -160,7 +160,7 @@ ] }, { - "column": "UserData", + "column": "userData", "css_classes": "col-sm-2", "show": false, "type": "textbox_save", @@ -179,7 +179,7 @@ ] }, { - "column": "Status", + "column": "status", "css_classes": "col-sm-1", "show": false, "type": "replace", @@ -215,7 +215,7 @@ ] }, { - "column": "Extra", + "column": "extra", "css_classes": "col-sm-3", "show": false, "type": "label", diff --git a/front/plugins/_publisher_pushsafer/config.json b/front/plugins/_publisher_pushsafer/config.json index a7826942..40b6541a 100755 --- a/front/plugins/_publisher_pushsafer/config.json +++ b/front/plugins/_publisher_pushsafer/config.json @@ -31,7 +31,7 @@ "params": [], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -46,7 +46,7 @@ ] }, { - "column": "Plugin", + "column": "plugin", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -65,7 +65,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -80,7 +80,7 @@ ] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -95,7 +95,7 @@ ] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "css_classes": "col-sm-1", "show": true, "type": "eval", @@ -115,7 +115,7 @@ ] }, { - "column": "Watched_Value2", + "column": "watchedValue2", "css_classes": "col-sm-2", "show": true, "type": "textarea_readonly", @@ -130,7 +130,7 @@ ] }, { - "column": "Watched_Value3", + "column": "watchedValue3", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -145,7 +145,7 @@ ] }, { - "column": "Watched_Value4", + "column": "watchedValue4", "css_classes": "col-sm-2", "show": false, "type": "device_mac", @@ -160,7 +160,7 @@ ] }, { - "column": "UserData", + "column": "userData", "css_classes": "col-sm-2", "show": false, "type": "textbox_save", @@ -179,7 +179,7 @@ ] }, { - "column": "Status", + "column": "status", "css_classes": "col-sm-1", "show": false, "type": "replace", @@ -215,7 +215,7 @@ ] }, { - "column": "Extra", + "column": "extra", "css_classes": "col-sm-3", "show": false, "type": "label", diff --git a/front/plugins/_publisher_telegram/config.json b/front/plugins/_publisher_telegram/config.json index 9b6cbb65..7786acd1 100755 --- a/front/plugins/_publisher_telegram/config.json +++ b/front/plugins/_publisher_telegram/config.json @@ -27,7 +27,7 @@ "params": [], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -42,7 +42,7 @@ ] }, { - "column": "Plugin", + "column": "plugin", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -61,7 +61,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "css_classes": "col-sm-2", "show": false, "type": "url", @@ -76,7 +76,7 @@ ] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -95,7 +95,7 @@ ] }, { - "column": "DateTimeCreated", + "column": "dateTimeCreated", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -110,7 +110,7 @@ ] }, { - "column": "DateTimeChanged", + "column": "dateTimeChanged", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -129,7 +129,7 @@ ] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "css_classes": "col-sm-1", "show": true, "type": "eval", @@ -149,7 +149,7 @@ ] }, { - "column": "Watched_Value2", + "column": "watchedValue2", "css_classes": "col-sm-8", "show": true, "type": "textarea_readonly", @@ -164,7 +164,7 @@ ] }, { - "column": "Watched_Value3", + "column": "watchedValue3", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -183,7 +183,7 @@ ] }, { - "column": "Watched_Value4", + "column": "watchedValue4", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -202,7 +202,7 @@ ] }, { - "column": "UserData", + "column": "userData", "css_classes": "col-sm-2", "show": false, "type": "textbox_save", @@ -221,7 +221,7 @@ ] }, { - "column": "Status", + "column": "status", "css_classes": "col-sm-1", "show": false, "type": "replace", @@ -257,7 +257,7 @@ ] }, { - "column": "Extra", + "column": "extra", "css_classes": "col-sm-3", "show": false, "type": "label", diff --git a/front/plugins/_publisher_webhook/config.json b/front/plugins/_publisher_webhook/config.json index 65a3481c..069e39f6 100755 --- a/front/plugins/_publisher_webhook/config.json +++ b/front/plugins/_publisher_webhook/config.json @@ -31,7 +31,7 @@ "params": [], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -46,7 +46,7 @@ ] }, { - "column": "Plugin", + "column": "plugin", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -65,7 +65,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -80,7 +80,7 @@ ] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -95,7 +95,7 @@ ] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "css_classes": "col-sm-1", "show": true, "type": "eval", @@ -115,7 +115,7 @@ ] }, { - "column": "Watched_Value2", + "column": "watchedValue2", "css_classes": "col-sm-3", "show": true, "type": "textarea_readonly", @@ -130,7 +130,7 @@ ] }, { - "column": "Watched_Value3", + "column": "watchedValue3", "css_classes": "col-sm-3", "show": true, "type": "textarea_readonly", @@ -145,7 +145,7 @@ ] }, { - "column": "Watched_Value4", + "column": "watchedValue4", "css_classes": "col-sm-2", "show": false, "type": "device_mac", @@ -160,7 +160,7 @@ ] }, { - "column": "UserData", + "column": "userData", "css_classes": "col-sm-2", "show": false, "type": "textbox_save", @@ -179,7 +179,7 @@ ] }, { - "column": "Status", + "column": "status", "css_classes": "col-sm-1", "show": false, "type": "replace", @@ -215,7 +215,7 @@ ] }, { - "column": "Extra", + "column": "extra", "css_classes": "col-sm-3", "show": false, "type": "label", diff --git a/front/plugins/adguard_import/config.json b/front/plugins/adguard_import/config.json index f4df3377..411230e0 100644 --- a/front/plugins/adguard_import/config.json +++ b/front/plugins/adguard_import/config.json @@ -8,7 +8,7 @@ "mapped_to_table": "CurrentScan", "data_filters": [ { - "compare_column": "Object_PrimaryID", + "compare_column": "objectPrimaryId", "compare_operator": "==", "compare_field_id": "txtMacFilter", "compare_js_template": "'{value}'.toString()", @@ -378,7 +378,7 @@ ], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -393,7 +393,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "mapped_to_column": "scanMac", "css_classes": "col-sm-3", "show": true, @@ -409,7 +409,7 @@ ] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "mapped_to_column": "scanLastIP", "css_classes": "col-sm-2", "show": true, @@ -425,7 +425,7 @@ ] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "mapped_to_column": "scanName", "css_classes": "col-sm-2", "show": true, @@ -441,7 +441,7 @@ ] }, { - "column": "Watched_Value2", + "column": "watchedValue2", "mapped_to_column": "scanType", "css_classes": "col-sm-2", "show": true, @@ -457,7 +457,7 @@ ] }, { - "column": "Watched_Value3", + "column": "watchedValue3", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -472,7 +472,7 @@ ] }, { - "column": "Watched_Value4", + "column": "watchedValue4", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -506,7 +506,7 @@ ] }, { - "column": "DateTimeCreated", + "column": "dateTimeCreated", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -521,7 +521,7 @@ ] }, { - "column": "DateTimeChanged", + "column": "dateTimeChanged", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -536,7 +536,7 @@ ] }, { - "column": "Status", + "column": "status", "css_classes": "col-sm-1", "show": true, "type": "replace", diff --git a/front/plugins/arp_scan/config.json b/front/plugins/arp_scan/config.json index 98beb02e..973927c4 100755 --- a/front/plugins/arp_scan/config.json +++ b/front/plugins/arp_scan/config.json @@ -8,7 +8,7 @@ "mapped_to_table": "CurrentScan", "data_filters": [ { - "compare_column": "Object_PrimaryID", + "compare_column": "objectPrimaryId", "compare_operator": "==", "compare_field_id": "txtMacFilter", "compare_js_template": "'{value}'.toString()", @@ -343,12 +343,12 @@ } ] }, - "default_value": ["Watched_Value1", "Watched_Value2"], + "default_value": ["watchedValue1", "watchedValue2"], "options": [ - "Watched_Value1", - "Watched_Value2", - "Watched_Value3", - "Watched_Value4" + "watchedValue1", + "watchedValue2", + "watchedValue3", + "watchedValue4" ], "localized": ["name", "description"], "name": [ @@ -368,15 +368,15 @@ "description": [ { "language_code": "en_us", - "string": "Send a notification if selected values change. Use CTRL + Click to select/deselect.
  • Watched_Value1 is IP
  • Watched_Value2 is Vendor
  • Watched_Value3 is Interface
  • Watched_Value4 is N/A
" + "string": "Send a notification if selected values change. Use CTRL + Click to select/deselect.
  • watchedValue1 is IP
  • watchedValue2 is Vendor
  • watchedValue3 is Interface
  • watchedValue4 is N/A
" }, { "language_code": "es_es", - "string": "Envía una notificación si los valores seleccionados cambian. Utilice CTRL + clic para seleccionar/deseleccionar.
  • Valor_observado1 es IP
  • Valor_observado2 es Proveedor
  • Valor_observado3 es Interfaz
  • Valor_observado4 es N/A
" + "string": "Envía una notificación si los valores seleccionados cambian. Utilice CTRL + clic para seleccionar/deseleccionar.
  • watchedValue1 es IP
  • watchedValue2 es Proveedor
  • watchedValue3 es Interfaz
  • watchedValue4 es N/A
" }, { "language_code": "de_de", - "string": "Sende eine Benachrichtigung, wenn ein ausgwählter Wert sich ändert. STRG + klicken zum aus-/abwählen.
  • Watched_Value1 ist die IP
  • Watched_Value2 ist der Hersteller
  • Watched_Value3 ist das Interface
  • Watched_Value4 ist nicht in Verwendung
" + "string": "Sende eine Benachrichtigung, wenn ein ausgwählter Wert sich ändert. STRG + klicken zum aus-/abwählen.
  • watchedValue1 ist die IP
  • watchedValue2 ist der Hersteller
  • watchedValue3 ist das Interface
  • watchedValue4 ist nicht in Verwendung
" } ] }, @@ -484,7 +484,7 @@ ], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -499,7 +499,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "mapped_to_column": "scanMac", "css_classes": "col-sm-3", "show": true, @@ -515,7 +515,7 @@ ] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "mapped_to_column": "scanLastIP", "css_classes": "col-sm-2", "show": true, @@ -531,7 +531,7 @@ ] }, { - "column": "Watched_Value2", + "column": "watchedValue2", "mapped_to_column": "scanVendor", "css_classes": "col-sm-2", "show": true, @@ -582,7 +582,7 @@ ] }, { - "column": "DateTimeCreated", + "column": "dateTimeCreated", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -605,7 +605,7 @@ ] }, { - "column": "DateTimeChanged", + "column": "dateTimeChanged", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -628,7 +628,7 @@ ] }, { - "column": "Status", + "column": "status", "css_classes": "col-sm-1", "show": true, "type": "replace", diff --git a/front/plugins/asuswrt_import/config.json b/front/plugins/asuswrt_import/config.json index 0135ab20..eac84984 100755 --- a/front/plugins/asuswrt_import/config.json +++ b/front/plugins/asuswrt_import/config.json @@ -9,7 +9,7 @@ "mapped_to_table": "CurrentScan", "data_filters": [ { - "compare_column": "Object_PrimaryID", + "compare_column": "objectPrimaryId", "compare_operator": "==", "compare_field_id": "txtMacFilter", "compare_js_template": "'{value}'.toString()", @@ -431,7 +431,7 @@ ], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -448,7 +448,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "css_classes": "col-sm-2", "default_value": "", "localized": [ @@ -474,7 +474,7 @@ "type": "device_mac" }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "css_classes": "col-sm-2", "default_value": "", "localized": [ @@ -500,7 +500,7 @@ "type": "device_ip" }, { - "column": "Watched_Value1", + "column": "watchedValue1", "css_classes": "col-sm-2", "default_value": "", "localized": [ @@ -526,7 +526,7 @@ "type": "label" }, { - "column": "Watched_Value2", + "column": "watchedValue2", "mapped_to_column": "scanVendor", "css_classes": "col-sm-2", "default_value": "", @@ -573,7 +573,7 @@ ] }, { - "column": "DateTimeCreated", + "column": "dateTimeCreated", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -590,7 +590,7 @@ ] }, { - "column": "DateTimeChanged", + "column": "dateTimeChanged", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -607,7 +607,7 @@ ] }, { - "column": "Status", + "column": "status", "css_classes": "col-sm-1", "show": true, "type": "replace", diff --git a/front/plugins/avahi_scan/config.json b/front/plugins/avahi_scan/config.json index d588e64d..d6650b98 100755 --- a/front/plugins/avahi_scan/config.json +++ b/front/plugins/avahi_scan/config.json @@ -8,7 +8,7 @@ "show_ui": true, "data_filters": [ { - "compare_column": "Object_PrimaryID", + "compare_column": "objectPrimaryId", "compare_operator": "==", "compare_field_id": "txtMacFilter", "compare_js_template": "'{value}'.toString()", @@ -291,7 +291,7 @@ ], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -306,7 +306,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "css_classes": "col-sm-2", "show": true, "type": "device_name_mac", @@ -325,7 +325,7 @@ ] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -344,7 +344,7 @@ ] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -359,7 +359,7 @@ ] }, { - "column": "Watched_Value2", + "column": "watchedValue2", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -374,7 +374,7 @@ ] }, { - "column": "DateTimeCreated", + "column": "dateTimeCreated", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -389,7 +389,7 @@ ] }, { - "column": "DateTimeChanged", + "column": "dateTimeChanged", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -404,7 +404,7 @@ ] }, { - "column": "Status", + "column": "status", "css_classes": "col-sm-1", "show": true, "type": "replace", diff --git a/front/plugins/db_cleanup/script.py b/front/plugins/db_cleanup/script.py index 4d1c74e2..a6966e2c 100755 --- a/front/plugins/db_cleanup/script.py +++ b/front/plugins/db_cleanup/script.py @@ -82,16 +82,16 @@ def cleanup_database( # Cleanup Online History mylog("verbose", [f"[{pluginName}] Online_History: Delete all but keep latest 150 entries"]) cursor.execute( - """DELETE from Online_History where "Index" not in ( - SELECT "Index" from Online_History - order by Scan_Date desc limit 150)""" + """DELETE from Online_History where "index" not in ( + SELECT "index" from Online_History + order by scanDate desc limit 150)""" ) mylog("verbose", [f"[{pluginName}] Online_History deleted rows: {cursor.rowcount}"]) # ----------------------------------------------------- # Cleanup Events mylog("verbose", f"[{pluginName}] Events: Delete all older than {str(DAYS_TO_KEEP_EVENTS)} days (DAYS_TO_KEEP_EVENTS setting)") - sql = f"""DELETE FROM Events WHERE eve_DateTime <= date('now', '-{str(DAYS_TO_KEEP_EVENTS)} day')""" + sql = f"""DELETE FROM Events WHERE eveDateTime <= date('now', '-{str(DAYS_TO_KEEP_EVENTS)} day')""" mylog("verbose", [f"[{pluginName}] SQL : {sql}"]) cursor.execute(sql) mylog("verbose", [f"[{pluginName}] Events deleted rows: {cursor.rowcount}"]) @@ -100,7 +100,7 @@ def cleanup_database( # Sessions (derived snapshot — trimmed to the same window as Events so the # two tables stay in sync without introducing a separate setting) mylog("verbose", f"[{pluginName}] Sessions: Delete all older than {str(DAYS_TO_KEEP_EVENTS)} days (reuses DAYS_TO_KEEP_EVENTS)") - sql = f"""DELETE FROM Sessions WHERE ses_DateTimeConnection <= date('now', '-{str(DAYS_TO_KEEP_EVENTS)} day')""" + sql = f"""DELETE FROM Sessions WHERE sesDateTimeConnection <= date('now', '-{str(DAYS_TO_KEEP_EVENTS)} day')""" mylog("verbose", [f"[{pluginName}] SQL : {sql}"]) cursor.execute(sql) mylog("verbose", [f"[{pluginName}] Sessions deleted rows: {cursor.rowcount}"]) @@ -113,7 +113,7 @@ def cleanup_database( SELECT "Index" FROM ( SELECT "Index", - ROW_NUMBER() OVER(PARTITION BY "Plugin" ORDER BY DateTimeChanged DESC) AS row_num + ROW_NUMBER() OVER(PARTITION BY plugin ORDER BY dateTimeChanged DESC) AS row_num FROM Plugins_History ) AS ranked_objects WHERE row_num <= {str(PLUGINS_KEEP_HIST)} @@ -130,7 +130,7 @@ def cleanup_database( SELECT "Index" FROM ( SELECT "Index", - ROW_NUMBER() OVER(PARTITION BY "Notifications" ORDER BY DateTimeCreated DESC) AS row_num + ROW_NUMBER() OVER(PARTITION BY "index" ORDER BY dateTimeCreated DESC) AS row_num FROM Notifications ) AS ranked_objects WHERE row_num <= {histCount} @@ -147,7 +147,7 @@ def cleanup_database( SELECT "Index" FROM ( SELECT "Index", - ROW_NUMBER() OVER(PARTITION BY "AppEvents" ORDER BY DateTimeCreated DESC) AS row_num + ROW_NUMBER() OVER(PARTITION BY "index" ORDER BY dateTimeCreated DESC) AS row_num FROM AppEvents ) AS ranked_objects WHERE row_num <= {histCount} @@ -192,10 +192,10 @@ def cleanup_database( DELETE FROM Plugins_Objects WHERE rowid > ( SELECT MIN(rowid) FROM Plugins_Objects p2 - WHERE Plugins_Objects.Plugin = p2.Plugin - AND Plugins_Objects.Object_PrimaryID = p2.Object_PrimaryID - AND Plugins_Objects.Object_SecondaryID = p2.Object_SecondaryID - AND Plugins_Objects.UserData = p2.UserData + WHERE Plugins_Objects.plugin = p2.plugin + AND Plugins_Objects.objectPrimaryId = p2.objectPrimaryId + AND Plugins_Objects.objectSecondaryId = p2.objectSecondaryId + AND Plugins_Objects.userData = p2.userData ) """ ) diff --git a/front/plugins/ddns_update/config.json b/front/plugins/ddns_update/config.json index eec75e92..2337f809 100755 --- a/front/plugins/ddns_update/config.json +++ b/front/plugins/ddns_update/config.json @@ -5,7 +5,7 @@ "enabled": true, "data_filters": [ { - "compare_column": "Object_PrimaryID", + "compare_column": "objectPrimaryId", "compare_operator": "==", "compare_field_id": "txtMacFilter", "compare_js_template": "'{value}'.toString()", @@ -437,12 +437,12 @@ } ] }, - "default_value": ["Watched_Value1"], + "default_value": ["watchedValue1"], "options": [ - "Watched_Value1", - "Watched_Value2", - "Watched_Value3", - "Watched_Value4" + "watchedValue1", + "watchedValue2", + "watchedValue3", + "watchedValue4" ], "localized": ["name", "description"], "name": [ @@ -462,11 +462,11 @@ "description": [ { "language_code": "en_us", - "string": "Send a notification if selected values change. Use CTRL + Click to select/deselect.
  • Watched_Value1 is Previous IP (not recommended)
  • Watched_Value2 unused
  • Watched_Value3 unused
  • Watched_Value4 unused
" + "string": "Send a notification if selected values change. Use CTRL + Click to select/deselect.
  • watchedValue1 is Previous IP (not recommended)
  • watchedValue2 unused
  • watchedValue3 unused
  • watchedValue4 unused
" }, { "language_code": "de_de", - "string": "Sende eine Benachrichtigung, wenn ein ausgwählter Wert sich ändert. STRG + klicken zum aus-/abwählen.
  • Watched_Value1 ist die Vorige IP (nicht empfohlen)
  • Watched_Value2 ist nicht in Verwendung
  • Watched_Value3 ist nicht in Verwendung
  • Watched_Value4 ist nicht in Verwendung
" + "string": "Sende eine Benachrichtigung, wenn ein ausgwählter Wert sich ändert. STRG + klicken zum aus-/abwählen.
  • watchedValue1 ist die Vorige IP (nicht empfohlen)
  • watchedValue2 ist nicht in Verwendung
  • watchedValue3 ist nicht in Verwendung
  • watchedValue4 ist nicht in Verwendung
" } ] }, @@ -507,22 +507,22 @@ "description": [ { "language_code": "en_us", - "string": "Send a notification only on these statuses. new means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. watched-changed means that selected Watched_ValueN columns changed." + "string": "Send a notification only on these statuses. new means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. watched-changed means that selected watchedValueN columns changed." }, { "language_code": "es_es", - "string": "Envíe una notificación solo en estos estados. new significa que se descubrió un nuevo objeto único (una combinación única de PrimaryId y SecondaryId). watched-changed significa que las columnas Watched_ValueN seleccionadas cambiaron." + "string": "Envíe una notificación solo en estos estados. new significa que se descubrió un nuevo objeto único (una combinación única de PrimaryId y SecondaryId). watched-changed significa que las columnas watchedValueN seleccionadas cambiaron." }, { "language_code": "de_de", - "string": "Benachrichtige nur bei diesen Status. new bedeutet ein neues eindeutiges (einzigartige Kombination aus PrimaryId und SecondaryId) Objekt wurde erkennt. watched-changed bedeutet eine ausgewählte Watched_ValueN-Spalte hat sich geändert." + "string": "Benachrichtige nur bei diesen Status. new bedeutet ein neues eindeutiges (einzigartige Kombination aus PrimaryId und SecondaryId) Objekt wurde erkennt. watched-changed bedeutet eine ausgewählte watchedValueN-Spalte hat sich geändert." } ] } ], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -537,7 +537,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "css_classes": "col-sm-2", "show": true, "type": "device_name_mac", @@ -560,7 +560,7 @@ ] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "css_classes": "col-sm-2", "show": true, "type": "device_ip", @@ -583,7 +583,7 @@ ] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -628,7 +628,7 @@ ] }, { - "column": "DateTimeCreated", + "column": "dateTimeCreated", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -651,7 +651,7 @@ ] }, { - "column": "DateTimeChanged", + "column": "dateTimeChanged", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -674,7 +674,7 @@ ] }, { - "column": "Status", + "column": "status", "css_classes": "col-sm-1", "show": true, "type": "replace", diff --git a/front/plugins/dhcp_leases/ASUS_ROUTERS.md b/front/plugins/dhcp_leases/ASUS_ROUTERS.md index 6fc257aa..4ad0d7da 100755 --- a/front/plugins/dhcp_leases/ASUS_ROUTERS.md +++ b/front/plugins/dhcp_leases/ASUS_ROUTERS.md @@ -78,7 +78,7 @@ volumes: 10. Load the `DHCPLSS` plugin and add the search path: `/etc/dnsmasq/dnsmasq.leases` -Configure the plugin, and save everything. You can trigger a manual run. +Configure the plugin, and save everything. You can trigger a manual run. > [!NOTE] > DHCP leases don't allow for realtime tracking and the freshness of the data depends on the DHCP leasing time (usually set to 1 or 24h, or 3600 to 86400 seconds). @@ -93,8 +93,8 @@ DHCPLSS_CMD: 'python3 /app/front/plugins/dhcp_leases/script.py paths={paths}' DHCPLSS_paths_to_check: ['/etc/dnsmasq/dnsmasq.leases'] DHCPLSS_RUN_SCHD: '*/5 * * * *' DHCPLSS_TUN_TIMEOUT: 5 -DHCPLSS_WATCH: ['Watched_Value1', 'Watched_Value4'] -DHCPLSS_REPORT_ON: ['new', 'watched_changed'] +DHCPLSS_WATCH: ['watchedValue1', 'watchedValue4'] +DHCPLSS_REPORT_ON: ['new', 'watched-changed'] ``` You can check the the `dnsmasq.leases` file in the container by running `ls /etc/dnsmasq/`: diff --git a/front/plugins/dhcp_leases/config.json b/front/plugins/dhcp_leases/config.json index 70adb612..b760f8d3 100755 --- a/front/plugins/dhcp_leases/config.json +++ b/front/plugins/dhcp_leases/config.json @@ -7,7 +7,7 @@ "data_source": "script", "data_filters": [ { - "compare_column": "Object_PrimaryID", + "compare_column": "objectPrimaryId", "compare_operator": "==", "compare_field_id": "txtMacFilter", "compare_js_template": "'{value}'.toString()", @@ -64,7 +64,7 @@ ], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -79,7 +79,7 @@ ] }, { - "column": "Plugin", + "column": "plugin", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -102,7 +102,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "mapped_to_column": "scanMac", "css_classes": "col-sm-2", "show": true, @@ -126,7 +126,7 @@ ] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "mapped_to_column": "scanLastIP", "css_classes": "col-sm-2", "show": true, @@ -150,7 +150,7 @@ ] }, { - "column": "DateTimeCreated", + "column": "dateTimeCreated", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -173,7 +173,7 @@ ] }, { - "column": "DateTimeChanged", + "column": "dateTimeChanged", "mapped_to_column": "scanLastConnection", "css_classes": "col-sm-2", "show": true, @@ -197,7 +197,7 @@ ] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -220,7 +220,7 @@ ] }, { - "column": "Watched_Value2", + "column": "watchedValue2", "mapped_to_column": "scanName", "css_classes": "col-sm-2", "show": true, @@ -244,7 +244,7 @@ ] }, { - "column": "Watched_Value3", + "column": "watchedValue3", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -267,7 +267,7 @@ ] }, { - "column": "Watched_Value4", + "column": "watchedValue4", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -290,7 +290,7 @@ ] }, { - "column": "UserData", + "column": "userData", "css_classes": "col-sm-2", "show": false, "type": "textbox_save", @@ -313,7 +313,7 @@ ] }, { - "column": "Extra", + "column": "extra", "css_classes": "col-sm-3", "show": true, "type": "label", @@ -363,7 +363,7 @@ ] }, { - "column": "Status", + "column": "status", "css_classes": "col-sm-1", "show": true, "type": "replace", @@ -758,12 +758,12 @@ } ] }, - "default_value": ["Watched_Value1", "Watched_Value4"], + "default_value": ["watchedValue1", "watchedValue4"], "options": [ - "Watched_Value1", - "Watched_Value2", - "Watched_Value3", - "Watched_Value4" + "watchedValue1", + "watchedValue2", + "watchedValue3", + "watchedValue4" ], "localized": ["name", "description"], "name": [ @@ -783,15 +783,15 @@ "description": [ { "language_code": "en_us", - "string": "Send a notification if selected values change. Use CTRL + Click to select/deselect.
  • Watched_Value1 is Active
  • Watched_Value2 is Hostname
  • Watched_Value3 is hardware
  • Watched_Value4 is State
" + "string": "Send a notification if selected values change. Use CTRL + Click to select/deselect.
  • watchedValue1 is Active
  • watchedValue2 is Hostname
  • watchedValue3 is hardware
  • watchedValue4 is State
" }, { "language_code": "es_es", - "string": "Enviar una notificación si los valores seleccionados cambian. Utilice CTRL + clic para seleccionar/deseleccionar.
  • Watched_Value1 está activo
  • Watched_Value2 es el nombre de host
  • Watched_Value3 es hardware
  • Watched_Value4 es Estado
" + "string": "Enviar una notificación si los valores seleccionados cambian. Utilice CTRL + clic para seleccionar/deseleccionar.
  • watchedValue1 está activo
  • watchedValue2 es el nombre de host
  • watchedValue3 es hardware
  • watchedValue4 es Estado
" }, { "language_code": "de_de", - "string": "Sende eine Benachrichtigung, wenn ein ausgwählter Wert sich ändert. STRG + klicken zum aus-/abwählen.
  • Watched_Value1 ist der Aktivstatus
  • Watched_Value2 ist der Hostname
  • Watched_Value3 ist die Hardware
  • Watched_Value4 ist der Zustand
" + "string": "Sende eine Benachrichtigung, wenn ein ausgwählter Wert sich ändert. STRG + klicken zum aus-/abwählen.
  • watchedValue1 ist der Aktivstatus
  • watchedValue2 ist der Hostname
  • watchedValue3 ist die Hardware
  • watchedValue4 ist der Zustand
" } ] }, @@ -832,15 +832,15 @@ "description": [ { "language_code": "en_us", - "string": "Send a notification only on these statuses. new means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. watched-changed means that selected Watched_ValueN columns changed." + "string": "Send a notification only on these statuses. new means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. watched-changed means that selected watchedValueN columns changed." }, { "language_code": "es_es", - "string": "Envíe una notificación solo en estos estados. new significa que se descubrió un nuevo objeto único (una combinación única de PrimaryId y SecondaryId). watched-changed significa que las columnas Watched_ValueN seleccionadas cambiaron." + "string": "Envíe una notificación solo en estos estados. new significa que se descubrió un nuevo objeto único (una combinación única de PrimaryId y SecondaryId). watched-changed significa que las columnas watchedValueN seleccionadas cambiaron." }, { "language_code": "de_de", - "string": "Benachrichtige nur bei diesen Status. new bedeutet ein neues eindeutiges (einzigartige Kombination aus PrimaryId und SecondaryId) Objekt wurde erkennt. watched-changed bedeutet eine ausgewählte Watched_ValueN-Spalte hat sich geändert." + "string": "Benachrichtige nur bei diesen Status. new bedeutet ein neues eindeutiges (einzigartige Kombination aus PrimaryId und SecondaryId) Objekt wurde erkennt. watched-changed bedeutet eine ausgewählte watchedValueN-Spalte hat sich geändert." } ] } diff --git a/front/plugins/dhcp_servers/config.json b/front/plugins/dhcp_servers/config.json index 57169612..049ec58d 100755 --- a/front/plugins/dhcp_servers/config.json +++ b/front/plugins/dhcp_servers/config.json @@ -40,7 +40,7 @@ "params": [], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -55,7 +55,7 @@ ] }, { - "column": "Plugin", + "column": "plugin", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -74,7 +74,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "css_classes": "col-sm-2", "show": true, "type": "device_ip", @@ -93,7 +93,7 @@ ] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -112,7 +112,7 @@ ] }, { - "column": "DateTimeCreated", + "column": "dateTimeCreated", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -131,7 +131,7 @@ ] }, { - "column": "DateTimeChanged", + "column": "dateTimeChanged", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -150,7 +150,7 @@ ] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -169,7 +169,7 @@ ] }, { - "column": "Watched_Value2", + "column": "watchedValue2", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -188,7 +188,7 @@ ] }, { - "column": "Watched_Value3", + "column": "watchedValue3", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -207,7 +207,7 @@ ] }, { - "column": "Watched_Value4", + "column": "watchedValue4", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -226,7 +226,7 @@ ] }, { - "column": "UserData", + "column": "userData", "css_classes": "col-sm-2", "show": true, "type": "textbox_save", @@ -245,7 +245,7 @@ ] }, { - "column": "Status", + "column": "status", "css_classes": "col-sm-1", "show": true, "type": "replace", @@ -281,7 +281,7 @@ ] }, { - "column": "Extra", + "column": "extra", "css_classes": "col-sm-3", "show": true, "type": "label", @@ -483,12 +483,12 @@ } ] }, - "default_value": ["Watched_Value1"], + "default_value": ["watchedValue1"], "options": [ - "Watched_Value1", - "Watched_Value2", - "Watched_Value3", - "Watched_Value4" + "watchedValue1", + "watchedValue2", + "watchedValue3", + "watchedValue4" ], "localized": ["name", "description"], "name": [ @@ -504,11 +504,11 @@ "description": [ { "language_code": "en_us", - "string": "Send a notification if selected values change. Use CTRL + Click to select/deselect.
  • Watched_Value1 is Domain Name Server
  • Watched_Value2 is IP Offered
  • Watched_Value3 is Interface
  • Watched_Value4 is Router
" + "string": "Send a notification if selected values change. Use CTRL + Click to select/deselect.
  • watchedValue1 is Domain Name Server
  • watchedValue2 is IP Offered
  • watchedValue3 is Interface
  • watchedValue4 is Router
" }, { "language_code": "es_es", - "string": "Envíe una notificación si los valores seleccionados cambian. Utilice CTRL + clic para seleccionar/deseleccionar.
  • Watched_Value1 es servidor de nombres de dominio
  • Watched_Value2 es IP ofrecida
  • Watched_Value3 es Interfaz
  • Watched_Value4 es enrutador
" + "string": "Envíe una notificación si los valores seleccionados cambian. Utilice CTRL + clic para seleccionar/deseleccionar.
  • watchedValue1 es servidor de nombres de dominio
  • watchedValue2 es IP ofrecida
  • watchedValue3 es Interfaz
  • watchedValue4 es enrutador
" } ] }, @@ -540,11 +540,11 @@ "description": [ { "language_code": "en_us", - "string": "Send a notification only on these statuses. new means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. watched-changed means that selected Watched_ValueN columns changed." + "string": "Send a notification only on these statuses. new means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. watched-changed means that selected watchedValueN columns changed." }, { "language_code": "es_es", - "string": "Envíe una notificación solo en estos estados. new significa que se descubrió un nuevo objeto único (una combinación única de PrimaryId y SecondaryId). watched-changed significa que las columnas Watched_ValueN seleccionadas cambiaron." + "string": "Envíe una notificación solo en estos estados. new significa que se descubrió un nuevo objeto único (una combinación única de PrimaryId y SecondaryId). watched-changed significa que las columnas watchedValueN seleccionadas cambiaron." } ] } diff --git a/front/plugins/dig_scan/config.json b/front/plugins/dig_scan/config.json index 8729f9a5..23c58237 100755 --- a/front/plugins/dig_scan/config.json +++ b/front/plugins/dig_scan/config.json @@ -8,7 +8,7 @@ "show_ui": true, "data_filters": [ { - "compare_column": "Object_PrimaryID", + "compare_column": "objectPrimaryId", "compare_operator": "==", "compare_field_id": "txtMacFilter", "compare_js_template": "'{value}'.toString()", @@ -299,7 +299,7 @@ ], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -314,7 +314,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "css_classes": "col-sm-2", "show": true, "type": "device_name_mac", @@ -333,7 +333,7 @@ ] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -352,7 +352,7 @@ ] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -367,7 +367,7 @@ ] }, { - "column": "Watched_Value2", + "column": "watchedValue2", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -382,7 +382,7 @@ ] }, { - "column": "DateTimeCreated", + "column": "dateTimeCreated", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -397,7 +397,7 @@ ] }, { - "column": "DateTimeChanged", + "column": "dateTimeChanged", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -412,7 +412,7 @@ ] }, { - "column": "Status", + "column": "status", "css_classes": "col-sm-1", "show": true, "type": "replace", diff --git a/front/plugins/freebox/config.json b/front/plugins/freebox/config.json index 9ab39ec6..000b69d2 100755 --- a/front/plugins/freebox/config.json +++ b/front/plugins/freebox/config.json @@ -8,7 +8,7 @@ "mapped_to_table": "CurrentScan", "data_filters": [ { - "compare_column": "Object_PrimaryID", + "compare_column": "objectPrimaryId", "compare_operator": "==", "compare_field_id": "txtMacFilter", "compare_js_template": "'{value}'.toString()", @@ -376,7 +376,7 @@ ], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -393,7 +393,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "mapped_to_column": "scanMac", "css_classes": "col-sm-3", "show": true, @@ -411,7 +411,7 @@ ] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "mapped_to_column": "scanLastIP", "css_classes": "col-sm-2", "show": true, @@ -429,7 +429,7 @@ ] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "mapped_to_column": "scanName", "css_classes": "col-sm-2", "show": true, @@ -447,7 +447,7 @@ ] }, { - "column": "Watched_Value2", + "column": "watchedValue2", "mapped_to_column": "scanVendor", "css_classes": "col-sm-2", "show": true, @@ -465,7 +465,7 @@ ] }, { - "column": "Watched_Value3", + "column": "watchedValue3", "mapped_to_column": "scanType", "css_classes": "col-sm-2", "show": true, @@ -483,7 +483,7 @@ ] }, { - "column": "Watched_Value4", + "column": "watchedValue4", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -521,7 +521,7 @@ ] }, { - "column": "DateTimeCreated", + "column": "dateTimeCreated", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -538,7 +538,7 @@ ] }, { - "column": "DateTimeChanged", + "column": "dateTimeChanged", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -555,7 +555,7 @@ ] }, { - "column": "Status", + "column": "status", "css_classes": "col-sm-1", "show": true, "type": "replace", diff --git a/front/plugins/icmp_scan/config.json b/front/plugins/icmp_scan/config.json index d3fac3db..e04f3ac0 100755 --- a/front/plugins/icmp_scan/config.json +++ b/front/plugins/icmp_scan/config.json @@ -9,7 +9,7 @@ "show_ui": true, "data_filters": [ { - "compare_column": "Object_PrimaryID", + "compare_column": "objectPrimaryId", "compare_operator": "==", "compare_field_id": "txtMacFilter", "compare_js_template": "'{value}'.toString()", @@ -364,7 +364,7 @@ ], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -381,7 +381,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "mapped_to_column": "scanMac", "css_classes": "col-sm-3", "show": true, @@ -399,7 +399,7 @@ ] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "mapped_to_column": "scanLastIP", "css_classes": "col-sm-2", "show": true, @@ -417,7 +417,7 @@ ] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -434,7 +434,7 @@ ] }, { - "column": "Watched_Value2", + "column": "watchedValue2", "css_classes": "col-sm-2", "show": true, "type": "textarea_readonly", @@ -451,7 +451,7 @@ ] }, { - "column": "Watched_Value3", + "column": "watchedValue3", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -468,7 +468,7 @@ ] }, { - "column": "Watched_Value4", + "column": "watchedValue4", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -506,7 +506,7 @@ ] }, { - "column": "DateTimeCreated", + "column": "dateTimeCreated", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -523,7 +523,7 @@ ] }, { - "column": "DateTimeChanged", + "column": "dateTimeChanged", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -540,7 +540,7 @@ ] }, { - "column": "Status", + "column": "status", "css_classes": "col-sm-1", "show": true, "type": "replace", diff --git a/front/plugins/internet_ip/config.json b/front/plugins/internet_ip/config.json index 446f30d1..679913d5 100755 --- a/front/plugins/internet_ip/config.json +++ b/front/plugins/internet_ip/config.json @@ -7,7 +7,7 @@ "mapped_to_table": "CurrentScan", "data_filters": [ { - "compare_column": "Object_PrimaryID", + "compare_column": "objectPrimaryId", "compare_operator": "==", "compare_field_id": "txtMacFilter", "compare_js_template": "'{value}'.toString()", @@ -329,12 +329,12 @@ } ] }, - "default_value": ["Watched_Value1"], + "default_value": ["watchedValue1"], "options": [ - "Watched_Value1", - "Watched_Value2", - "Watched_Value3", - "Watched_Value4" + "watchedValue1", + "watchedValue2", + "watchedValue3", + "watchedValue4" ], "localized": ["name", "description"], "name": [ @@ -354,11 +354,11 @@ "description": [ { "language_code": "en_us", - "string": "Send a notification if selected values change. Use CTRL + Click to select/deselect.
  • Watched_Value1 is Previous IP (not recommended)
  • Watched_Value2 unused
  • Watched_Value3 unused
  • Watched_Value4 type
" + "string": "Send a notification if selected values change. Use CTRL + Click to select/deselect.
  • watchedValue1 is Previous IP (not recommended)
  • watchedValue2 unused
  • watchedValue3 unused
  • watchedValue4 type
" }, { "language_code": "de_de", - "string": "Sende eine Benachrichtigung, wenn ein ausgwählter Wert sich ändert. STRG + klicken zum aus-/abwählen.
  • Watched_Value1 ist die Vorige IP (nicht empfohlen)
  • Watched_Value2 ist nicht in Verwendung
  • Watched_Value3 ist nicht in Verwendung
  • Watched_Value4 ist nicht in Verwendung
" + "string": "Sende eine Benachrichtigung, wenn ein ausgwählter Wert sich ändert. STRG + klicken zum aus-/abwählen.
  • watchedValue1 ist die Vorige IP (nicht empfohlen)
  • watchedValue2 ist nicht in Verwendung
  • watchedValue3 ist nicht in Verwendung
  • watchedValue4 ist nicht in Verwendung
" } ] }, @@ -399,15 +399,15 @@ "description": [ { "language_code": "en_us", - "string": "Send a notification only on these statuses. new means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. watched-changed means that selected Watched_ValueN columns changed." + "string": "Send a notification only on these statuses. new means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. watched-changed means that selected watchedValueN columns changed." }, { "language_code": "es_es", - "string": "Envíe una notificación solo en estos estados. new significa que se descubrió un nuevo objeto único (una combinación única de PrimaryId y SecondaryId). watched-changed significa que las columnas Watched_ValueN seleccionadas cambiaron." + "string": "Envíe una notificación solo en estos estados. new significa que se descubrió un nuevo objeto único (una combinación única de PrimaryId y SecondaryId). watched-changed significa que las columnas watchedValueN seleccionadas cambiaron." }, { "language_code": "de_de", - "string": "Benachrichtige nur bei diesen Status. new bedeutet ein neues eindeutiges (einzigartige Kombination aus PrimaryId und SecondaryId) Objekt wurde erkennt. watched-changed bedeutet eine ausgewählte Watched_ValueN-Spalte hat sich geändert." + "string": "Benachrichtige nur bei diesen Status. new bedeutet ein neues eindeutiges (einzigartige Kombination aus PrimaryId und SecondaryId) Objekt wurde erkennt. watched-changed bedeutet eine ausgewählte watchedValueN-Spalte hat sich geändert." } ] }, @@ -478,7 +478,7 @@ ], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -493,7 +493,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "mapped_to_column": "scanMac", "css_classes": "col-sm-3", "show": true, @@ -517,7 +517,7 @@ ] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "mapped_to_column": "scanLastIP", "css_classes": "col-sm-2", "show": true, @@ -541,7 +541,7 @@ ] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -560,7 +560,7 @@ ] }, { - "column": "Watched_Value2", + "column": "watchedValue2", "css_classes": "col-sm-2", "show": true, "type": "textarea_readonly", @@ -575,7 +575,7 @@ ] }, { - "column": "Watched_Value3", + "column": "watchedValue3", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -590,7 +590,7 @@ ] }, { - "column": "Watched_Value4", + "column": "watchedValue4", "mapped_to_column": "scanType", "css_classes": "col-sm-2", "show": false, @@ -633,7 +633,7 @@ ] }, { - "column": "DateTimeCreated", + "column": "dateTimeCreated", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -656,7 +656,7 @@ ] }, { - "column": "DateTimeChanged", + "column": "dateTimeChanged", "mapped_to_column": "scanLastConnection", "css_classes": "col-sm-2", "show": true, @@ -680,7 +680,7 @@ ] }, { - "column": "Status", + "column": "status", "css_classes": "col-sm-1", "show": true, "type": "replace", diff --git a/front/plugins/internet_speedtest/README.md b/front/plugins/internet_speedtest/README.md index 898b2c67..17979043 100755 --- a/front/plugins/internet_speedtest/README.md +++ b/front/plugins/internet_speedtest/README.md @@ -1,6 +1,6 @@ ## Overview -A plugin allowing for executing regular internet speed tests. +A plugin allowing for executing regular internet speed tests. ### Usage @@ -43,9 +43,9 @@ Inside the container, a Python version of speedtest often exists in the virtual ### Data Mapping -- **Watched_Value1** — Download Speed (Mbps). -- **Watched_Value2** — Upload Speed (Mbps). -- **Watched_Value3** — Full JSON payload (useful for n8n or detailed webhooks). +- **watchedValue1** — Download Speed (Mbps). +- **watchedValue2** — Upload Speed (Mbps). +- **watchedValue3** — Full JSON payload (useful for n8n or detailed webhooks). ### Notes diff --git a/front/plugins/internet_speedtest/config.json b/front/plugins/internet_speedtest/config.json index 0d8a97f1..49df80eb 100755 --- a/front/plugins/internet_speedtest/config.json +++ b/front/plugins/internet_speedtest/config.json @@ -39,7 +39,7 @@ "params": [], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -54,7 +54,7 @@ ] }, { - "column": "Plugin", + "column": "plugin", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -77,7 +77,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "css_classes": "col-sm-2", "show": false, "type": "url", @@ -96,7 +96,7 @@ ] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -119,7 +119,7 @@ ] }, { - "column": "DateTimeCreated", + "column": "dateTimeCreated", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -138,7 +138,7 @@ ] }, { - "column": "DateTimeChanged", + "column": "dateTimeChanged", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -161,7 +161,7 @@ ] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "css_classes": "col-sm-2", "show": true, "type": "threshold", @@ -197,7 +197,7 @@ ] }, { - "column": "Watched_Value2", + "column": "watchedValue2", "css_classes": "col-sm-2", "show": true, "type": "threshold", @@ -233,7 +233,7 @@ ] }, { - "column": "Watched_Value3", + "column": "watchedValue3", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -256,7 +256,7 @@ ] }, { - "column": "Watched_Value4", + "column": "watchedValue4", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -279,7 +279,7 @@ ] }, { - "column": "UserData", + "column": "userData", "css_classes": "col-sm-2", "show": false, "type": "textbox_save", @@ -302,7 +302,7 @@ ] }, { - "column": "Status", + "column": "status", "css_classes": "col-sm-1", "show": false, "type": "replace", @@ -342,7 +342,7 @@ ] }, { - "column": "Extra", + "column": "extra", "css_classes": "col-sm-3", "show": false, "type": "label", @@ -568,10 +568,10 @@ }, "default_value": [], "options": [ - "Watched_Value1", - "Watched_Value2", - "Watched_Value3", - "Watched_Value4" + "watchedValue1", + "watchedValue2", + "watchedValue3", + "watchedValue4" ], "localized": ["name", "description"], "name": [ @@ -591,15 +591,15 @@ "description": [ { "language_code": "en_us", - "string": "Send a notification if selected values change. Use CTRL + Click to select/deselect.
  • Watched_Value1 is Download speed (not recommended)
  • Watched_Value2 is Upload speed (not recommended)
  • Watched_Value3 is JSON payload for webhooks (schema varies by engine)
  • Watched_Value4 unused
" + "string": "Send a notification if selected values change. Use CTRL + Click to select/deselect.
  • watchedValue1 is Download speed (not recommended)
  • watchedValue2 is Upload speed (not recommended)
  • watchedValue3 is JSON payload for webhooks (schema varies by engine)
  • watchedValue4 unused
" }, { "language_code": "es_es", - "string": "Envíe una notificación si los valores seleccionados cambian. Use CTRL + Clic para seleccionar/deseleccionar.
  • Watched_Value1 es la velocidad de descarga (no recomendado)
  • Watched_Value2 es la velocidad de carga (no recomendado)
  • Watched_Value3 es la carga útil JSON para webhooks (el esquema varía según el motor)
  • Watched_Value4 no se usa
" + "string": "Envíe una notificación si los valores seleccionados cambian. Use CTRL + Clic para seleccionar/deseleccionar.
  • watchedValue1 es la velocidad de descarga (no recomendado)
  • watchedValue2 es la velocidad de carga (no recomendado)
  • watchedValue3 es la carga útil JSON para webhooks (el esquema varía según el motor)
  • watchedValue4 no se usa
" }, { "language_code": "de_de", - "string": "Sende eine Benachrichtigung, wenn ein ausgwählter Wert sich ändert. STRG + klicken zum aus-/abwählen.
  • Watched_Value1 ist die Download-Geschwindigkeit (nicht empfohlen)
  • Watched_Value2 ist die Upload-Geschwindigkeit (nicht empfohlen)
  • Watched_Value3 ist JSON-Payload für Webhooks (Schema variiert je nach Engine)
  • Watched_Value4 ist nicht in Verwendung
" + "string": "Sende eine Benachrichtigung, wenn ein ausgwählter Wert sich ändert. STRG + klicken zum aus-/abwählen.
  • watchedValue1 ist die Download-Geschwindigkeit (nicht empfohlen)
  • watchedValue2 ist die Upload-Geschwindigkeit (nicht empfohlen)
  • watchedValue3 ist JSON-Payload für Webhooks (Schema variiert je nach Engine)
  • watchedValue4 ist nicht in Verwendung
" } ] }, @@ -640,15 +640,15 @@ "description": [ { "language_code": "en_us", - "string": "Send a notification only on these statuses. new means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. watched-changed means that selected Watched_ValueN columns changed." + "string": "Send a notification only on these statuses. new means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. watched-changed means that selected watchedValueN columns changed." }, { "language_code": "es_es", - "string": "Envíe una notificación solo en estos estados. new significa que se descubrió un nuevo objeto único (combinación única de PrimaryId y SecondaryId). watched-changed significa que seleccionó Watched_ValueN Las columnas cambiaron." + "string": "Envíe una notificación solo en estos estados. new significa que se descubrió un nuevo objeto único (combinación única de PrimaryId y SecondaryId). watched-changed significa que seleccionó watchedValueN Las columnas cambiaron." }, { "language_code": "de_de", - "string": "Benachrichtige nur bei diesen Status. new bedeutet ein neues eindeutiges (einzigartige Kombination aus PrimaryId und SecondaryId) Objekt wurde erkennt. watched-changed bedeutet eine ausgewählte Watched_ValueN-Spalte hat sich geändert." + "string": "Benachrichtige nur bei diesen Status. new bedeutet ein neues eindeutiges (einzigartige Kombination aus PrimaryId und SecondaryId) Objekt wurde erkennt. watched-changed bedeutet eine ausgewählte watchedValueN-Spalte hat sich geändert." } ] } diff --git a/front/plugins/ipneigh/config.json b/front/plugins/ipneigh/config.json index 3ba4d08e..1ca114a9 100755 --- a/front/plugins/ipneigh/config.json +++ b/front/plugins/ipneigh/config.json @@ -8,7 +8,7 @@ "mapped_to_table": "CurrentScan", "data_filters": [ { - "compare_column": "Object_PrimaryID", + "compare_column": "objectPrimaryId", "compare_operator": "==", "compare_field_id": "txtMacFilter", "compare_js_template": "'{value}'.toString()", @@ -273,7 +273,7 @@ ], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -290,7 +290,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "mapped_to_column": "scanMac", "css_classes": "col-sm-3", "show": true, @@ -308,7 +308,7 @@ ] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "mapped_to_column": "scanLastIP", "css_classes": "col-sm-2", "show": true, @@ -326,7 +326,7 @@ ] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "mapped_to_column": "scanName", "css_classes": "col-sm-2", "show": true, @@ -344,7 +344,7 @@ ] }, { - "column": "Watched_Value2", + "column": "watchedValue2", "mapped_to_column": "scanVendor", "css_classes": "col-sm-2", "show": true, @@ -362,7 +362,7 @@ ] }, { - "column": "Watched_Value3", + "column": "watchedValue3", "mapped_to_column": "scanType", "css_classes": "col-sm-2", "show": true, @@ -380,7 +380,7 @@ ] }, { - "column": "Watched_Value4", + "column": "watchedValue4", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -418,7 +418,7 @@ ] }, { - "column": "DateTimeCreated", + "column": "dateTimeCreated", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -435,7 +435,7 @@ ] }, { - "column": "DateTimeChanged", + "column": "dateTimeChanged", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -452,7 +452,7 @@ ] }, { - "column": "Status", + "column": "status", "css_classes": "col-sm-1", "show": true, "type": "replace", diff --git a/front/plugins/luci_import/config.json b/front/plugins/luci_import/config.json index e12eaeac..490e8932 100755 --- a/front/plugins/luci_import/config.json +++ b/front/plugins/luci_import/config.json @@ -9,7 +9,7 @@ "mapped_to_table": "CurrentScan", "data_filters": [ { - "compare_column": "Object_PrimaryID", + "compare_column": "objectPrimaryId", "compare_operator": "==", "compare_field_id": "txtMacFilter", "compare_js_template": "'{value}'.toString()", @@ -450,7 +450,7 @@ ], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -469,7 +469,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "mapped_to_column": "scanMac", "css_classes": "col-sm-3", "show": true, @@ -489,7 +489,7 @@ ] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "mapped_to_column": "scanLastIP", "css_classes": "col-sm-2", "show": true, @@ -509,7 +509,7 @@ ] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -528,7 +528,7 @@ ] }, { - "column": "Watched_Value2", + "column": "watchedValue2", "mapped_to_column": "scanName", "css_classes": "col-sm-2", "show": true, @@ -571,7 +571,7 @@ ] }, { - "column": "DateTimeCreated", + "column": "dateTimeCreated", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -590,7 +590,7 @@ ] }, { - "column": "DateTimeChanged", + "column": "dateTimeChanged", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -609,7 +609,7 @@ ] }, { - "column": "Status", + "column": "status", "css_classes": "col-sm-1", "show": true, "type": "replace", diff --git a/front/plugins/mikrotik_scan/config.json b/front/plugins/mikrotik_scan/config.json index f3cc3f58..b968d804 100755 --- a/front/plugins/mikrotik_scan/config.json +++ b/front/plugins/mikrotik_scan/config.json @@ -336,7 +336,7 @@ ], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -351,7 +351,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "mapped_to_column": "scanMac", "css_classes": "col-sm-3", "show": true, @@ -382,7 +382,7 @@ ] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "mapped_to_column": "scanLastIP", "css_classes": "col-sm-2", "show": true, @@ -398,7 +398,7 @@ ] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "css_classes": "col-sm-2", "show": true, "type": "device_ip", @@ -413,7 +413,7 @@ ] }, { - "column": "Watched_Value2", + "column": "watchedValue2", "mapped_to_column": "scanName", "css_classes": "col-sm-2", "show": true, @@ -429,7 +429,7 @@ ] }, { - "column": "Watched_Value3", + "column": "watchedValue3", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -444,7 +444,7 @@ ] }, { - "column": "Watched_Value4", + "column": "watchedValue4", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -459,7 +459,7 @@ ] }, { - "column": "HelpVal1", + "column": "helpVal1", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -493,7 +493,7 @@ ] }, { - "column": "DateTimeCreated", + "column": "dateTimeCreated", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -512,7 +512,7 @@ ] }, { - "column": "DateTimeChanged", + "column": "dateTimeChanged", "css_classes": "col-sm-2", "show": true, "type": "label", diff --git a/front/plugins/nbtscan_scan/config.json b/front/plugins/nbtscan_scan/config.json index 48b8a589..b64fad72 100755 --- a/front/plugins/nbtscan_scan/config.json +++ b/front/plugins/nbtscan_scan/config.json @@ -8,7 +8,7 @@ "show_ui": true, "data_filters": [ { - "compare_column": "Object_PrimaryID", + "compare_column": "objectPrimaryId", "compare_operator": "==", "compare_field_id": "txtMacFilter", "compare_js_template": "'{value}'.toString()", @@ -299,7 +299,7 @@ ], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -314,7 +314,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "css_classes": "col-sm-2", "show": true, "type": "device_name_mac", @@ -333,7 +333,7 @@ ] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -352,7 +352,7 @@ ] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -367,7 +367,7 @@ ] }, { - "column": "Watched_Value2", + "column": "watchedValue2", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -382,7 +382,7 @@ ] }, { - "column": "DateTimeCreated", + "column": "dateTimeCreated", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -397,7 +397,7 @@ ] }, { - "column": "DateTimeChanged", + "column": "dateTimeChanged", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -412,7 +412,7 @@ ] }, { - "column": "Status", + "column": "status", "css_classes": "col-sm-1", "show": true, "type": "replace", diff --git a/front/plugins/nmap_dev_scan/config.json b/front/plugins/nmap_dev_scan/config.json index 55cf9c77..a5b3487c 100755 --- a/front/plugins/nmap_dev_scan/config.json +++ b/front/plugins/nmap_dev_scan/config.json @@ -8,7 +8,7 @@ "mapped_to_table": "CurrentScan", "data_filters": [ { - "compare_column": "Object_PrimaryID", + "compare_column": "objectPrimaryId", "compare_operator": "==", "compare_field_id": "txtMacFilter", "compare_js_template": "'{value}'.toString()", @@ -290,10 +290,10 @@ }, "default_value": [], "options": [ - "Watched_Value1", - "Watched_Value2", - "Watched_Value3", - "Watched_Value4" + "watchedValue1", + "watchedValue2", + "watchedValue3", + "watchedValue4" ], "localized": [ "name", @@ -316,15 +316,15 @@ "description": [ { "language_code": "en_us", - "string": "Send a notification if selected values change. Use CTRL + Click to select/deselect.
  • Watched_Value1 is Name
  • Watched_Value2 is Vendor
  • Watched_Value3 is Interface
  • Watched_Value4 is N/A
" + "string": "Send a notification if selected values change. Use CTRL + Click to select/deselect.
  • watchedValue1 is Name
  • watchedValue2 is Vendor
  • watchedValue3 is Interface
  • watchedValue4 is N/A
" }, { "language_code": "es_es", - "string": "Envía una notificación si los valores seleccionados cambian. Utilice CTRL + clic para seleccionar/deseleccionar.
  • Valor_observado1 es Name
  • Valor_observado2 es Proveedor
  • Valor_observado3 es Interfaz
  • Valor_observado4 es N/A
" + "string": "Envía una notificación si los valores seleccionados cambian. Utilice CTRL + clic para seleccionar/deseleccionar.
  • watchedValue1 es Name
  • watchedValue2 es Proveedor
  • watchedValue3 es Interfaz
  • watchedValue4 es N/A
" }, { "language_code": "de_de", - "string": "Sende eine Benachrichtigung, wenn ein ausgwählter Wert sich ändert. STRG + klicken zum aus-/abwählen.
  • Watched_Value1 ist der Namen
  • Watched_Value2 ist der Hersteller
  • Watched_Value3 ist das Interface
  • Watched_Value4 ist nicht in Verwendung
" + "string": "Sende eine Benachrichtigung, wenn ein ausgwählter Wert sich ändert. STRG + klicken zum aus-/abwählen.
  • watchedValue1 ist der Namen
  • watchedValue2 ist der Hersteller
  • watchedValue3 ist das Interface
  • watchedValue4 ist nicht in Verwendung
" } ] }, @@ -522,7 +522,7 @@ ], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -539,7 +539,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "mapped_to_column": "scanMac", "css_classes": "col-sm-3", "show": true, @@ -557,7 +557,7 @@ ] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "mapped_to_column": "scanLastIP", "css_classes": "col-sm-2", "show": true, @@ -575,7 +575,7 @@ ] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "mapped_to_column": "scanName", "css_classes": "col-sm-2", "show": true, @@ -593,7 +593,7 @@ ] }, { - "column": "Watched_Value2", + "column": "watchedValue2", "mapped_to_column": "scanVendor", "css_classes": "col-sm-2", "show": true, @@ -619,7 +619,7 @@ ] }, { - "column": "Watched_Value3", + "column": "watchedValue3", "mapped_to_column": "scanLastQuery", "css_classes": "col-sm-2", "show": true, @@ -670,7 +670,7 @@ ] }, { - "column": "DateTimeCreated", + "column": "dateTimeCreated", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -695,7 +695,7 @@ ] }, { - "column": "DateTimeChanged", + "column": "dateTimeChanged", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -720,7 +720,7 @@ ] }, { - "column": "Status", + "column": "status", "css_classes": "col-sm-1", "show": true, "type": "replace", diff --git a/front/plugins/nmap_scan/config.json b/front/plugins/nmap_scan/config.json index 34a6d561..b5326075 100755 --- a/front/plugins/nmap_scan/config.json +++ b/front/plugins/nmap_scan/config.json @@ -72,7 +72,7 @@ ], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -87,7 +87,7 @@ ] }, { - "column": "Plugin", + "column": "plugin", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -102,7 +102,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "css_classes": "col-sm-2", "show": true, "type": "device_name_mac", @@ -121,7 +121,7 @@ ] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -140,7 +140,7 @@ ] }, { - "column": "DateTimeCreated", + "column": "dateTimeCreated", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -159,7 +159,7 @@ ] }, { - "column": "DateTimeChanged", + "column": "dateTimeChanged", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -178,7 +178,7 @@ ] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "css_classes": "col-sm-1", "show": true, "type": "label", @@ -197,7 +197,7 @@ ] }, { - "column": "Watched_Value2", + "column": "watchedValue2", "css_classes": "col-sm-1", "show": true, "type": "label", @@ -215,7 +215,7 @@ ] }, { - "column": "Watched_Value3", + "column": "watchedValue3", "css_classes": "col-sm-1", "show": true, "type": "regex.url_http_https", @@ -239,7 +239,7 @@ ] }, { - "column": "Watched_Value4", + "column": "watchedValue4", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -258,7 +258,7 @@ ] }, { - "column": "Extra", + "column": "extra", "css_classes": "col-sm-1", "show": false, "type": "label", @@ -277,7 +277,7 @@ ] }, { - "column": "UserData", + "column": "userData", "css_classes": "col-sm-3", "show": true, "type": "textbox_save", @@ -315,7 +315,7 @@ ] }, { - "column": "Status", + "column": "status", "css_classes": "col-sm-1", "show": true, "type": "replace", @@ -558,12 +558,12 @@ } ] }, - "default_value": ["Watched_Value1"], + "default_value": ["watchedValue1"], "options": [ - "Watched_Value1", - "Watched_Value2", - "Watched_Value3", - "Watched_Value4" + "watchedValue1", + "watchedValue2", + "watchedValue3", + "watchedValue4" ], "localized": ["name", "description"], "name": [ @@ -579,11 +579,11 @@ "description": [ { "language_code": "en_us", - "string": "Send a notification if selected values change. Use CTRL + Click to select/deselect.
  • Watched_Value1 is service type (e.g.: http, ssh)
  • Watched_Value2 is Status (open or closed)
  • Watched_Value3 unused
  • Watched_Value4 unused
" + "string": "Send a notification if selected values change. Use CTRL + Click to select/deselect.
  • watchedValue1 is service type (e.g.: http, ssh)
  • watchedValue2 is Status (open or closed)
  • watchedValue3 unused
  • watchedValue4 unused
" }, { "language_code": "es_es", - "string": "Envíe una notificación si los valores seleccionados cambian. Utilice CTRL + clic para seleccionar/deseleccionar.
  • Watched_Value1 es el tipo de servicio (p. ej., http, ssh)
  • Watched_Value2 es el estado (abierto o cerrado)
  • Watched_Value3 no utilizado
  • Watched_Value4 no utilizado
" + "string": "Envíe una notificación si los valores seleccionados cambian. Utilice CTRL + clic para seleccionar/deseleccionar.
  • watchedValue1 es el tipo de servicio (p. ej., http, ssh)
  • watchedValue2 es el estado (abierto o cerrado)
  • watchedValue3 no utilizado
  • watchedValue4 no utilizado
" } ] }, @@ -615,11 +615,11 @@ "description": [ { "language_code": "en_us", - "string": "Send a notification only on these statuses. new means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. watched-changed means that selected Watched_ValueN columns changed." + "string": "Send a notification only on these statuses. new means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. watched-changed means that selected watchedValueN columns changed." }, { "language_code": "es_es", - "string": "Envíe una notificación solo en estos estados. new significa que se descubrió un nuevo objeto único (combinación única de PrimaryId y SecondaryId). watched-changed significa que seleccionó Watched_ValueN Las columnas cambiaron." + "string": "Envíe una notificación solo en estos estados. new significa que se descubrió un nuevo objeto único (combinación única de PrimaryId y SecondaryId). watched-changed significa que seleccionó watchedValueN Las columnas cambiaron." } ] } diff --git a/front/plugins/notification_processing/README.md b/front/plugins/notification_processing/README.md index 9fad5a26..c1deabf0 100755 --- a/front/plugins/notification_processing/README.md +++ b/front/plugins/notification_processing/README.md @@ -33,12 +33,12 @@ The following notification types are available based on the `NTFPRCS_INCLUDED_SE - Notifies about specific events triggered by a device. - The device must have **Alert Events** enabled in its settings. - Includes events: - - `Connected`, `Down Reconnected`, `Disconnected`,`IP Changed` + - `Connected`, `Down Reconnected`, `Disconnected`,`IP Changed` - you can exclude devices with a custom where condition via the `NTFPRCS_event_condition` setting ### `plugins` - Notifies when an event is triggered by a plugin. -- These notifications depend on the plugin's configuration of the `Watched_Value1-4` values and the `_REPORT_ON` settings. +- These notifications depend on the plugin's configuration of the `watchedValue1-4` values and the `_REPORT_ON` settings. ## Device-Specific Overrides diff --git a/front/plugins/notification_processing/config.json b/front/plugins/notification_processing/config.json index 3333fd79..726c0382 100755 --- a/front/plugins/notification_processing/config.json +++ b/front/plugins/notification_processing/config.json @@ -225,7 +225,7 @@ "description": [ { "language_code": "en_us", - "string": "Custom text template for new device notifications. Use {FieldName} placeholders, e.g. {devName} ({eve_MAC}) - {eve_IP}. Leave empty for default formatting. Available fields: {devName}, {eve_MAC}, {devVendor}, {eve_IP}, {eve_DateTime}, {eve_EventType}, {devComments}." + "string": "Custom text template for new device notifications. Use {FieldName} placeholders, e.g. {devName} ({eveMac}) - {eveIp}. Leave empty for default formatting. Available fields: {devName}, {eveMac}, {devVendor}, {eveIp}, {eveDateTime}, {eveEventType}, {devComments}." } ] }, @@ -249,7 +249,7 @@ "description": [ { "language_code": "en_us", - "string": "Custom text template for down device notifications. Use {FieldName} placeholders, e.g. {devName} ({eve_MAC}) - {eve_IP}. Leave empty for default formatting. Available fields: {devName}, {eve_MAC}, {devVendor}, {eve_IP}, {eve_DateTime}, {eve_EventType}, {devComments}." + "string": "Custom text template for down device notifications. Use {FieldName} placeholders, e.g. {devName} ({eveMac}) - {eveIp}. Leave empty for default formatting. Available fields: {devName}, {eveMac}, {devVendor}, {eveIp}, {eveDateTime}, {eveEventType}, {devComments}." } ] }, @@ -273,7 +273,7 @@ "description": [ { "language_code": "en_us", - "string": "Custom text template for reconnected device notifications. Use {FieldName} placeholders, e.g. {devName} ({eve_MAC}) reconnected at {eve_DateTime}. Leave empty for default formatting. Available fields: {devName}, {eve_MAC}, {devVendor}, {eve_IP}, {eve_DateTime}, {eve_EventType}, {devComments}." + "string": "Custom text template for reconnected device notifications. Use {FieldName} placeholders, e.g. {devName} ({eveMac}) reconnected at {eveDateTime}. Leave empty for default formatting. Available fields: {devName}, {eveMac}, {devVendor}, {eveIp}, {eveDateTime}, {eveEventType}, {devComments}." } ] }, @@ -297,7 +297,7 @@ "description": [ { "language_code": "en_us", - "string": "Custom text template for event notifications. Use {FieldName} placeholders, e.g. {devName} ({eve_MAC}) {eve_EventType} at {eve_DateTime}. Leave empty for default formatting. Available fields: {devName}, {eve_MAC}, {devVendor}, {eve_IP}, {eve_DateTime}, {eve_EventType}, {devComments}." + "string": "Custom text template for event notifications. Use {FieldName} placeholders, e.g. {devName} ({eveMac}) {eveEventType} at {eveDateTime}. Leave empty for default formatting. Available fields: {devName}, {eveMac}, {devVendor}, {eveIp}, {eveDateTime}, {eveEventType}, {devComments}." } ] }, @@ -321,7 +321,7 @@ "description": [ { "language_code": "en_us", - "string": "Custom text template for plugin event notifications. Use {FieldName} placeholders, e.g. {Plugin}: {Object_PrimaryId} - {Status}. Leave empty for default formatting. Available fields: {Plugin}, {Object_PrimaryId}, {Object_SecondaryId}, {DateTimeChanged}, {Watched_Value1}, {Watched_Value2}, {Watched_Value3}, {Watched_Value4}, {Status}." + "string": "Custom text template for plugin event notifications. Use {FieldName} placeholders, e.g. {plugin}: {objectPrimaryId} - {status}. Leave empty for default formatting. Available fields: {plugin}, {objectPrimaryId}, {objectSecondaryId}, {dateTimeChanged}, {watchedValue1}, {watchedValue2}, {watchedValue3}, {watchedValue4}, {status}." } ] } diff --git a/front/plugins/nslookup_scan/config.json b/front/plugins/nslookup_scan/config.json index 6bf444d0..00449c2d 100755 --- a/front/plugins/nslookup_scan/config.json +++ b/front/plugins/nslookup_scan/config.json @@ -8,7 +8,7 @@ "show_ui": true, "data_filters": [ { - "compare_column": "Object_PrimaryID", + "compare_column": "objectPrimaryId", "compare_operator": "==", "compare_field_id": "txtMacFilter", "compare_js_template": "'{value}'.toString()", @@ -299,7 +299,7 @@ ], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -314,7 +314,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "css_classes": "col-sm-3", "show": true, "type": "device_name_mac", @@ -329,7 +329,7 @@ ] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -344,7 +344,7 @@ ] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -359,7 +359,7 @@ ] }, { - "column": "Watched_Value2", + "column": "watchedValue2", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -374,7 +374,7 @@ ] }, { - "column": "DateTimeCreated", + "column": "dateTimeCreated", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -393,7 +393,7 @@ ] }, { - "column": "DateTimeChanged", + "column": "dateTimeChanged", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -412,7 +412,7 @@ ] }, { - "column": "Status", + "column": "status", "css_classes": "col-sm-1", "show": true, "type": "replace", diff --git a/front/plugins/omada_sdn_imp/config.json b/front/plugins/omada_sdn_imp/config.json index 394d3940..e4560f44 100755 --- a/front/plugins/omada_sdn_imp/config.json +++ b/front/plugins/omada_sdn_imp/config.json @@ -8,7 +8,7 @@ "mapped_to_table": "CurrentScan", "data_filters": [ { - "compare_column": "Object_PrimaryID", + "compare_column": "objectPrimaryId", "compare_operator": "==", "compare_field_id": "txtMacFilter", "compare_js_template": "'{value}'.toString()", @@ -403,7 +403,7 @@ "description": [ { "language_code": "en_us", - "string": "Send a notification if selected values change. Use CTRL + Click to select/deselect.
  • Watched_Value1 is Hostname
  • Watched_Value2 is Parent Node
  • Watched_Value3 is Port
  • Watched_Value4 is SSID
" + "string": "Send a notification if selected values change. Use CTRL + Click to select/deselect.
  • watchedValue1 is Hostname
  • watchedValue2 is Parent Node
  • watchedValue3 is Port
  • watchedValue4 is SSID
" } ], "function": "WATCH", @@ -419,10 +419,10 @@ } ], "options": [ - "Watched_Value1", - "Watched_Value2", - "Watched_Value3", - "Watched_Value4" + "watchedValue1", + "watchedValue2", + "watchedValue3", + "watchedValue4" ], "type": { "dataType": "array", @@ -440,7 +440,7 @@ "description": [ { "language_code": "en_us", - "string": "Send a notification only on these statuses. new means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. watched-changed means that selected Watched_ValueN columns changed." + "string": "Send a notification only on these statuses. new means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. watched-changed means that selected watchedValueN columns changed." } ], "function": "REPORT_ON", @@ -542,7 +542,7 @@ ], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -557,7 +557,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "mapped_to_column": "scanMac", "css_classes": "col-sm-3", "show": true, @@ -581,7 +581,7 @@ ] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "mapped_to_column": "scanLastIP", "css_classes": "col-sm-2", "show": true, @@ -605,7 +605,7 @@ ] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "mapped_to_column": "scanName", "css_classes": "col-sm-2", "show": true, @@ -621,7 +621,7 @@ ] }, { - "column": "Watched_Value2", + "column": "watchedValue2", "mapped_to_column": "scanParentMAC", "css_classes": "col-sm-2", "show": true, @@ -637,7 +637,7 @@ ] }, { - "column": "Watched_Value3", + "column": "watchedValue3", "mapped_to_column": "scanParentPort", "css_classes": "col-sm-2", "show": true, @@ -653,7 +653,7 @@ ] }, { - "column": "Watched_Value4", + "column": "watchedValue4", "mapped_to_column": "scanSSID", "css_classes": "col-sm-2", "show": true, @@ -669,7 +669,7 @@ ] }, { - "column": "Extra", + "column": "extra", "mapped_to_column": "scanType", "css_classes": "col-sm-2", "show": false, @@ -712,7 +712,7 @@ ] }, { - "column": "DateTimeCreated", + "column": "dateTimeCreated", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -735,7 +735,7 @@ ] }, { - "column": "DateTimeChanged", + "column": "dateTimeChanged", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -758,7 +758,7 @@ ] }, { - "column": "Status", + "column": "status", "css_classes": "col-sm-1", "show": true, "type": "replace", diff --git a/front/plugins/omada_sdn_imp/omada_sdn.py b/front/plugins/omada_sdn_imp/omada_sdn.py index 9447df69..922f7971 100755 --- a/front/plugins/omada_sdn_imp/omada_sdn.py +++ b/front/plugins/omada_sdn_imp/omada_sdn.py @@ -319,7 +319,7 @@ def main(): # make sure the below mapping is mapped in config.json, for example: # "database_column_definitions": [ # { - # "column": "Object_PrimaryID", <--------- the value I save into primaryId + # "column": "objectPrimaryId", <--------- the value I save into primaryId # "mapped_to_column": "scanMac", <--------- gets unserted into the CurrentScan DB table column scanMac # watched1 = 'null' , # figure a way to run my udpate script delayed diff --git a/front/plugins/omada_sdn_openapi/config.json b/front/plugins/omada_sdn_openapi/config.json index cc594202..b3737b78 100755 --- a/front/plugins/omada_sdn_openapi/config.json +++ b/front/plugins/omada_sdn_openapi/config.json @@ -8,7 +8,7 @@ "mapped_to_table": "CurrentScan", "data_filters": [ { - "compare_column": "Object_PrimaryID", + "compare_column": "objectPrimaryId", "compare_operator": "==", "compare_field_id": "txtMacFilter", "compare_js_template": "'{value}'.toString()", @@ -380,7 +380,7 @@ "description": [ { "language_code": "en_us", - "string": "Send a notification if selected values change. Use CTRL + Click to select/deselect.
  • Watched_Value1 is Device Name
  • Watched_Value2 is Parent Node MAC
  • Watched_Value3 is Parent Node Port
  • Watched_Value4 is Parent Node SSID
" + "string": "Send a notification if selected values change. Use CTRL + Click to select/deselect.
  • watchedValue1 is Device Name
  • watchedValue2 is Parent Node MAC
  • watchedValue3 is Parent Node Port
  • watchedValue4 is Parent Node SSID
" } ], "function": "WATCH", @@ -392,10 +392,10 @@ } ], "options": [ - "Watched_Value1", - "Watched_Value2", - "Watched_Value3", - "Watched_Value4" + "watchedValue1", + "watchedValue2", + "watchedValue3", + "watchedValue4" ], "type": { "dataType": "array", @@ -413,7 +413,7 @@ "description": [ { "language_code": "en_us", - "string": "Send a notification only on these statuses. new means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. watched-changed means that selected Watched_ValueN columns changed." + "string": "Send a notification only on these statuses. new means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. watched-changed means that selected watchedValueN columns changed." } ], "function": "REPORT_ON", @@ -517,7 +517,7 @@ ], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -532,7 +532,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "mapped_to_column": "scanMac", "css_classes": "col-sm-3", "show": true, @@ -548,7 +548,7 @@ ] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "mapped_to_column": "scanLastIP", "css_classes": "col-sm-2", "show": true, @@ -564,7 +564,7 @@ ] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "mapped_to_column": "scanName", "css_classes": "col-sm-2", "show": true, @@ -580,7 +580,7 @@ ] }, { - "column": "Watched_Value2", + "column": "watchedValue2", "mapped_to_column": "scanParentMAC", "css_classes": "col-sm-2", "show": true, @@ -596,7 +596,7 @@ ] }, { - "column": "Watched_Value3", + "column": "watchedValue3", "mapped_to_column": "scanParentPort", "css_classes": "col-sm-2", "show": true, @@ -612,7 +612,7 @@ ] }, { - "column": "Watched_Value4", + "column": "watchedValue4", "mapped_to_column": "scanSSID", "css_classes": "col-sm-2", "show": true, @@ -628,7 +628,7 @@ ] }, { - "column": "Extra", + "column": "extra", "mapped_to_column": "scanType", "css_classes": "col-sm-2", "show": false, @@ -663,7 +663,7 @@ ] }, { - "column": "DateTimeCreated", + "column": "dateTimeCreated", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -678,7 +678,7 @@ ] }, { - "column": "DateTimeChanged", + "column": "dateTimeChanged", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -693,7 +693,7 @@ ] }, { - "column": "HelpVal1", + "column": "helpVal1", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -708,7 +708,7 @@ ] }, { - "column": "HelpVal2", + "column": "helpVal2", "mapped_to_column": "scanSite", "css_classes": "col-sm-2", "show": true, @@ -724,7 +724,7 @@ ] }, { - "column": "HelpVal3", + "column": "helpVal3", "css_classes": "col-sm-2", "show": true, "type": "label", diff --git a/front/plugins/pihole_api_scan/config.json b/front/plugins/pihole_api_scan/config.json index 2ed11f33..f5605c17 100644 --- a/front/plugins/pihole_api_scan/config.json +++ b/front/plugins/pihole_api_scan/config.json @@ -8,7 +8,7 @@ "mapped_to_table": "CurrentScan", "data_filters": [ { - "compare_column": "Object_PrimaryID", + "compare_column": "objectPrimaryId", "compare_operator": "==", "compare_field_id": "txtMacFilter", "compare_js_template": "'{value}'.toString()", @@ -441,7 +441,7 @@ ], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -456,7 +456,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "mapped_to_column": "scanMac", "css_classes": "col-sm-3", "show": true, @@ -472,7 +472,7 @@ ] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "mapped_to_column": "scanLastIP", "css_classes": "col-sm-2", "show": true, @@ -488,7 +488,7 @@ ] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "mapped_to_column": "scanName", "css_classes": "col-sm-2", "show": true, @@ -504,7 +504,7 @@ ] }, { - "column": "Watched_Value2", + "column": "watchedValue2", "mapped_to_column": "scanVendor", "css_classes": "col-sm-2", "show": true, @@ -520,7 +520,7 @@ ] }, { - "column": "Watched_Value3", + "column": "watchedValue3", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -535,7 +535,7 @@ ] }, { - "column": "Watched_Value4", + "column": "watchedValue4", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -569,7 +569,7 @@ ] }, { - "column": "DateTimeCreated", + "column": "dateTimeCreated", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -584,7 +584,7 @@ ] }, { - "column": "DateTimeChanged", + "column": "dateTimeChanged", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -599,7 +599,7 @@ ] }, { - "column": "Status", + "column": "status", "css_classes": "col-sm-1", "show": true, "type": "replace", diff --git a/front/plugins/pihole_scan/config.json b/front/plugins/pihole_scan/config.json index 6d96f111..c8a371de 100755 --- a/front/plugins/pihole_scan/config.json +++ b/front/plugins/pihole_scan/config.json @@ -8,7 +8,7 @@ "mapped_to_table": "CurrentScan", "data_filters": [ { - "compare_column": "Object_PrimaryID", + "compare_column": "objectPrimaryId", "compare_operator": "==", "compare_field_id": "txtMacFilter", "compare_js_template": "'{value}'.toString()", @@ -105,7 +105,7 @@ { "elementType": "input", "elementOptions": [], "transformers": [] } ] }, - "default_value": "SELECT n.hwaddr AS Object_PrimaryID, {s-quote}null{s-quote} AS Object_SecondaryID, datetime() AS DateTime, na.ip AS Watched_Value1, n.lastQuery AS Watched_Value2, na.name AS Watched_Value3, n.macVendor AS Watched_Value4, {s-quote}null{s-quote} AS Extra, n.hwaddr AS ForeignKey FROM EXTERNAL_PIHOLE.Network AS n LEFT JOIN EXTERNAL_PIHOLE.Network_Addresses AS na ON na.network_id = n.id WHERE n.hwaddr NOT LIKE {s-quote}ip-%{s-quote} AND n.hwaddr is not {s-quote}00:00:00:00:00:00{s-quote} AND na.ip is not null", + "default_value": "SELECT n.hwaddr AS objectPrimaryId, {s-quote}null{s-quote} AS objectSecondaryId, datetime() AS dateTimeChanged, na.ip AS watchedValue1, n.lastQuery AS watchedValue2, na.name AS watchedValue3, n.macVendor AS watchedValue4, {s-quote}null{s-quote} AS extra, n.hwaddr AS foreignKey FROM EXTERNAL_PIHOLE.Network AS n LEFT JOIN EXTERNAL_PIHOLE.Network_Addresses AS na ON na.network_id = n.id WHERE n.hwaddr NOT LIKE {s-quote}ip-%{s-quote} AND n.hwaddr is not {s-quote}00:00:00:00:00:00{s-quote} AND na.ip is not null", "options": [], "localized": ["name", "description"], "name": [ @@ -295,12 +295,12 @@ } ] }, - "default_value": ["Watched_Value1", "Watched_Value2"], + "default_value": ["watchedValue1", "watchedValue2"], "options": [ - "Watched_Value1", - "Watched_Value2", - "Watched_Value3", - "Watched_Value4" + "watchedValue1", + "watchedValue2", + "watchedValue3", + "watchedValue4" ], "localized": ["name", "description"], "name": [ @@ -316,11 +316,11 @@ "description": [ { "language_code": "en_us", - "string": "Send a notification if selected values change. Use CTRL + Click to select/deselect.
  • Watched_Value1 is IP
  • Watched_Value2 is Last Query
  • Watched_Value3 is Name
  • Watched_Value4 is N/A
" + "string": "Send a notification if selected values change. Use CTRL + Click to select/deselect.
  • watchedValue1 is IP
  • watchedValue2 is Last Query
  • watchedValue3 is Name
  • watchedValue4 is N/A
" }, { "language_code": "es_es", - "string": "Envíe una notificación si los valores seleccionados cambian. Utilice CTRL + clic para seleccionar/deseleccionar.
  • Watched_Value1 es IP
  • Watched_Value2 es Proveedor
  • Watched_Value3 is es Interfaz
  • Watched_Value4 es N/A
" + "string": "Envíe una notificación si los valores seleccionados cambian. Utilice CTRL + clic para seleccionar/deseleccionar.
  • watchedValue1 es IP
  • watchedValue2 es Proveedor
  • watchedValue3 is es Interfaz
  • watchedValue4 es N/A
" } ] }, @@ -369,7 +369,7 @@ "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -384,7 +384,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "mapped_to_column": "scanMac", "css_classes": "col-sm-2", "show": true, @@ -404,7 +404,7 @@ ] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -419,7 +419,7 @@ ] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "mapped_to_column": "scanLastIP", "css_classes": "col-sm-2", "show": true, @@ -439,7 +439,7 @@ ] }, { - "column": "Watched_Value2", + "column": "watchedValue2", "mapped_to_column": "scanLastQuery", "css_classes": "col-sm-2", "show": true, @@ -455,7 +455,7 @@ ] }, { - "column": "Watched_Value3", + "column": "watchedValue3", "mapped_to_column": "scanName", "css_classes": "col-sm-2", "show": true, @@ -471,7 +471,7 @@ ] }, { - "column": "Watched_Value4", + "column": "watchedValue4", "mapped_to_column": "scanVendor", "css_classes": "col-sm-2", "show": true, @@ -510,7 +510,7 @@ ] }, { - "column": "DateTimeCreated", + "column": "dateTimeCreated", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -529,7 +529,7 @@ ] }, { - "column": "DateTimeChanged", + "column": "dateTimeChanged", "css_classes": "col-sm-2", "show": true, "type": "label", diff --git a/front/plugins/snmp_discovery/config.json b/front/plugins/snmp_discovery/config.json index b89ed4ad..1c2ea990 100755 --- a/front/plugins/snmp_discovery/config.json +++ b/front/plugins/snmp_discovery/config.json @@ -7,7 +7,7 @@ "data_source": "script", "data_filters": [ { - "compare_column": "Object_PrimaryID", + "compare_column": "objectPrimaryId", "compare_operator": "==", "compare_field_id": "txtMacFilter", "compare_js_template": "'{value}'.toString()", @@ -65,7 +65,7 @@ ], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -80,7 +80,7 @@ ] }, { - "column": "Plugin", + "column": "plugin", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -99,7 +99,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "mapped_to_column": "scanMac", "css_classes": "col-sm-2", "show": true, @@ -119,7 +119,7 @@ ] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "mapped_to_column": "scanLastIP", "css_classes": "col-sm-2", "show": true, @@ -139,7 +139,7 @@ ] }, { - "column": "DateTimeCreated", + "column": "dateTimeCreated", "mapped_to_column": "scanLastConnection", "css_classes": "col-sm-2", "show": true, @@ -159,7 +159,7 @@ ] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "mapped_to_column": "scanName", "css_classes": "col-sm-2", "show": true, @@ -179,7 +179,7 @@ ] }, { - "column": "Watched_Value2", + "column": "watchedValue2", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -198,7 +198,7 @@ ] }, { - "column": "Watched_Value3", + "column": "watchedValue3", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -217,7 +217,7 @@ ] }, { - "column": "Watched_Value4", + "column": "watchedValue4", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -236,7 +236,7 @@ ] }, { - "column": "UserData", + "column": "userData", "css_classes": "col-sm-2", "show": false, "type": "textbox_save", @@ -255,7 +255,7 @@ ] }, { - "column": "Extra", + "column": "extra", "css_classes": "col-sm-3", "show": true, "type": "label", @@ -297,7 +297,7 @@ ] }, { - "column": "Status", + "column": "status", "css_classes": "col-sm-1", "show": true, "type": "replace", @@ -663,12 +663,12 @@ } ] }, - "default_value": ["Watched_Value1"], + "default_value": ["watchedValue1"], "options": [ - "Watched_Value1", - "Watched_Value2", - "Watched_Value3", - "Watched_Value4" + "watchedValue1", + "watchedValue2", + "watchedValue3", + "watchedValue4" ], "localized": ["name", "description"], "name": [ @@ -684,11 +684,11 @@ "description": [ { "language_code": "en_us", - "string": "Send a notification if selected values change. Use CTRL + Click to select/deselect.
  • Watched_Value1 is Hostname (not discoverable)
  • Watched_Value2 is Router IP
  • Watched_Value3 is not used
  • Watched_Value4 is not used
" + "string": "Send a notification if selected values change. Use CTRL + Click to select/deselect.
  • watchedValue1 is Hostname (not discoverable)
  • watchedValue2 is Router IP
  • watchedValue3 is not used
  • watchedValue4 is not used
" }, { "language_code": "es_es", - "string": "Envíe una notificación si los valores seleccionados cambian. Utilice CTRL + clic para seleccionar/deseleccionar.
  • Watched_Value1 es el nombre de host (no detectable)
  • Watched_Value2 es la IP del enrutador
  • Watched_Value3< /code> no se utiliza
  • Watched_Value4 no se utiliza
" + "string": "Envíe una notificación si los valores seleccionados cambian. Utilice CTRL + clic para seleccionar/deseleccionar.
  • watchedValue1 es el nombre de host (no detectable)
  • watchedValue2 es la IP del enrutador
  • watchedValue3< /code> no se utiliza
  • watchedValue4 no se utiliza
" } ] }, @@ -725,11 +725,11 @@ "description": [ { "language_code": "en_us", - "string": "Send a notification only on these statuses. new means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. watched-changed means that selected Watched_ValueN columns changed." + "string": "Send a notification only on these statuses. new means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. watched-changed means that selected watchedValueN columns changed." }, { "language_code": "es_es", - "string": "Envíe una notificación solo en estos estados. new significa que se descubrió un nuevo objeto único (una combinación única de PrimaryId y SecondaryId). watched-changed significa que las columnas Watched_ValueN seleccionadas cambiaron." + "string": "Envíe una notificación solo en estos estados. new significa que se descubrió un nuevo objeto único (una combinación única de PrimaryId y SecondaryId). watched-changed significa que las columnas watchedValueN seleccionadas cambiaron." } ] } diff --git a/front/plugins/sync/config.json b/front/plugins/sync/config.json index bb98a239..1c5695d2 100755 --- a/front/plugins/sync/config.json +++ b/front/plugins/sync/config.json @@ -7,7 +7,7 @@ "mapped_to_table": "CurrentScan", "data_filters": [ { - "compare_column": "Object_PrimaryID", + "compare_column": "objectPrimaryId", "compare_operator": "==", "compare_field_id": "txtMacFilter", "compare_js_template": "'{value}'.toString()", @@ -667,7 +667,7 @@ ], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -684,7 +684,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "mapped_to_column": "scanMac", "css_classes": "col-sm-3", "show": true, @@ -710,7 +710,7 @@ ] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "mapped_to_column": "scanLastIP", "css_classes": "col-sm-2", "show": true, @@ -736,7 +736,7 @@ ] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "mapped_to_column": "scanName", "css_classes": "col-sm-2", "show": true, @@ -754,7 +754,7 @@ ] }, { - "column": "Watched_Value2", + "column": "watchedValue2", "mapped_to_column": "scanVendor", "css_classes": "col-sm-2", "show": true, @@ -772,7 +772,7 @@ ] }, { - "column": "Watched_Value3", + "column": "watchedValue3", "mapped_to_column": "scanSyncHubNode", "css_classes": "col-sm-2", "show": true, @@ -790,7 +790,7 @@ ] }, { - "column": "Watched_Value4", + "column": "watchedValue4", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -836,7 +836,7 @@ ] }, { - "column": "DateTimeCreated", + "column": "dateTimeCreated", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -861,7 +861,7 @@ ] }, { - "column": "DateTimeChanged", + "column": "dateTimeChanged", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -886,7 +886,7 @@ ] }, { - "column": "Status", + "column": "status", "css_classes": "col-sm-1", "show": true, "type": "replace", diff --git a/front/plugins/unifi_api_import/config.json b/front/plugins/unifi_api_import/config.json index 8ee8fba7..423e486b 100755 --- a/front/plugins/unifi_api_import/config.json +++ b/front/plugins/unifi_api_import/config.json @@ -8,7 +8,7 @@ "mapped_to_table": "CurrentScan", "data_filters": [ { - "compare_column": "Object_PrimaryID", + "compare_column": "objectPrimaryId", "compare_operator": "==", "compare_field_id": "txtMacFilter", "compare_js_template": "'{value}'.toString()", @@ -569,7 +569,7 @@ ], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -586,7 +586,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "mapped_to_column": "scanMac", "css_classes": "col-sm-3", "show": true, @@ -604,7 +604,7 @@ ] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "mapped_to_column": "scanLastIP", "css_classes": "col-sm-2", "show": true, @@ -622,7 +622,7 @@ ] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "mapped_to_column": "scanName", "css_classes": "col-sm-2", "show": true, @@ -640,7 +640,7 @@ ] }, { - "column": "Watched_Value2", + "column": "watchedValue2", "mapped_to_column": "scanType", "css_classes": "col-sm-2", "show": true, @@ -658,7 +658,7 @@ ] }, { - "column": "Watched_Value3", + "column": "watchedValue3", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -675,7 +675,7 @@ ] }, { - "column": "Watched_Value4", + "column": "watchedValue4", "mapped_to_column": "scanParentMAC", "css_classes": "col-sm-2", "show": true, @@ -714,7 +714,7 @@ ] }, { - "column": "DateTimeCreated", + "column": "dateTimeCreated", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -731,7 +731,7 @@ ] }, { - "column": "DateTimeChanged", + "column": "dateTimeChanged", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -748,7 +748,7 @@ ] }, { - "column": "Status", + "column": "status", "css_classes": "col-sm-1", "show": true, "type": "replace", diff --git a/front/plugins/unifi_import/config.json b/front/plugins/unifi_import/config.json index ce215cc9..b5274514 100755 --- a/front/plugins/unifi_import/config.json +++ b/front/plugins/unifi_import/config.json @@ -72,7 +72,7 @@ ], "data_filters": [ { - "compare_column": "Object_PrimaryID", + "compare_column": "objectPrimaryId", "compare_field_id": "txtMacFilter", "compare_js_template": "'{value}'.toString()", "compare_operator": "==", @@ -81,7 +81,7 @@ ], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -96,7 +96,7 @@ ] }, { - "column": "Plugin", + "column": "plugin", "css_classes": "col-sm-2", "default_value": "", "localized": ["name"], @@ -119,7 +119,7 @@ "type": "label" }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "css_classes": "col-sm-2", "default_value": "", "localized": ["name"], @@ -143,7 +143,7 @@ "type": "device_mac" }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "css_classes": "col-sm-2", "default_value": "", "localized": ["name"], @@ -167,7 +167,7 @@ "type": "device_ip" }, { - "column": "DateTimeCreated", + "column": "dateTimeCreated", "css_classes": "col-sm-2", "default_value": "", "localized": ["name"], @@ -190,7 +190,7 @@ "type": "label" }, { - "column": "DateTimeChanged", + "column": "dateTimeChanged", "css_classes": "col-sm-2", "default_value": "", "localized": ["name"], @@ -214,7 +214,7 @@ "type": "label" }, { - "column": "Watched_Value1", + "column": "watchedValue1", "css_classes": "col-sm-2", "default_value": "", "localized": ["name"], @@ -238,7 +238,7 @@ "type": "label" }, { - "column": "Watched_Value2", + "column": "watchedValue2", "mapped_to_column": "scanVendor", "css_classes": "col-sm-2", "default_value": "", @@ -262,7 +262,7 @@ "type": "label" }, { - "column": "Watched_Value3", + "column": "watchedValue3", "mapped_to_column": "scanType", "css_classes": "col-sm-2", "default_value": "", @@ -286,7 +286,7 @@ "type": "label" }, { - "column": "Watched_Value4", + "column": "watchedValue4", "css_classes": "col-sm-2", "default_value": "", "localized": ["name"], @@ -309,7 +309,7 @@ "type": "label" }, { - "column": "UserData", + "column": "userData", "css_classes": "col-sm-2", "default_value": "", "localized": ["name"], @@ -359,7 +359,7 @@ "type": "label" }, { - "column": "Extra", + "column": "extra", "css_classes": "col-sm-3", "default_value": "", "localized": ["name"], @@ -382,7 +382,7 @@ "type": "label" }, { - "column": "HelpVal1", + "column": "helpVal1", "mapped_to_column": "scanParentMAC", "css_classes": "col-sm-2", "default_value": "", @@ -398,7 +398,7 @@ "type": "label" }, { - "column": "HelpVal2", + "column": "helpVal2", "mapped_to_column": "scanParentPort", "css_classes": "col-sm-2", "default_value": "", @@ -414,7 +414,7 @@ "type": "label" }, { - "column": "Status", + "column": "status", "css_classes": "col-sm-1", "default_value": "", "localized": ["name"], @@ -992,15 +992,15 @@ ] }, { - "default_value": ["Watched_Value1", "Watched_Value4"], + "default_value": ["watchedValue1", "watchedValue4"], "description": [ { "language_code": "en_us", - "string": "Send a notification if selected values change. Use CTRL + Click to select/deselect.
  • Watched_Value1 is Hostname
  • Watched_Value2 is Vendor
  • Watched_Value3 is Type
  • Watched_Value4 is Online
" + "string": "Send a notification if selected values change. Use CTRL + Click to select/deselect.
  • watchedValue1 is Hostname
  • watchedValue2 is Vendor
  • watchedValue3 is Type
  • watchedValue4 is Online
" }, { "language_code": "es_es", - "string": "Envíe una notificación si los valores seleccionados cambian. Utilice CTRL + clic para seleccionar/deseleccionar.
  • Watched_Value1 es el nombre de host
  • Watched_Value2 es el proveedor
  • Watched_Value3 es el tipo
  • Watched_Value4 es Online
" + "string": "Envíe una notificación si los valores seleccionados cambian. Utilice CTRL + clic para seleccionar/deseleccionar.
  • watchedValue1 es el nombre de host
  • watchedValue2 es el proveedor
  • watchedValue3 es el tipo
  • watchedValue4 es Online
" } ], "function": "WATCH", @@ -1016,10 +1016,10 @@ } ], "options": [ - "Watched_Value1", - "Watched_Value2", - "Watched_Value3", - "Watched_Value4" + "watchedValue1", + "watchedValue2", + "watchedValue3", + "watchedValue4" ], "type": { "dataType": "array", @@ -1037,11 +1037,11 @@ "description": [ { "language_code": "en_us", - "string": "Send a notification only on these statuses. new means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. watched-changed means that selected Watched_ValueN columns changed." + "string": "Send a notification only on these statuses. new means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. watched-changed means that selected watchedValueN columns changed." }, { "language_code": "es_es", - "string": "Envíe una notificación solo en estos estados. new significa que se descubrió un nuevo objeto único (una combinación única de PrimaryId y SecondaryId). watched-changed significa que las columnas Watched_ValueN seleccionadas cambiaron." + "string": "Envíe una notificación solo en estos estados. new significa que se descubrió un nuevo objeto único (una combinación única de PrimaryId y SecondaryId). watched-changed significa que las columnas watchedValueN seleccionadas cambiaron." } ], "function": "REPORT_ON", diff --git a/front/plugins/vendor_update/config.json b/front/plugins/vendor_update/config.json index 79f29f93..17f1f925 100755 --- a/front/plugins/vendor_update/config.json +++ b/front/plugins/vendor_update/config.json @@ -304,12 +304,12 @@ } ] }, - "default_value": ["Watched_Value1"], + "default_value": ["watchedValue1"], "options": [ - "Watched_Value1", - "Watched_Value2", - "Watched_Value3", - "Watched_Value4" + "watchedValue1", + "watchedValue2", + "watchedValue3", + "watchedValue4" ], "localized": ["name", "description"], "name": [ @@ -329,11 +329,11 @@ "description": [ { "language_code": "en_us", - "string": "Send a notification if selected values change. Use CTRL + Click to select/deselect.
  • Watched_Value1 is vendor name
  • Watched_Value2 is device name
  • Watched_Value3 unused
  • Watched_Value4 unused
" + "string": "Send a notification if selected values change. Use CTRL + Click to select/deselect.
  • watchedValue1 is vendor name
  • watchedValue2 is device name
  • watchedValue3 unused
  • watchedValue4 unused
" }, { "language_code": "de_de", - "string": "Sende eine Benachrichtigung, wenn ein ausgwählter Wert sich ändert. STRG + klicken zum aus-/abwählen.
  • Watched_Value1 ist der Herstellername
  • Watched_Value2 ist der Gerätename
  • Watched_Value3 ist nicht in Verwendung
  • Watched_Value4 ist nicht in Verwendung
" + "string": "Sende eine Benachrichtigung, wenn ein ausgwählter Wert sich ändert. STRG + klicken zum aus-/abwählen.
  • watchedValue1 ist der Herstellername
  • watchedValue2 ist der Gerätename
  • watchedValue3 ist nicht in Verwendung
  • watchedValue4 ist nicht in Verwendung
" } ] }, @@ -374,22 +374,22 @@ "description": [ { "language_code": "en_us", - "string": "Send a notification only on these statuses. new means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. watched-changed means that selected Watched_ValueN columns changed." + "string": "Send a notification only on these statuses. new means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. watched-changed means that selected watchedValueN columns changed." }, { "language_code": "es_es", - "string": "Envíe una notificación solo en estos estados. new significa que se descubrió un nuevo objeto único (combinación única de PrimaryId y SecondaryId). watched-changed significa que seleccionó Watched_ValueN Las columnas cambiaron." + "string": "Envíe una notificación solo en estos estados. new significa que se descubrió un nuevo objeto único (combinación única de PrimaryId y SecondaryId). watched-changed significa que seleccionó watchedValueN Las columnas cambiaron." }, { "language_code": "de_de", - "string": "Benachrichtige nur bei diesen Status. new bedeutet ein neues eindeutiges (einzigartige Kombination aus PrimaryId und SecondaryId) Objekt wurde erkennt. watched-changed bedeutet eine ausgewählte Watched_ValueN-Spalte hat sich geändert." + "string": "Benachrichtige nur bei diesen Status. new bedeutet ein neues eindeutiges (einzigartige Kombination aus PrimaryId und SecondaryId) Objekt wurde erkennt. watched-changed bedeutet eine ausgewählte watchedValueN-Spalte hat sich geändert." } ] } ], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -404,7 +404,7 @@ ] }, { - "column": "Plugin", + "column": "plugin", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -427,7 +427,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "mapped_to_column": "scanMac", "css_classes": "col-sm-2", "show": true, @@ -451,7 +451,7 @@ ] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "mapped_to_column": "scanLastIP", "css_classes": "col-sm-2", "show": true, @@ -475,7 +475,7 @@ ] }, { - "column": "DateTimeCreated", + "column": "dateTimeCreated", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -498,7 +498,7 @@ ] }, { - "column": "DateTimeChanged", + "column": "dateTimeChanged", "mapped_to_column": "scanLastConnection", "css_classes": "col-sm-2", "show": true, @@ -549,7 +549,7 @@ ] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "mapped_to_column": "scanVendor", "css_classes": "col-sm-2", "show": true, @@ -569,7 +569,7 @@ ] }, { - "column": "Watched_Value2", + "column": "watchedValue2", "mapped_to_column": "scanName", "css_classes": "col-sm-2", "show": true, @@ -593,7 +593,7 @@ ] }, { - "column": "Watched_Value3", + "column": "watchedValue3", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -612,7 +612,7 @@ ] }, { - "column": "Watched_Value4", + "column": "watchedValue4", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -631,7 +631,7 @@ ] }, { - "column": "UserData", + "column": "userData", "css_classes": "col-sm-2", "show": false, "type": "textbox_save", @@ -654,7 +654,7 @@ ] }, { - "column": "Extra", + "column": "extra", "css_classes": "col-sm-3", "show": false, "type": "label", @@ -673,7 +673,7 @@ ] }, { - "column": "Status", + "column": "status", "css_classes": "col-sm-1", "show": true, "type": "replace", diff --git a/front/plugins/wake_on_lan/config.json b/front/plugins/wake_on_lan/config.json index ed9233f2..38f7fc01 100755 --- a/front/plugins/wake_on_lan/config.json +++ b/front/plugins/wake_on_lan/config.json @@ -7,7 +7,7 @@ "data_source": "script", "data_filters": [ { - "compare_column": "Object_PrimaryID", + "compare_column": "objectPrimaryId", "compare_operator": "==", "compare_field_id": "txtMacFilter", "compare_js_template": "'{value}'.toString()", @@ -356,7 +356,7 @@ ], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -371,7 +371,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "css_classes": "col-sm-2", "show": true, "type": "device_name_mac", @@ -386,7 +386,7 @@ ] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "css_classes": "col-sm-2", "show": true, "type": "device_ip", @@ -401,7 +401,7 @@ ] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -416,7 +416,7 @@ ] }, { - "column": "Watched_Value2", + "column": "watchedValue2", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -431,7 +431,7 @@ ] }, { - "column": "Watched_Value3", + "column": "watchedValue3", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -446,7 +446,7 @@ ] }, { - "column": "Watched_Value4", + "column": "watchedValue4", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -476,7 +476,7 @@ ] }, { - "column": "DateTimeCreated", + "column": "dateTimeCreated", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -491,7 +491,7 @@ ] }, { - "column": "DateTimeChanged", + "column": "dateTimeChanged", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -506,7 +506,7 @@ ] }, { - "column": "Status", + "column": "status", "css_classes": "col-sm-1", "show": true, "type": "replace", diff --git a/front/plugins/website_monitor/config.json b/front/plugins/website_monitor/config.json index 9aeb11f0..251605fe 100755 --- a/front/plugins/website_monitor/config.json +++ b/front/plugins/website_monitor/config.json @@ -51,7 +51,7 @@ ], "database_column_definitions": [ { - "column": "Index", + "column": "index", "css_classes": "col-sm-2", "show": true, "type": "none", @@ -66,7 +66,7 @@ ] }, { - "column": "Plugin", + "column": "plugin", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -85,7 +85,7 @@ ] }, { - "column": "Object_PrimaryID", + "column": "objectPrimaryId", "css_classes": "col-sm-2", "show": true, "type": "url", @@ -104,7 +104,7 @@ ] }, { - "column": "Object_SecondaryID", + "column": "objectSecondaryId", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -123,7 +123,7 @@ ] }, { - "column": "DateTimeCreated", + "column": "dateTimeCreated", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -142,7 +142,7 @@ ] }, { - "column": "DateTimeChanged", + "column": "dateTimeChanged", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -161,7 +161,7 @@ ] }, { - "column": "Watched_Value1", + "column": "watchedValue1", "css_classes": "col-sm-2", "show": true, "type": "threshold", @@ -201,7 +201,7 @@ ] }, { - "column": "Watched_Value2", + "column": "watchedValue2", "css_classes": "col-sm-2", "show": true, "type": "label", @@ -220,7 +220,7 @@ ] }, { - "column": "Watched_Value3", + "column": "watchedValue3", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -239,7 +239,7 @@ ] }, { - "column": "Watched_Value4", + "column": "watchedValue4", "css_classes": "col-sm-2", "show": false, "type": "label", @@ -258,7 +258,7 @@ ] }, { - "column": "UserData", + "column": "userData", "css_classes": "col-sm-2", "show": true, "type": "textbox_save", @@ -277,7 +277,7 @@ ] }, { - "column": "Status", + "column": "status", "css_classes": "col-sm-1", "show": true, "type": "replace", @@ -313,7 +313,7 @@ ] }, { - "column": "Extra", + "column": "extra", "css_classes": "col-sm-3", "show": false, "type": "label", @@ -543,12 +543,12 @@ } ] }, - "default_value": ["Watched_Value1"], + "default_value": ["watchedValue1"], "options": [ - "Watched_Value1", - "Watched_Value2", - "Watched_Value3", - "Watched_Value4" + "watchedValue1", + "watchedValue2", + "watchedValue3", + "watchedValue4" ], "localized": ["name", "description"], "name": [ @@ -564,11 +564,11 @@ "description": [ { "language_code": "en_us", - "string": "Send a notification if selected values change. Use CTRL + Click to select/deselect.
  • Watched_Value1 is response status code (e.g.: 200, 404)
  • Watched_Value2 is Latency (not recommended)
  • Watched_Value3 unused
  • Watched_Value4 unused
" + "string": "Send a notification if selected values change. Use CTRL + Click to select/deselect.
  • watchedValue1 is response status code (e.g.: 200, 404)
  • watchedValue2 is Latency (not recommended)
  • watchedValue3 unused
  • watchedValue4 unused
" }, { "language_code": "es_es", - "string": "Envíe una notificación si los valores seleccionados cambian. Use CTRL + Click para seleccionar/deseleccionar.
  • Watched_Value1 es un código de estado de respuesta (por ejemplo: 200, 404)
  • Valor_observado2 es Latencia (no recomendado)
  • Valor_observado3 no utilizado
  • Valor_observado4 sin usar
" + "string": "Envíe una notificación si los valores seleccionados cambian. Use CTRL + Click para seleccionar/deseleccionar.
  • watchedValue1 es un código de estado de respuesta (por ejemplo: 200, 404)
  • watchedValue2 es Latencia (no recomendado)
  • watchedValue3 no utilizado
  • watchedValue4 sin usar
" } ] }, @@ -605,11 +605,11 @@ "description": [ { "language_code": "en_us", - "string": "Send a notification only on these statuses. new means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. watched-changed means that selected Watched_ValueN columns changed." + "string": "Send a notification only on these statuses. new means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. watched-changed means that selected watchedValueN columns changed." }, { "language_code": "es_es", - "string": "Envíe una notificación solo en estos estados. new significa que se descubrió un nuevo objeto único (combinación única de PrimaryId y SecondaryId). watched-changed significa que seleccionó Watched_ValueN Las columnas cambiaron." + "string": "Envíe una notificación solo en estos estados. new significa que se descubrió un nuevo objeto único (combinación única de PrimaryId y SecondaryId). watched-changed significa que seleccionó watchedValueN Las columnas cambiaron." } ] }, diff --git a/front/pluginsCore.php b/front/pluginsCore.php index 32566603..1e19ff07 100755 --- a/front/pluginsCore.php +++ b/front/pluginsCore.php @@ -246,9 +246,9 @@ function genericSaveData (id) { headers: { "Authorization": `Bearer ${apiToken}` }, data: JSON.stringify({ dbtable: "Plugins_Objects", - columnName: "Index", + columnName: "index", id: index, - columns: "UserData", + columns: "userData", values: columnValue }), contentType: "application/json", @@ -413,26 +413,26 @@ function getColumnDefinitions(pluginObj) { function getEventData(prefix, colDefinitions, pluginObj) { // Extract event data specific to the plugin and format it for DataTables return pluginUnprocessedEvents - .filter(event => event.Plugin === prefix && shouldBeShown(event, pluginObj)) // Filter events for the specific plugin + .filter(event => event.plugin === prefix && shouldBeShown(event, pluginObj)) // Filter events for the specific plugin .map(event => colDefinitions.map(colDef => event[colDef.column] || '')); // Map to the defined columns } function getObjectData(prefix, colDefinitions, pluginObj) { // Extract object data specific to the plugin and format it for DataTables return pluginObjects - .filter(object => object.Plugin === prefix && shouldBeShown(object, pluginObj)) // Filter objects for the specific plugin - .map(object => colDefinitions.map(colDef => getFormControl(colDef, object[colDef.column], object["Index"], colDefinitions, object))); // Map to the defined columns + .filter(object => object.plugin === prefix && shouldBeShown(object, pluginObj)) // Filter objects for the specific plugin + .map(object => colDefinitions.map(colDef => getFormControl(colDef, object[colDef.column], object["index"], colDefinitions, object))); // Map to the defined columns } function getHistoryData(prefix, colDefinitions, pluginObj) { return pluginHistory - .filter(history => history.Plugin === prefix && shouldBeShown(history, pluginObj)) // First, filter based on the plugin prefix - .sort((a, b) => b.Index - a.Index) // Then, sort by the Index field in descending order + .filter(history => history.plugin === prefix && shouldBeShown(history, pluginObj)) // First, filter based on the plugin prefix + .sort((a, b) => b.index - a.index) // Then, sort by the Index field in descending order .slice(0, 50) // Limit the result to the first 50 entries .map(object => colDefinitions.map(colDef => - getFormControl(colDef, object[colDef.column], object["Index"], colDefinitions, object) + getFormControl(colDef, object[colDef.column], object["index"], colDefinitions, object) ) ); } diff --git a/front/report.php b/front/report.php index 498eb06b..d27efdc8 100755 --- a/front/report.php +++ b/front/report.php @@ -43,9 +43,9 @@ @@ -100,23 +100,23 @@ // Display the selected format data and update timestamp switch (format) { - case 'HTML': + case 'html': notificationData.innerHTML = formatData; break; - case 'JSON': + case 'json': notificationData.innerHTML = `
                                                       ${jsonSyntaxHighlight(JSON.stringify(JSON.parse(formatData), undefined, 4))}
                                                     
`; break; - case 'Text': + case 'text': notificationData.innerHTML = `
${formatData}
`; break; } // console.log(notification) - timestamp.textContent = localizeTimestamp(notification.DateTimeCreated); - notiGuid.textContent = notification.GUID; + timestamp.textContent = localizeTimestamp(notification.dateTimeCreated); + notiGuid.textContent = notification.guid; currentIndex = index; $("#notificationOutOff").html(`${currentIndex + 1}/${data.data.length}`); @@ -131,7 +131,7 @@ // Function to find the index of a notification by GUID function findIndexByGUID(data, guid) { - return data.findIndex(notification => notification.GUID == guid); + return data.findIndex(notification => notification.guid == guid); } // Listen for format selection changes diff --git a/front/report_templates/webhook_json_sample.json b/front/report_templates/webhook_json_sample.json index c252e4e6..f8201ab9 100755 --- a/front/report_templates/webhook_json_sample.json +++ b/front/report_templates/webhook_json_sample.json @@ -42,11 +42,11 @@ "title": "🔴 Down devices", "columnNames": [ "devName", - "eve_MAC", + "eveMac", "devVendor", - "eve_IP", - "eve_DateTime", - "eve_EventType" + "eveIp", + "eveDateTime", + "eveEventType" ] }, "down_devices": [], @@ -64,22 +64,22 @@ "down_reconnected": [ { "devName": "Phone - Moto 82", - "eve_MAC": "74:ac:74:ac:74:ac", + "eveMac": "74:ac:74:ac:74:ac", "devVendor": "Motorola Mobility LLC, a Lenovo Company", - "eve_IP": "192.168.1.167", - "eve_DateTime": "2025-01-11 10:05:01+11:00", - "eve_EventType": "Down Reconnected" + "eveIp": "192.168.1.167", + "eveDateTime": "2025-01-11 10:05:01+11:00", + "eveEventType": "Down Reconnected" } ], "down_reconnected_meta": { "title": "🔁 Reconnected down devices", "columnNames": [ "devName", - "eve_MAC", + "eveMac", "devVendor", - "eve_IP", - "eve_DateTime", - "eve_EventType" + "eveIp", + "eveDateTime", + "eveEventType" ] }, "events": [ @@ -103,28 +103,28 @@ "plugins_meta": { "title": "🔌 Plugins", "columnNames": [ - "Plugin", - "Object_PrimaryID", - "Object_SecondaryID", - "DateTimeChanged", - "Watched_Value1", - "Watched_Value2", - "Watched_Value3", - "Watched_Value4", - "Status" + "plugin", + "objectPrimaryId", + "objectSecondaryId", + "dateTimeChanged", + "watchedValue1", + "watchedValue2", + "watchedValue3", + "watchedValue4", + "status" ] }, "plugins": [ { - "Plugin": "ARPSCAN", - "Object_PrimaryID": "74:ac:74:ac:74:ac", - "Object_SecondaryID": "192.168.1.114", - "DateTimeChanged": "2025-01-11 12:21:00", - "Watched_Value1": "192.168.1.114", - "Watched_Value2": "Microsoft Corporation", - "Watched_Value3": "192.168.1.0/24 --interface=eth1", - "Watched_Value4": "", - "Status": "new" + "plugin": "ARPSCAN", + "objectPrimaryId": "74:ac:74:ac:74:ac", + "objectSecondaryId": "192.168.1.114", + "dateTimeChanged": "2025-01-11 12:21:00", + "watchedValue1": "192.168.1.114", + "watchedValue2": "Microsoft Corporation", + "watchedValue3": "192.168.1.0/24 --interface=eth1", + "watchedValue4": "", + "status": "new" } ] } diff --git a/scripts/db_cleanup/db_cleanup.py b/scripts/db_cleanup/db_cleanup.py index e55ee5e6..39e782af 100755 --- a/scripts/db_cleanup/db_cleanup.py +++ b/scripts/db_cleanup/db_cleanup.py @@ -34,12 +34,12 @@ def check_and_clean_device(): # Check all tables for MAC tables_checks = [ - f"SELECT 'Events' as source, * FROM Events WHERE eve_MAC='{mac}'", - f"SELECT 'Devices' as source, * FROM Devices WHERE dev_MAC='{mac}'", + f"SELECT 'Events' as source, * FROM Events WHERE eveMac='{mac}'", + f"SELECT 'Devices' as source, * FROM Devices WHERE devMac='{mac}'", f"SELECT 'CurrentScan' as source, * FROM CurrentScan WHERE scanMac='{mac}'", f"SELECT 'Notifications' as source, * FROM Notifications WHERE JSON LIKE '%{mac}%'", - f"SELECT 'AppEvents' as source, * FROM AppEvents WHERE ObjectPrimaryID LIKE '%{mac}%' OR ObjectSecondaryID LIKE '%{mac}%'", - f"SELECT 'Plugins_Objects' as source, * FROM Plugins_Objects WHERE Object_PrimaryID LIKE '%{mac}%'" + f"SELECT 'AppEvents' as source, * FROM AppEvents WHERE objectPrimaryId LIKE '%{mac}%' OR objectSecondaryId LIKE '%{mac}%'", + f"SELECT 'Plugins_Objects' as source, * FROM Plugins_Objects WHERE objectPrimaryId LIKE '%{mac}%'" ] found = False @@ -54,12 +54,12 @@ def check_and_clean_device(): if confirm.lower() == 'y': # Delete from all tables deletes = [ - f"DELETE FROM Events WHERE eve_MAC='{mac}'", - f"DELETE FROM Devices WHERE dev_MAC='{mac}'", + f"DELETE FROM Events WHERE eveMac='{mac}'", + f"DELETE FROM Devices WHERE devMac='{mac}'", f"DELETE FROM CurrentScan WHERE scanMac='{mac}'", f"DELETE FROM Notifications WHERE JSON LIKE '%{mac}%'", - f"DELETE FROM AppEvents WHERE ObjectPrimaryID LIKE '%{mac}%' OR ObjectSecondaryID LIKE '%{mac}%'", - f"DELETE FROM Plugins_Objects WHERE Object_PrimaryID LIKE '%{mac}%'" + f"DELETE FROM AppEvents WHERE objectPrimaryId LIKE '%{mac}%' OR objectSecondaryId LIKE '%{mac}%'", + f"DELETE FROM Plugins_Objects WHERE objectPrimaryId LIKE '%{mac}%'" ] for delete in deletes: @@ -73,12 +73,12 @@ def check_and_clean_device(): # Check all tables for IP tables_checks = [ - f"SELECT 'Events' as source, * FROM Events WHERE eve_IP='{ip}'", - f"SELECT 'Devices' as source, * FROM Devices WHERE dev_LastIP='{ip}'", + f"SELECT 'Events' as source, * FROM Events WHERE eveIp='{ip}'", + f"SELECT 'Devices' as source, * FROM Devices WHERE devLastIp='{ip}'", f"SELECT 'CurrentScan' as source, * FROM CurrentScan WHERE scanLastIP='{ip}'", f"SELECT 'Notifications' as source, * FROM Notifications WHERE JSON LIKE '%{ip}%'", - f"SELECT 'AppEvents' as source, * FROM AppEvents WHERE ObjectSecondaryID LIKE '%{ip}%'", - f"SELECT 'Plugins_Objects' as source, * FROM Plugins_Objects WHERE Object_SecondaryID LIKE '%{ip}%'" + f"SELECT 'AppEvents' as source, * FROM AppEvents WHERE objectSecondaryId LIKE '%{ip}%'", + f"SELECT 'Plugins_Objects' as source, * FROM Plugins_Objects WHERE objectSecondaryId LIKE '%{ip}%'" ] found = False @@ -93,12 +93,12 @@ def check_and_clean_device(): if confirm.lower() == 'y': # Delete from all tables deletes = [ - f"DELETE FROM Events WHERE eve_IP='{ip}'", - f"DELETE FROM Devices WHERE dev_LastIP='{ip}'", + f"DELETE FROM Events WHERE eveIp='{ip}'", + f"DELETE FROM Devices WHERE devLastIp='{ip}'", f"DELETE FROM CurrentScan WHERE scanLastIP='{ip}'", f"DELETE FROM Notifications WHERE JSON LIKE '%{ip}%'", - f"DELETE FROM AppEvents WHERE ObjectSecondaryID LIKE '%{ip}%'", - f"DELETE FROM Plugins_Objects WHERE Object_SecondaryID LIKE '%{ip}%'" + f"DELETE FROM AppEvents WHERE objectSecondaryId LIKE '%{ip}%'", + f"DELETE FROM Plugins_Objects WHERE objectSecondaryId LIKE '%{ip}%'" ] for delete in deletes: diff --git a/server/api_server/graphql_endpoint.py b/server/api_server/graphql_endpoint.py index ba417d84..99850800 100755 --- a/server/api_server/graphql_endpoint.py +++ b/server/api_server/graphql_endpoint.py @@ -154,30 +154,30 @@ class LangStringResult(ObjectType): # --- APP EVENTS --- class AppEvent(ObjectType): - Index = Int(description="Internal index") - GUID = String(description="Unique event GUID") - AppEventProcessed = Int(description="Processing status (0 or 1)") - DateTimeCreated = String(description="Event creation timestamp") + index = Int(description="Internal index") + guid = String(description="Unique event GUID") + appEventProcessed = Int(description="Processing status (0 or 1)") + dateTimeCreated = String(description="Event creation timestamp") - ObjectType = String(description="Type of the related object (Device, Setting, etc.)") - ObjectGUID = String(description="GUID of the related object") - ObjectPlugin = String(description="Plugin associated with the object") - ObjectPrimaryID = String(description="Primary identifier of the object") - ObjectSecondaryID = String(description="Secondary identifier of the object") - ObjectForeignKey = String(description="Foreign key reference") - ObjectIndex = Int(description="Object index") + objectType = String(description="Type of the related object (Device, Setting, etc.)") + objectGuid = String(description="GUID of the related object") + objectPlugin = String(description="Plugin associated with the object") + objectPrimaryId = String(description="Primary identifier of the object") + objectSecondaryId = String(description="Secondary identifier of the object") + objectForeignKey = String(description="Foreign key reference") + objectIndex = Int(description="Object index") - ObjectIsNew = Int(description="Is the object new? (0 or 1)") - ObjectIsArchived = Int(description="Is the object archived? (0 or 1)") - ObjectStatusColumn = String(description="Column used for status") - ObjectStatus = String(description="Object status value") + objectIsNew = Int(description="Is the object new? (0 or 1)") + objectIsArchived = Int(description="Is the object archived? (0 or 1)") + objectStatusColumn = String(description="Column used for status") + objectStatus = String(description="Object status value") - AppEventType = String(description="Type of application event") + appEventType = String(description="Type of application event") - Helper1 = String(description="Generic helper field 1") - Helper2 = String(description="Generic helper field 2") - Helper3 = String(description="Generic helper field 3") - Extra = String(description="Additional JSON data") + helper1 = String(description="Generic helper field 1") + helper2 = String(description="Generic helper field 2") + helper3 = String(description="Generic helper field 3") + extra = String(description="Additional JSON data") class AppEventResult(ObjectType): @@ -499,18 +499,18 @@ class Query(ObjectType): search_term = options.search.lower() searchable_fields = [ - "GUID", - "ObjectType", - "ObjectGUID", - "ObjectPlugin", - "ObjectPrimaryID", - "ObjectSecondaryID", - "ObjectStatus", - "AppEventType", - "Helper1", - "Helper2", - "Helper3", - "Extra", + "guid", + "objectType", + "objectGuid", + "objectPlugin", + "objectPrimaryId", + "objectSecondaryId", + "objectStatus", + "appEventType", + "helper1", + "helper2", + "helper3", + "extra", ] events_data = [ @@ -616,9 +616,9 @@ class Query(ObjectType): plugin_data = json.load(f).get("data", []) plugin_list = [ LangString( - langCode=entry.get("Language_Code"), - langStringKey=entry.get("String_Key"), - langStringText=entry.get("String_Value") + langCode=entry.get("languageCode"), + langStringKey=entry.get("stringKey"), + langStringText=entry.get("stringValue") ) for entry in plugin_data ] _langstrings_cache[cache_key] = plugin_list diff --git a/server/api_server/sessions_endpoint.py b/server/api_server/sessions_endpoint.py index f9435c84..dd6037d2 100755 --- a/server/api_server/sessions_endpoint.py +++ b/server/api_server/sessions_endpoint.py @@ -33,8 +33,8 @@ def create_session( cur.execute( """ - INSERT INTO Sessions (ses_MAC, ses_IP, ses_DateTimeConnection, ses_DateTimeDisconnection, - ses_EventTypeConnection, ses_EventTypeDisconnection) + INSERT INTO Sessions (sesMac, sesIp, sesDateTimeConnection, sesDateTimeDisconnection, + sesEventTypeConnection, sesEventTypeDisconnection) VALUES (?, ?, ?, ?, ?, ?) """, (mac, ip, start_time, end_time, event_type_conn, event_type_disc), @@ -52,7 +52,7 @@ def delete_session(mac): conn = get_temp_db_connection() cur = conn.cursor() - cur.execute("DELETE FROM Sessions WHERE ses_MAC = ?", (mac,)) + cur.execute("DELETE FROM Sessions WHERE sesMac = ?", (mac,)) conn.commit() conn.close() @@ -69,13 +69,13 @@ def get_sessions(mac=None, start_date=None, end_date=None): params = [] if mac: - sql += " AND ses_MAC = ?" + sql += " AND sesMac = ?" params.append(mac) if start_date: - sql += " AND ses_DateTimeConnection >= ?" + sql += " AND sesDateTimeConnection >= ?" params.append(start_date) if end_date: - sql += " AND ses_DateTimeDisconnection <= ?" + sql += " AND sesDateTimeDisconnection <= ?" params.append(end_date) cur.execute(sql, tuple(params)) @@ -106,49 +106,49 @@ def get_sessions_calendar(start_date, end_date, mac): sql = """ SELECT - SES1.ses_MAC, - SES1.ses_EventTypeConnection, - SES1.ses_DateTimeConnection, - SES1.ses_EventTypeDisconnection, - SES1.ses_DateTimeDisconnection, - SES1.ses_IP, - SES1.ses_AdditionalInfo, - SES1.ses_StillConnected, + SES1.sesMac, + SES1.sesEventTypeConnection, + SES1.sesDateTimeConnection, + SES1.sesEventTypeDisconnection, + SES1.sesDateTimeDisconnection, + SES1.sesIp, + SES1.sesAdditionalInfo, + SES1.sesStillConnected, CASE - WHEN SES1.ses_EventTypeConnection = '' THEN + WHEN SES1.sesEventTypeConnection = '' THEN IFNULL( ( - SELECT MAX(SES2.ses_DateTimeDisconnection) + SELECT MAX(SES2.sesDateTimeDisconnection) FROM Sessions AS SES2 - WHERE SES2.ses_MAC = SES1.ses_MAC - AND SES2.ses_DateTimeDisconnection < SES1.ses_DateTimeDisconnection - AND SES2.ses_DateTimeDisconnection BETWEEN Date(?) AND Date(?) + WHERE SES2.sesMac = SES1.sesMac + AND SES2.sesDateTimeDisconnection < SES1.sesDateTimeDisconnection + AND SES2.sesDateTimeDisconnection BETWEEN Date(?) AND Date(?) ), - DATETIME(SES1.ses_DateTimeDisconnection, '-1 hour') + DATETIME(SES1.sesDateTimeDisconnection, '-1 hour') ) - ELSE SES1.ses_DateTimeConnection - END AS ses_DateTimeConnectionCorrected, + ELSE SES1.sesDateTimeConnection + END AS sesDateTimeConnectionCorrected, CASE - WHEN SES1.ses_EventTypeDisconnection = '' THEN + WHEN SES1.sesEventTypeDisconnection = '' THEN ( - SELECT MIN(SES2.ses_DateTimeConnection) + SELECT MIN(SES2.sesDateTimeConnection) FROM Sessions AS SES2 - WHERE SES2.ses_MAC = SES1.ses_MAC - AND SES2.ses_DateTimeConnection > SES1.ses_DateTimeConnection - AND SES2.ses_DateTimeConnection BETWEEN Date(?) AND Date(?) + WHERE SES2.sesMac = SES1.sesMac + AND SES2.sesDateTimeConnection > SES1.sesDateTimeConnection + AND SES2.sesDateTimeConnection BETWEEN Date(?) AND Date(?) ) - ELSE SES1.ses_DateTimeDisconnection - END AS ses_DateTimeDisconnectionCorrected + ELSE SES1.sesDateTimeDisconnection + END AS sesDateTimeDisconnectionCorrected FROM Sessions AS SES1 WHERE ( - (SES1.ses_DateTimeConnection BETWEEN Date(?) AND Date(?)) - OR (SES1.ses_DateTimeDisconnection BETWEEN Date(?) AND Date(?)) - OR SES1.ses_StillConnected = 1 + (SES1.sesDateTimeConnection BETWEEN Date(?) AND Date(?)) + OR (SES1.sesDateTimeDisconnection BETWEEN Date(?) AND Date(?)) + OR SES1.sesStillConnected = 1 ) - AND (? IS NULL OR SES1.ses_MAC = ?) + AND (? IS NULL OR SES1.sesMac = ?) """ cur.execute( @@ -173,31 +173,31 @@ def get_sessions_calendar(start_date, end_date, mac): # Color logic (unchanged from PHP) if ( - row["ses_EventTypeConnection"] == "" or row["ses_EventTypeDisconnection"] == "" + row["sesEventTypeConnection"] == "" or row["sesEventTypeDisconnection"] == "" ): color = "#f39c12" - elif row["ses_StillConnected"] == 1: + elif row["sesStillConnected"] == 1: color = "#00a659" else: color = "#0073b7" # --- IMPORTANT FIX --- # FullCalendar v3 CANNOT handle end = null - end_dt = row["ses_DateTimeDisconnectionCorrected"] - if not end_dt and row["ses_StillConnected"] == 1: + end_dt = row["sesDateTimeDisconnectionCorrected"] + if not end_dt and row["sesStillConnected"] == 1: end_dt = now_iso tooltip = ( - f"Connection: {format_event_date(row['ses_DateTimeConnection'], row['ses_EventTypeConnection'])}\n" - f"Disconnection: {format_event_date(row['ses_DateTimeDisconnection'], row['ses_EventTypeDisconnection'])}\n" - f"IP: {row['ses_IP']}" + f"Connection: {format_event_date(row['sesDateTimeConnection'], row['sesEventTypeConnection'])}\n" + f"Disconnection: {format_event_date(row['sesDateTimeDisconnection'], row['sesEventTypeDisconnection'])}\n" + f"IP: {row['sesIp']}" ) events.append( { - "resourceId": row["ses_MAC"], + "resourceId": row["sesMac"], "title": "", - "start": format_date_iso(row["ses_DateTimeConnectionCorrected"]), + "start": format_date_iso(row["sesDateTimeConnectionCorrected"]), "end": format_date_iso(end_dt), "color": color, "tooltip": tooltip, @@ -219,20 +219,20 @@ def get_device_sessions(mac, period): sql = f""" SELECT - IFNULL(ses_DateTimeConnection, ses_DateTimeDisconnection) AS ses_DateTimeOrder, - ses_EventTypeConnection, - ses_DateTimeConnection, - ses_EventTypeDisconnection, - ses_DateTimeDisconnection, - ses_StillConnected, - ses_IP, - ses_AdditionalInfo + IFNULL(sesDateTimeConnection, sesDateTimeDisconnection) AS sesDateTimeOrder, + sesEventTypeConnection, + sesDateTimeConnection, + sesEventTypeDisconnection, + sesDateTimeDisconnection, + sesStillConnected, + sesIp, + sesAdditionalInfo FROM Sessions - WHERE ses_MAC = ? + WHERE sesMac = ? AND ( - ses_DateTimeConnection >= {period_date} - OR ses_DateTimeDisconnection >= {period_date} - OR ses_StillConnected = 1 + sesDateTimeConnection >= {period_date} + OR sesDateTimeDisconnection >= {period_date} + OR sesStillConnected = 1 ) """ @@ -245,44 +245,44 @@ def get_device_sessions(mac, period): for row in rows: # Connection DateTime - if row["ses_EventTypeConnection"] == "": - ini = row["ses_EventTypeConnection"] + if row["sesEventTypeConnection"] == "": + ini = row["sesEventTypeConnection"] else: - ini = format_date(row["ses_DateTimeConnection"]) + ini = format_date(row["sesDateTimeConnection"]) # Disconnection DateTime - if row["ses_StillConnected"]: + if row["sesStillConnected"]: end = "..." - elif row["ses_EventTypeDisconnection"] == "": - end = row["ses_EventTypeDisconnection"] + elif row["sesEventTypeDisconnection"] == "": + end = row["sesEventTypeDisconnection"] else: - end = format_date(row["ses_DateTimeDisconnection"]) + end = format_date(row["sesDateTimeDisconnection"]) # Duration - if row["ses_EventTypeConnection"] in ("", None) or row[ - "ses_EventTypeDisconnection" + if row["sesEventTypeConnection"] in ("", None) or row[ + "sesEventTypeDisconnection" ] in ("", None): dur = "..." - elif row["ses_StillConnected"]: - dur = format_date_diff(row["ses_DateTimeConnection"], None, tz_name)["text"] + elif row["sesStillConnected"]: + dur = format_date_diff(row["sesDateTimeConnection"], None, tz_name)["text"] else: - dur = format_date_diff(row["ses_DateTimeConnection"], row["ses_DateTimeDisconnection"], tz_name)["text"] + dur = format_date_diff(row["sesDateTimeConnection"], row["sesDateTimeDisconnection"], tz_name)["text"] # Additional Info - info = row["ses_AdditionalInfo"] - if row["ses_EventTypeConnection"] == "New Device": - info = f"{row['ses_EventTypeConnection']}: {info}" + info = row["sesAdditionalInfo"] + if row["sesEventTypeConnection"] == "New Device": + info = f"{row['sesEventTypeConnection']}: {info}" # Push row data table_data["data"].append( { - "ses_MAC": mac, - "ses_DateTimeOrder": row["ses_DateTimeOrder"], - "ses_Connection": ini, - "ses_Disconnection": end, - "ses_Duration": dur, - "ses_IP": row["ses_IP"], - "ses_Info": info, + "sesMac": mac, + "sesDateTimeOrder": row["sesDateTimeOrder"], + "sesConnection": ini, + "sesDisconnection": end, + "sesDuration": dur, + "sesIp": row["sesIp"], + "sesInfo": info, } ) @@ -307,42 +307,42 @@ def get_session_events(event_type, period_date): # Base SQLs sql_events = f""" SELECT - eve_DateTime AS eve_DateTimeOrder, + eveDateTime AS eveDateTimeOrder, devName, devOwner, - eve_DateTime, - eve_EventType, + eveDateTime, + eveEventType, NULL, NULL, NULL, NULL, - eve_IP, + eveIp, NULL, - eve_AdditionalInfo, + eveAdditionalInfo, NULL, devMac, - eve_PendingAlertEmail + evePendingAlertEmail FROM Events_Devices - WHERE eve_DateTime >= {period_date} + WHERE eveDateTime >= {period_date} """ sql_sessions = """ SELECT - IFNULL(ses_DateTimeConnection, ses_DateTimeDisconnection) AS ses_DateTimeOrder, + IFNULL(sesDateTimeConnection, sesDateTimeDisconnection) AS sesDateTimeOrder, devName, devOwner, NULL, NULL, - ses_DateTimeConnection, - ses_DateTimeDisconnection, + sesDateTimeConnection, + sesDateTimeDisconnection, NULL, NULL, - ses_IP, + sesIp, NULL, - ses_AdditionalInfo, - ses_StillConnected, + sesAdditionalInfo, + sesStillConnected, devMac, - 0 AS ses_PendingAlertEmail + 0 AS sesPendingAlertEmail FROM Sessions_Devices """ @@ -353,9 +353,9 @@ def get_session_events(event_type, period_date): sql = ( sql_sessions + f""" WHERE ( - ses_DateTimeConnection >= {period_date} - OR ses_DateTimeDisconnection >= {period_date} - OR ses_StillConnected = 1 + sesDateTimeConnection >= {period_date} + OR sesDateTimeDisconnection >= {period_date} + OR sesStillConnected = 1 ) """ ) @@ -363,17 +363,17 @@ def get_session_events(event_type, period_date): sql = ( sql_sessions + f""" WHERE ( - (ses_DateTimeConnection IS NULL AND ses_DateTimeDisconnection >= {period_date}) - OR (ses_DateTimeDisconnection IS NULL AND ses_StillConnected = 0 AND ses_DateTimeConnection >= {period_date}) + (sesDateTimeConnection IS NULL AND sesDateTimeDisconnection >= {period_date}) + OR (sesDateTimeDisconnection IS NULL AND sesStillConnected = 0 AND sesDateTimeConnection >= {period_date}) ) """ ) elif event_type == "voided": - sql = sql_events + ' AND eve_EventType LIKE "VOIDED%"' + sql = sql_events + ' AND eveEventType LIKE "VOIDED%"' elif event_type == "new": - sql = sql_events + ' AND eve_EventType = "New Device"' + sql = sql_events + ' AND eveEventType = "New Device"' elif event_type == "down": - sql = sql_events + ' AND eve_EventType = "Device Down"' + sql = sql_events + ' AND eveEventType = "Device Down"' else: sql = sql_events + " AND 1=0" diff --git a/server/const.py b/server/const.py index f8ea4782..135102ec 100755 --- a/server/const.py +++ b/server/const.py @@ -67,7 +67,7 @@ sql_devices_all = """ FROM DevicesView """ -sql_appevents = """select * from AppEvents order by DateTimeCreated desc""" +sql_appevents = """select * from AppEvents order by dateTimeCreated desc""" # The below query calculates counts of devices in various categories: # (connected/online, offline, down, new, archived), # as well as a combined count for devices that match any status listed in the UI_MY_DEVICES setting @@ -141,32 +141,32 @@ sql_devices_filters = """ sql_devices_stats = f""" SELECT - Online_Devices as online, - Down_Devices as down, - All_Devices as 'all', - Archived_Devices as archived, + onlineDevices as online, + downDevices as down, + allDevices as 'all', + archivedDevices as archived, (SELECT COUNT(*) FROM Devices a WHERE devIsNew = 1) as new, (SELECT COUNT(*) FROM Devices a WHERE devName IN ({NULL_EQUIVALENTS_SQL}) OR devName IS NULL) as unknown FROM Online_History - ORDER BY Scan_Date DESC + ORDER BY scanDate DESC LIMIT 1 """ -sql_events_pending_alert = "SELECT * FROM Events where eve_PendingAlertEmail is not 0" +sql_events_pending_alert = "SELECT * FROM Events where evePendingAlertEmail is not 0" sql_settings = "SELECT * FROM Settings" sql_plugins_objects = "SELECT * FROM Plugins_Objects" sql_language_strings = "SELECT * FROM Plugins_Language_Strings" sql_notifications_all = "SELECT * FROM Notifications" sql_online_history = "SELECT * FROM Online_History" sql_plugins_events = "SELECT * FROM Plugins_Events" -sql_plugins_history = "SELECT * FROM Plugins_History ORDER BY DateTimeChanged DESC" +sql_plugins_history = "SELECT * FROM Plugins_History ORDER BY dateTimeChanged DESC" sql_new_devices = """SELECT * FROM ( - SELECT eve_IP as devLastIP, - eve_MAC as devMac, - MAX(eve_DateTime) as lastEvent + SELECT eveIp as devLastIP, + eveMac as devMac, + MAX(eveDateTime) as lastEvent FROM Events_Devices - WHERE eve_PendingAlertEmail = 1 - AND eve_EventType = 'New Device' - GROUP BY eve_MAC + WHERE evePendingAlertEmail = 1 + AND eveEventType = 'New Device' + GROUP BY eveMac ORDER BY lastEvent ) t1 LEFT JOIN diff --git a/server/database.py b/server/database.py index 521e865b..c0c16d1d 100755 --- a/server/database.py +++ b/server/database.py @@ -16,6 +16,7 @@ from db.db_upgrade import ( ensure_Settings, ensure_Indexes, ensure_mac_lowercase_triggers, + migrate_to_camelcase, migrate_timestamps_to_utc, ) @@ -194,6 +195,10 @@ class DB: if not ensure_column(self.sql, "Devices", "devCanSleep", "INTEGER"): raise RuntimeError("ensure_column(devCanSleep) failed") + # CamelCase column migration (must run before UTC migration and + # before ensure_plugins_tables which uses IF NOT EXISTS with new names) + migrate_to_camelcase(self.sql) + # Settings table setup ensure_Settings(self.sql) diff --git a/server/db/db_upgrade.py b/server/db/db_upgrade.py index 55cda251..83739315 100755 --- a/server/db/db_upgrade.py +++ b/server/db/db_upgrade.py @@ -157,7 +157,7 @@ def ensure_views(sql) -> bool: sql.execute(""" CREATE VIEW Events_Devices AS SELECT * FROM Events - LEFT JOIN Devices ON eve_MAC = devMac; + LEFT JOIN Devices ON eveMac = devMac; """) sql.execute(""" DROP VIEW IF EXISTS LatestEventsPerMAC;""") @@ -165,7 +165,7 @@ def ensure_views(sql) -> bool: WITH RankedEvents AS ( SELECT e.*, - ROW_NUMBER() OVER (PARTITION BY e.eve_MAC ORDER BY e.eve_DateTime DESC) AS row_num + ROW_NUMBER() OVER (PARTITION BY e.eveMac ORDER BY e.eveDateTime DESC) AS row_num FROM Events AS e ) SELECT @@ -173,43 +173,43 @@ def ensure_views(sql) -> bool: d.*, c.* FROM RankedEvents AS e - LEFT JOIN Devices AS d ON e.eve_MAC = d.devMac - INNER JOIN CurrentScan AS c ON e.eve_MAC = c.scanMac + LEFT JOIN Devices AS d ON e.eveMac = d.devMac + INNER JOIN CurrentScan AS c ON e.eveMac = c.scanMac WHERE e.row_num = 1;""") sql.execute(""" DROP VIEW IF EXISTS Sessions_Devices;""") sql.execute( - """CREATE VIEW Sessions_Devices AS SELECT * FROM Sessions LEFT JOIN "Devices" ON ses_MAC = devMac;""" + """CREATE VIEW Sessions_Devices AS SELECT * FROM Sessions LEFT JOIN "Devices" ON sesMac = devMac;""" ) # handling the Convert_Events_to_Sessions / Sessions screens sql.execute("""DROP VIEW IF EXISTS Convert_Events_to_Sessions;""") - sql.execute("""CREATE VIEW Convert_Events_to_Sessions AS SELECT EVE1.eve_MAC, - EVE1.eve_IP, - EVE1.eve_EventType AS eve_EventTypeConnection, - EVE1.eve_DateTime AS eve_DateTimeConnection, - CASE WHEN EVE2.eve_EventType IN ('Disconnected', 'Device Down') OR - EVE2.eve_EventType IS NULL THEN EVE2.eve_EventType ELSE '' END AS eve_EventTypeDisconnection, - CASE WHEN EVE2.eve_EventType IN ('Disconnected', 'Device Down') THEN EVE2.eve_DateTime ELSE NULL END AS eve_DateTimeDisconnection, - CASE WHEN EVE2.eve_EventType IS NULL THEN 1 ELSE 0 END AS eve_StillConnected, - EVE1.eve_AdditionalInfo + sql.execute("""CREATE VIEW Convert_Events_to_Sessions AS SELECT EVE1.eveMac, + EVE1.eveIp, + EVE1.eveEventType AS eveEventTypeConnection, + EVE1.eveDateTime AS eveDateTimeConnection, + CASE WHEN EVE2.eveEventType IN ('Disconnected', 'Device Down') OR + EVE2.eveEventType IS NULL THEN EVE2.eveEventType ELSE '' END AS eveEventTypeDisconnection, + CASE WHEN EVE2.eveEventType IN ('Disconnected', 'Device Down') THEN EVE2.eveDateTime ELSE NULL END AS eveDateTimeDisconnection, + CASE WHEN EVE2.eveEventType IS NULL THEN 1 ELSE 0 END AS eveStillConnected, + EVE1.eveAdditionalInfo FROM Events AS EVE1 LEFT JOIN - Events AS EVE2 ON EVE1.eve_PairEventRowID = EVE2.RowID - WHERE EVE1.eve_EventType IN ('New Device', 'Connected','Down Reconnected') + Events AS EVE2 ON EVE1.evePairEventRowid = EVE2.RowID + WHERE EVE1.eveEventType IN ('New Device', 'Connected','Down Reconnected') UNION - SELECT eve_MAC, - eve_IP, - '' AS eve_EventTypeConnection, - NULL AS eve_DateTimeConnection, - eve_EventType AS eve_EventTypeDisconnection, - eve_DateTime AS eve_DateTimeDisconnection, - 0 AS eve_StillConnected, - eve_AdditionalInfo + SELECT eveMac, + eveIp, + '' AS eveEventTypeConnection, + NULL AS eveDateTimeConnection, + eveEventType AS eveEventTypeDisconnection, + eveDateTime AS eveDateTimeDisconnection, + 0 AS eveStillConnected, + eveAdditionalInfo FROM Events AS EVE1 - WHERE (eve_EventType = 'Device Down' OR - eve_EventType = 'Disconnected') AND - EVE1.eve_PairEventRowID IS NULL; + WHERE (eveEventType = 'Device Down' OR + eveEventType = 'Disconnected') AND + EVE1.evePairEventRowid IS NULL; """) sql.execute(""" DROP VIEW IF EXISTS LatestDeviceScan;""") @@ -316,10 +316,10 @@ def ensure_views(sql) -> bool: WHEN EXISTS ( SELECT 1 FROM Events e - WHERE LOWER(e.eve_MAC) = LOWER(Devices.devMac) - AND e.eve_EventType IN ('Connected','Disconnected','Device Down','Down Reconnected') - AND e.eve_DateTime >= datetime('now', '-{FLAP_WINDOW_HOURS} hours') - GROUP BY e.eve_MAC + WHERE LOWER(e.eveMac) = LOWER(Devices.devMac) + AND e.eveEventType IN ('Connected','Disconnected','Device Down','Down Reconnected') + AND e.eveDateTime >= datetime('now', '-{FLAP_WINDOW_HOURS} hours') + GROUP BY e.eveMac HAVING COUNT(*) >= {FLAP_THRESHOLD} ) THEN 1 @@ -360,10 +360,10 @@ def ensure_Indexes(sql) -> bool: SELECT MIN(rowid) FROM Events GROUP BY - eve_MAC, - eve_IP, - eve_EventType, - eve_DateTime + eveMac, + eveIp, + eveEventType, + eveDateTime ); """ @@ -373,32 +373,32 @@ def ensure_Indexes(sql) -> bool: # Sessions ( "idx_ses_mac_date", - "CREATE INDEX idx_ses_mac_date ON Sessions(ses_MAC, ses_DateTimeConnection, ses_DateTimeDisconnection, ses_StillConnected)", + "CREATE INDEX idx_ses_mac_date ON Sessions(sesMac, sesDateTimeConnection, sesDateTimeDisconnection, sesStillConnected)", ), # Events ( "idx_eve_mac_date_type", - "CREATE INDEX idx_eve_mac_date_type ON Events(eve_MAC, eve_DateTime, eve_EventType)", + "CREATE INDEX idx_eve_mac_date_type ON Events(eveMac, eveDateTime, eveEventType)", ), ( "idx_eve_alert_pending", - "CREATE INDEX idx_eve_alert_pending ON Events(eve_PendingAlertEmail)", + "CREATE INDEX idx_eve_alert_pending ON Events(evePendingAlertEmail)", ), ( "idx_eve_mac_datetime_desc", - "CREATE INDEX idx_eve_mac_datetime_desc ON Events(eve_MAC, eve_DateTime DESC)", + "CREATE INDEX idx_eve_mac_datetime_desc ON Events(eveMac, eveDateTime DESC)", ), ( "idx_eve_pairevent", - "CREATE INDEX idx_eve_pairevent ON Events(eve_PairEventRowID)", + "CREATE INDEX idx_eve_pairevent ON Events(evePairEventRowid)", ), ( "idx_eve_type_date", - "CREATE INDEX idx_eve_type_date ON Events(eve_EventType, eve_DateTime)", + "CREATE INDEX idx_eve_type_date ON Events(eveEventType, eveDateTime)", ), ( "idx_events_unique", - "CREATE UNIQUE INDEX idx_events_unique ON Events (eve_MAC, eve_IP, eve_EventType, eve_DateTime)", + "CREATE UNIQUE INDEX idx_events_unique ON Events (eveMac, eveIp, eveEventType, eveDateTime)", ), # Devices ("idx_dev_mac", "CREATE INDEX idx_dev_mac ON Devices(devMac)"), @@ -436,15 +436,15 @@ def ensure_Indexes(sql) -> bool: # Plugins_Objects ( "idx_plugins_plugin_mac_ip", - "CREATE INDEX idx_plugins_plugin_mac_ip ON Plugins_Objects(Plugin, Object_PrimaryID, Object_SecondaryID)", + "CREATE INDEX idx_plugins_plugin_mac_ip ON Plugins_Objects(plugin, objectPrimaryId, objectSecondaryId)", ), # Issue #1251: Optimize name resolution lookup # Plugins_History: covers both the db_cleanup window function - # (PARTITION BY Plugin ORDER BY DateTimeChanged DESC) and the - # API query (SELECT * … ORDER BY DateTimeChanged DESC). + # (PARTITION BY plugin ORDER BY dateTimeChanged DESC) and the + # API query (SELECT * … ORDER BY dateTimeChanged DESC). # Without this, both ops do a full 48k-row table sort on every cycle. ( "idx_plugins_history_plugin_dt", - "CREATE INDEX idx_plugins_history_plugin_dt ON Plugins_History(Plugin, DateTimeChanged DESC)", + "CREATE INDEX idx_plugins_history_plugin_dt ON Plugins_History(plugin, dateTimeChanged DESC)", ), ] @@ -547,94 +547,295 @@ def ensure_plugins_tables(sql) -> bool: # Plugin state sql_Plugins_Objects = """ CREATE TABLE IF NOT EXISTS Plugins_Objects( - "Index" INTEGER, - Plugin TEXT NOT NULL, - Object_PrimaryID TEXT NOT NULL, - Object_SecondaryID TEXT NOT NULL, - DateTimeCreated TEXT NOT NULL, - DateTimeChanged TEXT NOT NULL, - Watched_Value1 TEXT NOT NULL, - Watched_Value2 TEXT NOT NULL, - Watched_Value3 TEXT NOT NULL, - Watched_Value4 TEXT NOT NULL, - Status TEXT NOT NULL, - Extra TEXT NOT NULL, - UserData TEXT NOT NULL, - ForeignKey TEXT NOT NULL, - SyncHubNodeName TEXT, - "HelpVal1" TEXT, - "HelpVal2" TEXT, - "HelpVal3" TEXT, - "HelpVal4" TEXT, - ObjectGUID TEXT, - PRIMARY KEY("Index" AUTOINCREMENT) + "index" INTEGER, + plugin TEXT NOT NULL, + objectPrimaryId TEXT NOT NULL, + objectSecondaryId TEXT NOT NULL, + dateTimeCreated TEXT NOT NULL, + dateTimeChanged TEXT NOT NULL, + watchedValue1 TEXT NOT NULL, + watchedValue2 TEXT NOT NULL, + watchedValue3 TEXT NOT NULL, + watchedValue4 TEXT NOT NULL, + "status" TEXT NOT NULL, + extra TEXT NOT NULL, + userData TEXT NOT NULL, + foreignKey TEXT NOT NULL, + syncHubNodeName TEXT, + helpVal1 TEXT, + helpVal2 TEXT, + helpVal3 TEXT, + helpVal4 TEXT, + objectGuid TEXT, + PRIMARY KEY("index" AUTOINCREMENT) ); """ sql.execute(sql_Plugins_Objects) # Plugin execution results sql_Plugins_Events = """ CREATE TABLE IF NOT EXISTS Plugins_Events( - "Index" INTEGER, - Plugin TEXT NOT NULL, - Object_PrimaryID TEXT NOT NULL, - Object_SecondaryID TEXT NOT NULL, - DateTimeCreated TEXT NOT NULL, - DateTimeChanged TEXT NOT NULL, - Watched_Value1 TEXT NOT NULL, - Watched_Value2 TEXT NOT NULL, - Watched_Value3 TEXT NOT NULL, - Watched_Value4 TEXT NOT NULL, - Status TEXT NOT NULL, - Extra TEXT NOT NULL, - UserData TEXT NOT NULL, - ForeignKey TEXT NOT NULL, - SyncHubNodeName TEXT, - "HelpVal1" TEXT, - "HelpVal2" TEXT, - "HelpVal3" TEXT, - "HelpVal4" TEXT, - PRIMARY KEY("Index" AUTOINCREMENT) + "index" INTEGER, + plugin TEXT NOT NULL, + objectPrimaryId TEXT NOT NULL, + objectSecondaryId TEXT NOT NULL, + dateTimeCreated TEXT NOT NULL, + dateTimeChanged TEXT NOT NULL, + watchedValue1 TEXT NOT NULL, + watchedValue2 TEXT NOT NULL, + watchedValue3 TEXT NOT NULL, + watchedValue4 TEXT NOT NULL, + "status" TEXT NOT NULL, + extra TEXT NOT NULL, + userData TEXT NOT NULL, + foreignKey TEXT NOT NULL, + syncHubNodeName TEXT, + helpVal1 TEXT, + helpVal2 TEXT, + helpVal3 TEXT, + helpVal4 TEXT, + objectGuid TEXT, + PRIMARY KEY("index" AUTOINCREMENT) ); """ sql.execute(sql_Plugins_Events) # Plugin execution history sql_Plugins_History = """ CREATE TABLE IF NOT EXISTS Plugins_History( - "Index" INTEGER, - Plugin TEXT NOT NULL, - Object_PrimaryID TEXT NOT NULL, - Object_SecondaryID TEXT NOT NULL, - DateTimeCreated TEXT NOT NULL, - DateTimeChanged TEXT NOT NULL, - Watched_Value1 TEXT NOT NULL, - Watched_Value2 TEXT NOT NULL, - Watched_Value3 TEXT NOT NULL, - Watched_Value4 TEXT NOT NULL, - Status TEXT NOT NULL, - Extra TEXT NOT NULL, - UserData TEXT NOT NULL, - ForeignKey TEXT NOT NULL, - SyncHubNodeName TEXT, - "HelpVal1" TEXT, - "HelpVal2" TEXT, - "HelpVal3" TEXT, - "HelpVal4" TEXT, - PRIMARY KEY("Index" AUTOINCREMENT) + "index" INTEGER, + plugin TEXT NOT NULL, + objectPrimaryId TEXT NOT NULL, + objectSecondaryId TEXT NOT NULL, + dateTimeCreated TEXT NOT NULL, + dateTimeChanged TEXT NOT NULL, + watchedValue1 TEXT NOT NULL, + watchedValue2 TEXT NOT NULL, + watchedValue3 TEXT NOT NULL, + watchedValue4 TEXT NOT NULL, + "status" TEXT NOT NULL, + extra TEXT NOT NULL, + userData TEXT NOT NULL, + foreignKey TEXT NOT NULL, + syncHubNodeName TEXT, + helpVal1 TEXT, + helpVal2 TEXT, + helpVal3 TEXT, + helpVal4 TEXT, + objectGuid TEXT, + PRIMARY KEY("index" AUTOINCREMENT) ); """ sql.execute(sql_Plugins_History) # Dynamically generated language strings sql.execute("DROP TABLE IF EXISTS Plugins_Language_Strings;") sql.execute(""" CREATE TABLE IF NOT EXISTS Plugins_Language_Strings( - "Index" INTEGER, - Language_Code TEXT NOT NULL, - String_Key TEXT NOT NULL, - String_Value TEXT NOT NULL, - Extra TEXT NOT NULL, - PRIMARY KEY("Index" AUTOINCREMENT) + "index" INTEGER, + languageCode TEXT NOT NULL, + stringKey TEXT NOT NULL, + stringValue TEXT NOT NULL, + extra TEXT NOT NULL, + PRIMARY KEY("index" AUTOINCREMENT) ); """) return True +# =============================================================================== +# CamelCase Column Migration +# =============================================================================== + +# Mapping of (table_name, old_column_name) → new_column_name. +# Only entries where the name actually changes are listed. +# Columns like "Index" → "index" are cosmetic case changes handled +# implicitly by SQLite's case-insensitive matching. +_CAMELCASE_COLUMN_MAP = { + "Events": { + "eve_MAC": "eveMac", + "eve_IP": "eveIp", + "eve_DateTime": "eveDateTime", + "eve_EventType": "eveEventType", + "eve_AdditionalInfo": "eveAdditionalInfo", + "eve_PendingAlertEmail": "evePendingAlertEmail", + "eve_PairEventRowid": "evePairEventRowid", + "eve_PairEventRowID": "evePairEventRowid", + }, + "Sessions": { + "ses_MAC": "sesMac", + "ses_IP": "sesIp", + "ses_EventTypeConnection": "sesEventTypeConnection", + "ses_DateTimeConnection": "sesDateTimeConnection", + "ses_EventTypeDisconnection": "sesEventTypeDisconnection", + "ses_DateTimeDisconnection": "sesDateTimeDisconnection", + "ses_StillConnected": "sesStillConnected", + "ses_AdditionalInfo": "sesAdditionalInfo", + }, + "Online_History": { + "Index": "index", + "Scan_Date": "scanDate", + "Online_Devices": "onlineDevices", + "Down_Devices": "downDevices", + "All_Devices": "allDevices", + "Archived_Devices": "archivedDevices", + "Offline_Devices": "offlineDevices", + }, + "Plugins_Objects": { + "Index": "index", + "Plugin": "plugin", + "Object_PrimaryID": "objectPrimaryId", + "Object_SecondaryID": "objectSecondaryId", + "DateTimeCreated": "dateTimeCreated", + "DateTimeChanged": "dateTimeChanged", + "Watched_Value1": "watchedValue1", + "Watched_Value2": "watchedValue2", + "Watched_Value3": "watchedValue3", + "Watched_Value4": "watchedValue4", + "Status": "status", + "Extra": "extra", + "UserData": "userData", + "ForeignKey": "foreignKey", + "SyncHubNodeName": "syncHubNodeName", + "HelpVal1": "helpVal1", + "HelpVal2": "helpVal2", + "HelpVal3": "helpVal3", + "HelpVal4": "helpVal4", + "ObjectGUID": "objectGuid", + }, + "Plugins_Events": { + "Index": "index", + "Plugin": "plugin", + "Object_PrimaryID": "objectPrimaryId", + "Object_SecondaryID": "objectSecondaryId", + "DateTimeCreated": "dateTimeCreated", + "DateTimeChanged": "dateTimeChanged", + "Watched_Value1": "watchedValue1", + "Watched_Value2": "watchedValue2", + "Watched_Value3": "watchedValue3", + "Watched_Value4": "watchedValue4", + "Status": "status", + "Extra": "extra", + "UserData": "userData", + "ForeignKey": "foreignKey", + "SyncHubNodeName": "syncHubNodeName", + "HelpVal1": "helpVal1", + "HelpVal2": "helpVal2", + "HelpVal3": "helpVal3", + "HelpVal4": "helpVal4", + "ObjectGUID": "objectGuid", + }, + "Plugins_History": { + "Index": "index", + "Plugin": "plugin", + "Object_PrimaryID": "objectPrimaryId", + "Object_SecondaryID": "objectSecondaryId", + "DateTimeCreated": "dateTimeCreated", + "DateTimeChanged": "dateTimeChanged", + "Watched_Value1": "watchedValue1", + "Watched_Value2": "watchedValue2", + "Watched_Value3": "watchedValue3", + "Watched_Value4": "watchedValue4", + "Status": "status", + "Extra": "extra", + "UserData": "userData", + "ForeignKey": "foreignKey", + "SyncHubNodeName": "syncHubNodeName", + "HelpVal1": "helpVal1", + "HelpVal2": "helpVal2", + "HelpVal3": "helpVal3", + "HelpVal4": "helpVal4", + "ObjectGUID": "objectGuid", + }, + "Plugins_Language_Strings": { + "Index": "index", + "Language_Code": "languageCode", + "String_Key": "stringKey", + "String_Value": "stringValue", + "Extra": "extra", + }, + "AppEvents": { + "Index": "index", + "GUID": "guid", + "AppEventProcessed": "appEventProcessed", + "DateTimeCreated": "dateTimeCreated", + "ObjectType": "objectType", + "ObjectGUID": "objectGuid", + "ObjectPlugin": "objectPlugin", + "ObjectPrimaryID": "objectPrimaryId", + "ObjectSecondaryID": "objectSecondaryId", + "ObjectForeignKey": "objectForeignKey", + "ObjectIndex": "objectIndex", + "ObjectIsNew": "objectIsNew", + "ObjectIsArchived": "objectIsArchived", + "ObjectStatusColumn": "objectStatusColumn", + "ObjectStatus": "objectStatus", + "AppEventType": "appEventType", + "Helper1": "helper1", + "Helper2": "helper2", + "Helper3": "helper3", + "Extra": "extra", + }, + "Notifications": { + "Index": "index", + "GUID": "guid", + "DateTimeCreated": "dateTimeCreated", + "DateTimePushed": "dateTimePushed", + "Status": "status", + "JSON": "json", + "Text": "text", + "HTML": "html", + "PublishedVia": "publishedVia", + "Extra": "extra", + }, +} + + +def migrate_to_camelcase(sql) -> bool: + """ + Detects legacy (underscore/PascalCase) column names and renames them + to camelCase using ALTER TABLE … RENAME COLUMN (SQLite ≥ 3.25.0). + + Idempotent: columns already matching the new name are silently skipped. + """ + + # Quick probe: if Events table has 'eveMac' we're already on the new schema + sql.execute('PRAGMA table_info("Events")') + events_cols = {row[1] for row in sql.fetchall()} + if "eveMac" in events_cols: + mylog("verbose", ["[db_upgrade] Schema already uses camelCase — skipping migration"]) + return True + + if "eve_MAC" not in events_cols: + # Events table doesn't exist or has unexpected schema — skip silently + mylog("verbose", ["[db_upgrade] Events table missing/unrecognised — skipping camelCase migration"]) + return True + + mylog("none", ["[db_upgrade] Starting camelCase column migration …"]) + + # Drop views first — ALTER TABLE RENAME COLUMN will fail if a view + # references the old column name and the view SQL cannot be rewritten. + for view_name in ("Events_Devices", "LatestEventsPerMAC", "Sessions_Devices", + "Convert_Events_to_Sessions", "LatestDeviceScan", "DevicesView"): + sql.execute(f"DROP VIEW IF EXISTS {view_name};") + + renamed_count = 0 + + for table, column_map in _CAMELCASE_COLUMN_MAP.items(): + # Check table exists + sql.execute(f"SELECT name FROM sqlite_master WHERE type='table' AND name=?", (table,)) + if not sql.fetchone(): + mylog("verbose", [f"[db_upgrade] Table '{table}' does not exist — skipping"]) + continue + + # Get current column names (case-preserved) + sql.execute(f'PRAGMA table_info("{table}")') + current_cols = {row[1] for row in sql.fetchall()} + + for old_name, new_name in column_map.items(): + if old_name in current_cols and new_name not in current_cols: + sql.execute(f'ALTER TABLE "{table}" RENAME COLUMN "{old_name}" TO "{new_name}"') + renamed_count += 1 + mylog("verbose", [f"[db_upgrade] {table}.{old_name} → {new_name}"]) + + mylog("none", [f"[db_upgrade] ✓ camelCase migration complete — {renamed_count} columns renamed"]) + return True + + # =============================================================================== # UTC Timestamp Migration (added 2026-02-10) # =============================================================================== @@ -817,17 +1018,18 @@ def migrate_timestamps_to_utc(sql) -> bool: mylog("verbose", f"[db_upgrade] Starting UTC timestamp migration (offset: {offset_hours} hours)") - # List of tables and their datetime columns + # List of tables and their datetime columns (camelCase names — + # migrate_to_camelcase() runs before this function). timestamp_columns = { 'Devices': ['devFirstConnection', 'devLastConnection', 'devLastNotification'], - 'Events': ['eve_DateTime'], - 'Sessions': ['ses_DateTimeConnection', 'ses_DateTimeDisconnection'], - 'Notifications': ['DateTimeCreated', 'DateTimePushed'], - 'Online_History': ['Scan_Date'], - 'Plugins_Objects': ['DateTimeCreated', 'DateTimeChanged'], - 'Plugins_Events': ['DateTimeCreated', 'DateTimeChanged'], - 'Plugins_History': ['DateTimeCreated', 'DateTimeChanged'], - 'AppEvents': ['DateTimeCreated'], + 'Events': ['eveDateTime'], + 'Sessions': ['sesDateTimeConnection', 'sesDateTimeDisconnection'], + 'Notifications': ['dateTimeCreated', 'dateTimePushed'], + 'Online_History': ['scanDate'], + 'Plugins_Objects': ['dateTimeCreated', 'dateTimeChanged'], + 'Plugins_Events': ['dateTimeCreated', 'dateTimeChanged'], + 'Plugins_History': ['dateTimeCreated', 'dateTimeChanged'], + 'AppEvents': ['dateTimeCreated'], } for table, columns in timestamp_columns.items(): diff --git a/server/db/schema/app.sql b/server/db/schema/app.sql index e3b51763..dffd27b1 100644 --- a/server/db/schema/app.sql +++ b/server/db/schema/app.sql @@ -1,14 +1,14 @@ -CREATE TABLE Events (eve_MAC STRING (50) NOT NULL COLLATE NOCASE, eve_IP STRING (50) NOT NULL COLLATE NOCASE, eve_DateTime DATETIME NOT NULL, eve_EventType STRING (30) NOT NULL COLLATE NOCASE, eve_AdditionalInfo STRING (250) DEFAULT (''), eve_PendingAlertEmail BOOLEAN NOT NULL CHECK (eve_PendingAlertEmail IN (0, 1)) DEFAULT (1), eve_PairEventRowid INTEGER); -CREATE TABLE Sessions (ses_MAC STRING (50) COLLATE NOCASE, ses_IP STRING (50) COLLATE NOCASE, ses_EventTypeConnection STRING (30) COLLATE NOCASE, ses_DateTimeConnection DATETIME, ses_EventTypeDisconnection STRING (30) COLLATE NOCASE, ses_DateTimeDisconnection DATETIME, ses_StillConnected BOOLEAN, ses_AdditionalInfo STRING (250)); -CREATE TABLE IF NOT EXISTS "Online_History" ( - "Index" INTEGER, - "Scan_Date" TEXT, - "Online_Devices" INTEGER, - "Down_Devices" INTEGER, - "All_Devices" INTEGER, - "Archived_Devices" INTEGER, - "Offline_Devices" INTEGER, - PRIMARY KEY("Index" AUTOINCREMENT) +CREATE TABLE Events (eveMac STRING (50) NOT NULL COLLATE NOCASE, eveIp STRING (50) NOT NULL COLLATE NOCASE, eveDateTime DATETIME NOT NULL, eveEventType STRING (30) NOT NULL COLLATE NOCASE, eveAdditionalInfo STRING (250) DEFAULT (''), evePendingAlertEmail BOOLEAN NOT NULL CHECK (evePendingAlertEmail IN (0, 1)) DEFAULT (1), evePairEventRowid INTEGER); +CREATE TABLE Sessions (sesMac STRING (50) COLLATE NOCASE, sesIp STRING (50) COLLATE NOCASE, sesEventTypeConnection STRING (30) COLLATE NOCASE, sesDateTimeConnection DATETIME, sesEventTypeDisconnection STRING (30) COLLATE NOCASE, sesDateTimeDisconnection DATETIME, sesStillConnected BOOLEAN, sesAdditionalInfo STRING (250)); +CREATE TABLE IF NOT EXISTS Online_History ( + "index" INTEGER, + scanDate TEXT, + onlineDevices INTEGER, + downDevices INTEGER, + allDevices INTEGER, + archivedDevices INTEGER, + offlineDevices INTEGER, + PRIMARY KEY("index" AUTOINCREMENT) ); CREATE TABLE Devices ( devMac STRING (50) PRIMARY KEY NOT NULL COLLATE NOCASE, @@ -57,96 +57,98 @@ CREATE TABLE Devices ( devParentPortSource TEXT, devParentRelTypeSource TEXT, devVlanSource TEXT, - "devCustomProps" TEXT); -CREATE TABLE IF NOT EXISTS "Settings" ( - "setKey" TEXT, - "setName" TEXT, - "setDescription" TEXT, - "setType" TEXT, - "setOptions" TEXT, - "setGroup" TEXT, - "setValue" TEXT, - "setEvents" TEXT, - "setOverriddenByEnv" INTEGER + devCustomProps TEXT); +CREATE TABLE IF NOT EXISTS Settings ( + setKey TEXT, + setName TEXT, + setDescription TEXT, + setType TEXT, + setOptions TEXT, + setGroup TEXT, + setValue TEXT, + setEvents TEXT, + setOverriddenByEnv INTEGER ); -CREATE TABLE IF NOT EXISTS "Parameters" ( - "parID" TEXT PRIMARY KEY, - "parValue" TEXT +CREATE TABLE IF NOT EXISTS Parameters ( + parID TEXT PRIMARY KEY, + parValue TEXT ); CREATE TABLE Plugins_Objects( - "Index" INTEGER, - Plugin TEXT NOT NULL, - Object_PrimaryID TEXT NOT NULL, - Object_SecondaryID TEXT NOT NULL, - DateTimeCreated TEXT NOT NULL, - DateTimeChanged TEXT NOT NULL, - Watched_Value1 TEXT NOT NULL, - Watched_Value2 TEXT NOT NULL, - Watched_Value3 TEXT NOT NULL, - Watched_Value4 TEXT NOT NULL, - Status TEXT NOT NULL, - Extra TEXT NOT NULL, - UserData TEXT NOT NULL, - ForeignKey TEXT NOT NULL, - SyncHubNodeName TEXT, - "HelpVal1" TEXT, - "HelpVal2" TEXT, - "HelpVal3" TEXT, - "HelpVal4" TEXT, - ObjectGUID TEXT, - PRIMARY KEY("Index" AUTOINCREMENT) + "index" INTEGER, + plugin TEXT NOT NULL, + objectPrimaryId TEXT NOT NULL, + objectSecondaryId TEXT NOT NULL, + dateTimeCreated TEXT NOT NULL, + dateTimeChanged TEXT NOT NULL, + watchedValue1 TEXT NOT NULL, + watchedValue2 TEXT NOT NULL, + watchedValue3 TEXT NOT NULL, + watchedValue4 TEXT NOT NULL, + "status" TEXT NOT NULL, + extra TEXT NOT NULL, + userData TEXT NOT NULL, + foreignKey TEXT NOT NULL, + syncHubNodeName TEXT, + helpVal1 TEXT, + helpVal2 TEXT, + helpVal3 TEXT, + helpVal4 TEXT, + objectGuid TEXT, + PRIMARY KEY("index" AUTOINCREMENT) ); CREATE TABLE Plugins_Events( - "Index" INTEGER, - Plugin TEXT NOT NULL, - Object_PrimaryID TEXT NOT NULL, - Object_SecondaryID TEXT NOT NULL, - DateTimeCreated TEXT NOT NULL, - DateTimeChanged TEXT NOT NULL, - Watched_Value1 TEXT NOT NULL, - Watched_Value2 TEXT NOT NULL, - Watched_Value3 TEXT NOT NULL, - Watched_Value4 TEXT NOT NULL, - Status TEXT NOT NULL, - Extra TEXT NOT NULL, - UserData TEXT NOT NULL, - ForeignKey TEXT NOT NULL, - SyncHubNodeName TEXT, - "HelpVal1" TEXT, - "HelpVal2" TEXT, - "HelpVal3" TEXT, - "HelpVal4" TEXT, "ObjectGUID" TEXT, - PRIMARY KEY("Index" AUTOINCREMENT) + "index" INTEGER, + plugin TEXT NOT NULL, + objectPrimaryId TEXT NOT NULL, + objectSecondaryId TEXT NOT NULL, + dateTimeCreated TEXT NOT NULL, + dateTimeChanged TEXT NOT NULL, + watchedValue1 TEXT NOT NULL, + watchedValue2 TEXT NOT NULL, + watchedValue3 TEXT NOT NULL, + watchedValue4 TEXT NOT NULL, + "status" TEXT NOT NULL, + extra TEXT NOT NULL, + userData TEXT NOT NULL, + foreignKey TEXT NOT NULL, + syncHubNodeName TEXT, + helpVal1 TEXT, + helpVal2 TEXT, + helpVal3 TEXT, + helpVal4 TEXT, + objectGuid TEXT, + PRIMARY KEY("index" AUTOINCREMENT) ); CREATE TABLE Plugins_History( - "Index" INTEGER, - Plugin TEXT NOT NULL, - Object_PrimaryID TEXT NOT NULL, - Object_SecondaryID TEXT NOT NULL, - DateTimeCreated TEXT NOT NULL, - DateTimeChanged TEXT NOT NULL, - Watched_Value1 TEXT NOT NULL, - Watched_Value2 TEXT NOT NULL, - Watched_Value3 TEXT NOT NULL, - Watched_Value4 TEXT NOT NULL, - Status TEXT NOT NULL, - Extra TEXT NOT NULL, - UserData TEXT NOT NULL, - ForeignKey TEXT NOT NULL, - SyncHubNodeName TEXT, - "HelpVal1" TEXT, - "HelpVal2" TEXT, - "HelpVal3" TEXT, - "HelpVal4" TEXT, "ObjectGUID" TEXT, - PRIMARY KEY("Index" AUTOINCREMENT) + "index" INTEGER, + plugin TEXT NOT NULL, + objectPrimaryId TEXT NOT NULL, + objectSecondaryId TEXT NOT NULL, + dateTimeCreated TEXT NOT NULL, + dateTimeChanged TEXT NOT NULL, + watchedValue1 TEXT NOT NULL, + watchedValue2 TEXT NOT NULL, + watchedValue3 TEXT NOT NULL, + watchedValue4 TEXT NOT NULL, + "status" TEXT NOT NULL, + extra TEXT NOT NULL, + userData TEXT NOT NULL, + foreignKey TEXT NOT NULL, + syncHubNodeName TEXT, + helpVal1 TEXT, + helpVal2 TEXT, + helpVal3 TEXT, + helpVal4 TEXT, + objectGuid TEXT, + PRIMARY KEY("index" AUTOINCREMENT) ); CREATE TABLE Plugins_Language_Strings( - "Index" INTEGER, - Language_Code TEXT NOT NULL, - String_Key TEXT NOT NULL, - String_Value TEXT NOT NULL, - Extra TEXT NOT NULL, - PRIMARY KEY("Index" AUTOINCREMENT) + "index" INTEGER, + languageCode TEXT NOT NULL, + stringKey TEXT NOT NULL, + stringValue TEXT NOT NULL, + extra TEXT NOT NULL, + PRIMARY KEY("index" AUTOINCREMENT) ); CREATE TABLE CurrentScan ( scanMac STRING(50) NOT NULL COLLATE NOCASE, @@ -165,50 +167,50 @@ CREATE TABLE CurrentScan ( scanType STRING(250), UNIQUE(scanMac) ); -CREATE TABLE IF NOT EXISTS "AppEvents" ( - "Index" INTEGER PRIMARY KEY AUTOINCREMENT, - "GUID" TEXT UNIQUE, - "AppEventProcessed" BOOLEAN, - "DateTimeCreated" TEXT, - "ObjectType" TEXT, - "ObjectGUID" TEXT, - "ObjectPlugin" TEXT, - "ObjectPrimaryID" TEXT, - "ObjectSecondaryID" TEXT, - "ObjectForeignKey" TEXT, - "ObjectIndex" TEXT, - "ObjectIsNew" BOOLEAN, - "ObjectIsArchived" BOOLEAN, - "ObjectStatusColumn" TEXT, - "ObjectStatus" TEXT, - "AppEventType" TEXT, - "Helper1" TEXT, - "Helper2" TEXT, - "Helper3" TEXT, - "Extra" TEXT +CREATE TABLE IF NOT EXISTS AppEvents ( + "index" INTEGER PRIMARY KEY AUTOINCREMENT, + guid TEXT UNIQUE, + appEventProcessed BOOLEAN, + dateTimeCreated TEXT, + objectType TEXT, + objectGuid TEXT, + objectPlugin TEXT, + objectPrimaryId TEXT, + objectSecondaryId TEXT, + objectForeignKey TEXT, + objectIndex TEXT, + objectIsNew BOOLEAN, + objectIsArchived BOOLEAN, + objectStatusColumn TEXT, + objectStatus TEXT, + appEventType TEXT, + helper1 TEXT, + helper2 TEXT, + helper3 TEXT, + extra TEXT ); -CREATE TABLE IF NOT EXISTS "Notifications" ( - "Index" INTEGER, - "GUID" TEXT UNIQUE, - "DateTimeCreated" TEXT, - "DateTimePushed" TEXT, - "Status" TEXT, - "JSON" TEXT, - "Text" TEXT, - "HTML" TEXT, - "PublishedVia" TEXT, - "Extra" TEXT, - PRIMARY KEY("Index" AUTOINCREMENT) +CREATE TABLE IF NOT EXISTS Notifications ( + "index" INTEGER, + guid TEXT UNIQUE, + dateTimeCreated TEXT, + dateTimePushed TEXT, + "status" TEXT, + "json" TEXT, + "text" TEXT, + html TEXT, + publishedVia TEXT, + extra TEXT, + PRIMARY KEY("index" AUTOINCREMENT) ); -CREATE INDEX IDX_eve_DateTime ON Events (eve_DateTime); -CREATE INDEX IDX_eve_EventType ON Events (eve_EventType COLLATE NOCASE); -CREATE INDEX IDX_eve_MAC ON Events (eve_MAC COLLATE NOCASE); -CREATE INDEX IDX_eve_PairEventRowid ON Events (eve_PairEventRowid); -CREATE INDEX IDX_ses_EventTypeDisconnection ON Sessions (ses_EventTypeDisconnection COLLATE NOCASE); -CREATE INDEX IDX_ses_EventTypeConnection ON Sessions (ses_EventTypeConnection COLLATE NOCASE); -CREATE INDEX IDX_ses_DateTimeDisconnection ON Sessions (ses_DateTimeDisconnection); -CREATE INDEX IDX_ses_MAC ON Sessions (ses_MAC COLLATE NOCASE); -CREATE INDEX IDX_ses_DateTimeConnection ON Sessions (ses_DateTimeConnection); +CREATE INDEX IDX_eve_DateTime ON Events (eveDateTime); +CREATE INDEX IDX_eve_EventType ON Events (eveEventType COLLATE NOCASE); +CREATE INDEX IDX_eve_MAC ON Events (eveMac COLLATE NOCASE); +CREATE INDEX IDX_eve_PairEventRowid ON Events (evePairEventRowid); +CREATE INDEX IDX_ses_EventTypeDisconnection ON Sessions (sesEventTypeDisconnection COLLATE NOCASE); +CREATE INDEX IDX_ses_EventTypeConnection ON Sessions (sesEventTypeConnection COLLATE NOCASE); +CREATE INDEX IDX_ses_DateTimeDisconnection ON Sessions (sesDateTimeDisconnection); +CREATE INDEX IDX_ses_MAC ON Sessions (sesMac COLLATE NOCASE); +CREATE INDEX IDX_ses_DateTimeConnection ON Sessions (sesDateTimeConnection); CREATE INDEX IDX_dev_PresentLastScan ON Devices (devPresentLastScan); CREATE INDEX IDX_dev_FirstConnection ON Devices (devFirstConnection); CREATE INDEX IDX_dev_AlertDeviceDown ON Devices (devAlertDown); @@ -220,21 +222,20 @@ CREATE INDEX IDX_dev_NewDevice ON Devices (devIsNew); CREATE INDEX IDX_dev_Archived ON Devices (devIsArchived); CREATE UNIQUE INDEX IF NOT EXISTS idx_events_unique ON Events ( - eve_MAC, - eve_IP, - eve_EventType, - eve_DateTime + eveMac, + eveIp, + eveEventType, + eveDateTime ); CREATE VIEW Events_Devices AS SELECT * FROM Events - LEFT JOIN Devices ON eve_MAC = devMac -/* Events_Devices(eve_MAC,eve_IP,eve_DateTime,eve_EventType,eve_AdditionalInfo,eve_PendingAlertEmail,eve_PairEventRowid,devMac,devName,devOwner,devType,devVendor,devFavorite,devGroup,devComments,devFirstConnection,devLastConnection,devLastIP,devStaticIP,devScan,devLogEvents,devAlertEvents,devAlertDown,devSkipRepeated,devLastNotification,devPresentLastScan,devIsNew,devLocation,devIsArchived,devParentMAC,devParentPort,devIcon,devGUID,devSite,devSSID,devSyncHubNode,devSourcePlugin,devCustomProps) */; + LEFT JOIN Devices ON eveMac = devMac; CREATE VIEW LatestEventsPerMAC AS WITH RankedEvents AS ( SELECT e.*, - ROW_NUMBER() OVER (PARTITION BY e.eve_MAC ORDER BY e.eve_DateTime DESC) AS row_num + ROW_NUMBER() OVER (PARTITION BY e.eveMac ORDER BY e.eveDateTime DESC) AS row_num FROM Events AS e ) SELECT @@ -242,192 +243,33 @@ CREATE VIEW LatestEventsPerMAC AS d.*, c.* FROM RankedEvents AS e - LEFT JOIN Devices AS d ON e.eve_MAC = d.devMac - INNER JOIN CurrentScan AS c ON e.eve_MAC = c.scanMac - WHERE e.row_num = 1 -/* LatestEventsPerMAC(eve_MAC,eve_IP,eve_DateTime,eve_EventType,eve_AdditionalInfo,eve_PendingAlertEmail,eve_PairEventRowid,row_num,devMac,devName,devOwner,devType,devVendor,devFavorite,devGroup,devComments,devFirstConnection,devLastConnection,devLastIP,devStaticIP,devScan,devLogEvents,devAlertEvents,devAlertDown,devSkipRepeated,devLastNotification,devPresentLastScan,devIsNew,devLocation,devIsArchived,devParentMAC,devParentPort,devIcon,devGUID,devSite,devSSID,devSyncHubNode,devSourcePlugin,devCustomProps,scanMac,scanLastIP,scanVendor,scanSourcePlugin,scanName,scanLastQuery,scanLastConnection,scanSyncHubNode,scanSite,scanSSID,scanParentMAC,scanParentPort,scanType) */; -CREATE VIEW Sessions_Devices AS SELECT * FROM Sessions LEFT JOIN "Devices" ON ses_MAC = devMac -/* Sessions_Devices(ses_MAC,ses_IP,ses_EventTypeConnection,ses_DateTimeConnection,ses_EventTypeDisconnection,ses_DateTimeDisconnection,ses_StillConnected,ses_AdditionalInfo,devMac,devName,devOwner,devType,devVendor,devFavorite,devGroup,devComments,devFirstConnection,devLastConnection,devLastIP,devStaticIP,devScan,devLogEvents,devAlertEvents,devAlertDown,devSkipRepeated,devLastNotification,devPresentLastScan,devIsNew,devLocation,devIsArchived,devParentMAC,devParentPort,devIcon,devGUID,devSite,devSSID,devSyncHubNode,devSourcePlugin,devCustomProps) */; -CREATE VIEW Convert_Events_to_Sessions AS SELECT EVE1.eve_MAC, - EVE1.eve_IP, - EVE1.eve_EventType AS eve_EventTypeConnection, - EVE1.eve_DateTime AS eve_DateTimeConnection, - CASE WHEN EVE2.eve_EventType IN ('Disconnected', 'Device Down') OR - EVE2.eve_EventType IS NULL THEN EVE2.eve_EventType ELSE '' END AS eve_EventTypeDisconnection, - CASE WHEN EVE2.eve_EventType IN ('Disconnected', 'Device Down') THEN EVE2.eve_DateTime ELSE NULL END AS eve_DateTimeDisconnection, - CASE WHEN EVE2.eve_EventType IS NULL THEN 1 ELSE 0 END AS eve_StillConnected, - EVE1.eve_AdditionalInfo + LEFT JOIN Devices AS d ON e.eveMac = d.devMac + INNER JOIN CurrentScan AS c ON e.eveMac = c.scanMac + WHERE e.row_num = 1; +CREATE VIEW Sessions_Devices AS SELECT * FROM Sessions LEFT JOIN Devices ON sesMac = devMac; +CREATE VIEW Convert_Events_to_Sessions AS SELECT EVE1.eveMac, + EVE1.eveIp, + EVE1.eveEventType AS eveEventTypeConnection, + EVE1.eveDateTime AS eveDateTimeConnection, + CASE WHEN EVE2.eveEventType IN ('Disconnected', 'Device Down') OR + EVE2.eveEventType IS NULL THEN EVE2.eveEventType ELSE '' END AS eveEventTypeDisconnection, + CASE WHEN EVE2.eveEventType IN ('Disconnected', 'Device Down') THEN EVE2.eveDateTime ELSE NULL END AS eveDateTimeDisconnection, + CASE WHEN EVE2.eveEventType IS NULL THEN 1 ELSE 0 END AS eveStillConnected, + EVE1.eveAdditionalInfo FROM Events AS EVE1 LEFT JOIN - Events AS EVE2 ON EVE1.eve_PairEventRowID = EVE2.RowID - WHERE EVE1.eve_EventType IN ('New Device', 'Connected','Down Reconnected') + Events AS EVE2 ON EVE1.evePairEventRowid = EVE2.RowID + WHERE EVE1.eveEventType IN ('New Device', 'Connected','Down Reconnected') UNION - SELECT eve_MAC, - eve_IP, - '' AS eve_EventTypeConnection, - NULL AS eve_DateTimeConnection, - eve_EventType AS eve_EventTypeDisconnection, - eve_DateTime AS eve_DateTimeDisconnection, - 0 AS eve_StillConnected, - eve_AdditionalInfo + SELECT eveMac, + eveIp, + '' AS eveEventTypeConnection, + NULL AS eveDateTimeConnection, + eveEventType AS eveEventTypeDisconnection, + eveDateTime AS eveDateTimeDisconnection, + 0 AS eveStillConnected, + eveAdditionalInfo FROM Events AS EVE1 - WHERE (eve_EventType = 'Device Down' OR - eve_EventType = 'Disconnected') AND - EVE1.eve_PairEventRowID IS NULL -/* Convert_Events_to_Sessions(eve_MAC,eve_IP,eve_EventTypeConnection,eve_DateTimeConnection,eve_EventTypeDisconnection,eve_DateTimeDisconnection,eve_StillConnected,eve_AdditionalInfo) */; -CREATE TRIGGER "trg_insert_devices" - AFTER INSERT ON "Devices" - WHEN NOT EXISTS ( - SELECT 1 FROM AppEvents - WHERE AppEventProcessed = 0 - AND ObjectType = 'Devices' - AND ObjectGUID = NEW.devGUID - AND ObjectStatus = CASE WHEN NEW.devPresentLastScan = 1 THEN 'online' ELSE 'offline' END - AND AppEventType = 'insert' - ) - BEGIN - INSERT INTO "AppEvents" ( - "GUID", - "DateTimeCreated", - "AppEventProcessed", - "ObjectType", - "ObjectGUID", - "ObjectPrimaryID", - "ObjectSecondaryID", - "ObjectStatus", - "ObjectStatusColumn", - "ObjectIsNew", - "ObjectIsArchived", - "ObjectForeignKey", - "ObjectPlugin", - "AppEventType" - ) - VALUES ( - - lower( - hex(randomblob(4)) || '-' || hex(randomblob(2)) || '-' || '4' || - substr(hex( randomblob(2)), 2) || '-' || - substr('AB89', 1 + (abs(random()) % 4) , 1) || - substr(hex(randomblob(2)), 2) || '-' || - hex(randomblob(6)) - ) - , - DATETIME('now'), - FALSE, - 'Devices', - NEW.devGUID, -- ObjectGUID - NEW.devMac, -- ObjectPrimaryID - NEW.devLastIP, -- ObjectSecondaryID - CASE WHEN NEW.devPresentLastScan = 1 THEN 'online' ELSE 'offline' END, -- ObjectStatus - 'devPresentLastScan', -- ObjectStatusColumn - NEW.devIsNew, -- ObjectIsNew - NEW.devIsArchived, -- ObjectIsArchived - NEW.devGUID, -- ObjectForeignKey - 'DEVICES', -- ObjectForeignKey - 'insert' - ); - END; -CREATE TRIGGER "trg_update_devices" - AFTER UPDATE ON "Devices" - WHEN NOT EXISTS ( - SELECT 1 FROM AppEvents - WHERE AppEventProcessed = 0 - AND ObjectType = 'Devices' - AND ObjectGUID = NEW.devGUID - AND ObjectStatus = CASE WHEN NEW.devPresentLastScan = 1 THEN 'online' ELSE 'offline' END - AND AppEventType = 'update' - ) - BEGIN - INSERT INTO "AppEvents" ( - "GUID", - "DateTimeCreated", - "AppEventProcessed", - "ObjectType", - "ObjectGUID", - "ObjectPrimaryID", - "ObjectSecondaryID", - "ObjectStatus", - "ObjectStatusColumn", - "ObjectIsNew", - "ObjectIsArchived", - "ObjectForeignKey", - "ObjectPlugin", - "AppEventType" - ) - VALUES ( - - lower( - hex(randomblob(4)) || '-' || hex(randomblob(2)) || '-' || '4' || - substr(hex( randomblob(2)), 2) || '-' || - substr('AB89', 1 + (abs(random()) % 4) , 1) || - substr(hex(randomblob(2)), 2) || '-' || - hex(randomblob(6)) - ) - , - DATETIME('now'), - FALSE, - 'Devices', - NEW.devGUID, -- ObjectGUID - NEW.devMac, -- ObjectPrimaryID - NEW.devLastIP, -- ObjectSecondaryID - CASE WHEN NEW.devPresentLastScan = 1 THEN 'online' ELSE 'offline' END, -- ObjectStatus - 'devPresentLastScan', -- ObjectStatusColumn - NEW.devIsNew, -- ObjectIsNew - NEW.devIsArchived, -- ObjectIsArchived - NEW.devGUID, -- ObjectForeignKey - 'DEVICES', -- ObjectForeignKey - 'update' - ); - END; -CREATE TRIGGER "trg_delete_devices" - AFTER DELETE ON "Devices" - WHEN NOT EXISTS ( - SELECT 1 FROM AppEvents - WHERE AppEventProcessed = 0 - AND ObjectType = 'Devices' - AND ObjectGUID = OLD.devGUID - AND ObjectStatus = CASE WHEN OLD.devPresentLastScan = 1 THEN 'online' ELSE 'offline' END - AND AppEventType = 'delete' - ) - BEGIN - INSERT INTO "AppEvents" ( - "GUID", - "DateTimeCreated", - "AppEventProcessed", - "ObjectType", - "ObjectGUID", - "ObjectPrimaryID", - "ObjectSecondaryID", - "ObjectStatus", - "ObjectStatusColumn", - "ObjectIsNew", - "ObjectIsArchived", - "ObjectForeignKey", - "ObjectPlugin", - "AppEventType" - ) - VALUES ( - - lower( - hex(randomblob(4)) || '-' || hex(randomblob(2)) || '-' || '4' || - substr(hex( randomblob(2)), 2) || '-' || - substr('AB89', 1 + (abs(random()) % 4) , 1) || - substr(hex(randomblob(2)), 2) || '-' || - hex(randomblob(6)) - ) - , - DATETIME('now'), - FALSE, - 'Devices', - OLD.devGUID, -- ObjectGUID - OLD.devMac, -- ObjectPrimaryID - OLD.devLastIP, -- ObjectSecondaryID - CASE WHEN OLD.devPresentLastScan = 1 THEN 'online' ELSE 'offline' END, -- ObjectStatus - 'devPresentLastScan', -- ObjectStatusColumn - OLD.devIsNew, -- ObjectIsNew - OLD.devIsArchived, -- ObjectIsArchived - OLD.devGUID, -- ObjectForeignKey - 'DEVICES', -- ObjectForeignKey - 'delete' - ); - END; + WHERE (eveEventType = 'Device Down' OR + eveEventType = 'Disconnected') AND + EVE1.evePairEventRowid IS NULL; diff --git a/server/db/sql_safe_builder.py b/server/db/sql_safe_builder.py index fc3e11e2..e5a8b480 100755 --- a/server/db/sql_safe_builder.py +++ b/server/db/sql_safe_builder.py @@ -29,10 +29,10 @@ class SafeConditionBuilder: # Whitelist of allowed column names for filtering ALLOWED_COLUMNS = { - "eve_MAC", - "eve_DateTime", - "eve_IP", - "eve_EventType", + "eveMac", + "eveDateTime", + "eveIp", + "eveEventType", "devName", "devComments", "devLastIP", @@ -43,15 +43,15 @@ class SafeConditionBuilder: "devPresentLastScan", "devFavorite", "devIsNew", - "Plugin", - "Object_PrimaryId", - "Object_SecondaryId", - "DateTimeChanged", - "Watched_Value1", - "Watched_Value2", - "Watched_Value3", - "Watched_Value4", - "Status", + "plugin", + "objectPrimaryId", + "objectSecondaryId", + "dateTimeChanged", + "watchedValue1", + "watchedValue2", + "watchedValue3", + "watchedValue4", + "status", } # Whitelist of allowed comparison operators @@ -413,7 +413,7 @@ class SafeConditionBuilder: This method handles basic patterns like: - devName = 'value' (with optional AND/OR prefix) - devComments LIKE '%value%' - - eve_EventType IN ('type1', 'type2') + - eveEventType IN ('type1', 'type2') Args: condition: Single condition string to parse @@ -648,7 +648,7 @@ class SafeConditionBuilder: self.parameters[param_name] = event_type param_names.append(f":{param_name}") - sql_snippet = f"AND eve_EventType IN ({', '.join(param_names)})" + sql_snippet = f"AND eveEventType IN ({', '.join(param_names)})" return sql_snippet, self.parameters def get_safe_condition_legacy( diff --git a/server/initialise.py b/server/initialise.py index 182b0c02..49e50b05 100755 --- a/server/initialise.py +++ b/server/initialise.py @@ -27,7 +27,7 @@ from messaging.in_app import write_notification # =============================================================================== _LANGUAGES_JSON = os.path.join( - applicationPath, "front", "php", "templates", "language", "language_definitions" ,"languages.json" + applicationPath, "front", "php", "templates", "language", "language_definitions", "languages.json" ) @@ -204,6 +204,9 @@ def importConfigs(pm, db, all_plugins): # rename settings that have changed names due to code cleanup and migration to plugins # renameSettings(config_file) + # rename legacy DB column references in user config values (e.g. templates, WATCH lists) + renameColumnReferences(config_file) + fileModifiedTime = os.path.getmtime(config_file) mylog("debug", ["[Import Config] checking config file "]) @@ -582,7 +585,7 @@ def importConfigs(pm, db, all_plugins): # bulk-import language strings sql.executemany( - """INSERT INTO Plugins_Language_Strings ("Language_Code", "String_Key", "String_Value", "Extra") VALUES (?, ?, ?, ?)""", + """INSERT INTO Plugins_Language_Strings (languageCode, stringKey, stringValue, extra) VALUES (?, ?, ?, ?)""", stringSqlParams, ) @@ -845,3 +848,78 @@ def renameSettings(config_file): else: mylog("debug", "[Config] No old setting names found in the file. No changes made.") + + +# ------------------------------------------------------------------------------- +# Rename legacy DB column names in user-persisted config values (templates, WATCH lists, etc.) +# Follows the same backup-and-replace pattern as renameSettings(). +_column_replacements = { + # Event columns + r"\beve_MAC\b": "eveMac", + r"\beve_IP\b": "eveIp", + r"\beve_DateTime\b": "eveDateTime", + r"\beve_EventType\b": "eveEventType", + r"\beve_AdditionalInfo\b": "eveAdditionalInfo", + r"\beve_PendingAlertEmail\b": "evePendingAlertEmail", + r"\beve_PairEventRowid\b": "evePairEventRowid", + # Session columns + r"\bses_MAC\b": "sesMac", + r"\bses_IP\b": "sesIp", + r"\bses_StillConnected\b": "sesStillConnected", + r"\bses_AdditionalInfo\b": "sesAdditionalInfo", + # Plugin columns (templates + WATCH values) + r"\bObject_PrimaryID\b": "objectPrimaryId", + r"\bObject_PrimaryId\b": "objectPrimaryId", + r"\bObject_SecondaryID\b": "objectSecondaryId", + r"\bObject_SecondaryId\b": "objectSecondaryId", + r"\bWatched_Value1\b": "watchedValue1", + r"\bWatched_Value2\b": "watchedValue2", + r"\bWatched_Value3\b": "watchedValue3", + r"\bWatched_Value4\b": "watchedValue4", + r"\bDateTimeChanged\b": "dateTimeChanged", + r"\bDateTimeCreated\b": "dateTimeCreated", + r"\bSyncHubNodeName\b": "syncHubNodeName", + # Online_History (in case of API_CUSTOM_SQL) + r"\bScan_Date\b": "scanDate", + r"\bOnline_Devices\b": "onlineDevices", + r"\bDown_Devices\b": "downDevices", + r"\bAll_Devices\b": "allDevices", + r"\bArchived_Devices\b": "archivedDevices", + r"\bOffline_Devices\b": "offlineDevices", + # Language strings (unlikely in user config but thorough) + r"\bLanguage_Code\b": "languageCode", + r"\bString_Key\b": "stringKey", + r"\bString_Value\b": "stringValue", +} + + +def renameColumnReferences(config_file): + """Rename legacy DB column references in the user's app.conf file.""" + contains_old_refs = False + + with open(str(config_file), "r") as f: + for line in f: + if any(re.search(key, line) for key in _column_replacements): + mylog("debug", f"[Config] Old column reference found: ({line.strip()})") + contains_old_refs = True + break + + if not contains_old_refs: + mylog("debug", "[Config] No old column references found in config. No changes made.") + return + + timestamp = timeNowUTC(as_string=False).strftime("%Y%m%d%H%M%S") + backup_file = f"{config_file}_old_column_names_{timestamp}.bak" + mylog("none", f"[Config] Renaming legacy column references — backup: {backup_file}") + shutil.copy(str(config_file), backup_file) + + with ( + open(str(config_file), "r") as original, + open(str(config_file) + "_temp", "w") as temp, + ): + for line in original: + for pattern, replacement in _column_replacements.items(): + line = re.sub(pattern, replacement, line) + temp.write(line) + + shutil.move(str(config_file) + "_temp", str(config_file)) diff --git a/server/messaging/notification_sections.py b/server/messaging/notification_sections.py index a31fb204..f88821b2 100644 --- a/server/messaging/notification_sections.py +++ b/server/messaging/notification_sections.py @@ -25,11 +25,11 @@ SECTION_TITLES = { # Which column(s) contain datetime values per section (for timezone conversion) DATETIME_FIELDS = { - "new_devices": ["eve_DateTime"], - "down_devices": ["eve_DateTime"], - "down_reconnected": ["eve_DateTime"], - "events": ["eve_DateTime"], - "plugins": ["DateTimeChanged"], + "new_devices": ["eveDateTime"], + "down_devices": ["eveDateTime"], + "down_reconnected": ["eveDateTime"], + "events": ["eveDateTime"], + "plugins": ["dateTimeChanged"], } # --------------------------------------------------------------------------- @@ -47,78 +47,78 @@ SQL_TEMPLATES = { "new_devices": """ SELECT devName, - eve_MAC, + eveMac, devVendor, - devLastIP as eve_IP, - eve_DateTime, - eve_EventType, + devLastIP as eveIp, + eveDateTime, + eveEventType, devComments FROM Events_Devices - WHERE eve_PendingAlertEmail = 1 - AND eve_EventType = 'New Device' {condition} - ORDER BY eve_DateTime + WHERE evePendingAlertEmail = 1 + AND eveEventType = 'New Device' {condition} + ORDER BY eveDateTime """, "down_devices": """ SELECT devName, - eve_MAC, + eveMac, devVendor, - eve_IP, - eve_DateTime, - eve_EventType, + eveIp, + eveDateTime, + eveEventType, devComments FROM Events_Devices AS down_events - WHERE eve_PendingAlertEmail = 1 - AND down_events.eve_EventType = 'Device Down' - AND eve_DateTime < datetime('now', '-{alert_down_minutes} minutes') + WHERE evePendingAlertEmail = 1 + AND down_events.eveEventType = 'Device Down' + AND eveDateTime < datetime('now', '-{alert_down_minutes} minutes') AND NOT EXISTS ( SELECT 1 FROM Events AS connected_events - WHERE connected_events.eve_MAC = down_events.eve_MAC - AND connected_events.eve_EventType = 'Connected' - AND connected_events.eve_DateTime > down_events.eve_DateTime + WHERE connected_events.eveMac = down_events.eveMac + AND connected_events.eveEventType = 'Connected' + AND connected_events.eveDateTime > down_events.eveDateTime ) - ORDER BY down_events.eve_DateTime + ORDER BY down_events.eveDateTime """, "down_reconnected": """ SELECT devName, - eve_MAC, + eveMac, devVendor, - eve_IP, - eve_DateTime, - eve_EventType, + eveIp, + eveDateTime, + eveEventType, devComments FROM Events_Devices AS reconnected_devices - WHERE reconnected_devices.eve_EventType = 'Down Reconnected' - AND reconnected_devices.eve_PendingAlertEmail = 1 - ORDER BY reconnected_devices.eve_DateTime + WHERE reconnected_devices.eveEventType = 'Down Reconnected' + AND reconnected_devices.evePendingAlertEmail = 1 + ORDER BY reconnected_devices.eveDateTime """, "events": """ SELECT devName, - eve_MAC, + eveMac, devVendor, - devLastIP as eve_IP, - eve_DateTime, - eve_EventType, + devLastIP as eveIp, + eveDateTime, + eveEventType, devComments FROM Events_Devices - WHERE eve_PendingAlertEmail = 1 - AND eve_EventType IN ('Connected', 'Down Reconnected', 'Disconnected','IP Changed') {condition} - ORDER BY eve_DateTime + WHERE evePendingAlertEmail = 1 + AND eveEventType IN ('Connected', 'Down Reconnected', 'Disconnected','IP Changed') {condition} + ORDER BY eveDateTime """, "plugins": """ SELECT - Plugin, - Object_PrimaryId, - Object_SecondaryId, - DateTimeChanged, - Watched_Value1, - Watched_Value2, - Watched_Value3, - Watched_Value4, - Status + plugin, + objectPrimaryId, + objectSecondaryId, + dateTimeChanged, + watchedValue1, + watchedValue2, + watchedValue3, + watchedValue4, + status FROM Plugins_Events """, } diff --git a/server/messaging/reporting.py b/server/messaging/reporting.py index df0406d5..45a37453 100755 --- a/server/messaging/reporting.py +++ b/server/messaging/reporting.py @@ -114,16 +114,16 @@ def get_notifications(db): # Disable events where reporting is disabled sql.execute(""" - UPDATE Events SET eve_PendingAlertEmail = 0 - WHERE eve_PendingAlertEmail = 1 - AND eve_EventType NOT IN ('Device Down', 'Down Reconnected', 'New Device') - AND eve_MAC IN (SELECT devMac FROM Devices WHERE devAlertEvents = 0) + UPDATE Events SET evePendingAlertEmail = 0 + WHERE evePendingAlertEmail = 1 + AND eveEventType NOT IN ('Device Down', 'Down Reconnected', 'New Device') + AND eveMac IN (SELECT devMac FROM Devices WHERE devAlertEvents = 0) """) sql.execute(""" - UPDATE Events SET eve_PendingAlertEmail = 0 - WHERE eve_PendingAlertEmail = 1 - AND eve_EventType IN ('Device Down', 'Down Reconnected') - AND eve_MAC IN (SELECT devMac FROM Devices WHERE devAlertDown = 0) + UPDATE Events SET evePendingAlertEmail = 0 + WHERE evePendingAlertEmail = 1 + AND eveEventType IN ('Device Down', 'Down Reconnected') + AND eveMac IN (SELECT devMac FROM Devices WHERE devAlertDown = 0) """) alert_down_minutes = int(get_setting_value("NTFPRCS_alert_down_time") or 0) @@ -233,7 +233,7 @@ def skip_repeated_notifications(db): """ Skips sending alerts for devices recently notified. - Clears `eve_PendingAlertEmail` for events linked to devices whose last + Clears `evePendingAlertEmail` for events linked to devices whose last notification time is within their `devSkipRepeated` interval. Args: @@ -244,8 +244,8 @@ def skip_repeated_notifications(db): # due strfime : Overflow --> use "strftime / 60" mylog("verbose", "[Skip Repeated Notifications] Skip Repeated") - db.sql.execute("""UPDATE Events SET eve_PendingAlertEmail = 0 - WHERE eve_PendingAlertEmail = 1 AND eve_MAC IN + db.sql.execute("""UPDATE Events SET evePendingAlertEmail = 0 + WHERE evePendingAlertEmail = 1 AND eveMac IN ( SELECT devMac FROM Devices WHERE devLastNotification IS NOT NULL diff --git a/server/models/device_instance.py b/server/models/device_instance.py index 266ec55a..1384a75f 100755 --- a/server/models/device_instance.py +++ b/server/models/device_instance.py @@ -142,17 +142,17 @@ class DeviceInstance: objs = PluginObjectInstance().getByField( plugPrefix='NMAP', - matchedColumn='Object_PrimaryID', + matchedColumn='objectPrimaryId', matchedKey=primary, - returnFields=['Object_SecondaryID', 'Watched_Value2'] + returnFields=['objectSecondaryId', 'watchedValue2'] ) ports = [] for o in objs: - port = int(o.get('Object_SecondaryID') or 0) + port = int(o.get('objectSecondaryId') or 0) - ports.append({"port": port, "service": o.get('Watched_Value2', '')}) + ports.append({"port": port, "service": o.get('watchedValue2', '')}) return ports @@ -471,31 +471,31 @@ class DeviceInstance: LOWER(d.devParentMAC) AS devParentMAC, (SELECT COUNT(*) FROM Sessions - WHERE LOWER(ses_MAC) = LOWER(d.devMac) AND ( - ses_DateTimeConnection >= {period_date_sql} OR - ses_DateTimeDisconnection >= {period_date_sql} OR - ses_StillConnected = 1 + WHERE LOWER(sesMac) = LOWER(d.devMac) AND ( + sesDateTimeConnection >= {period_date_sql} OR + sesDateTimeDisconnection >= {period_date_sql} OR + sesStillConnected = 1 )) AS devSessions, (SELECT COUNT(*) FROM Events - WHERE LOWER(eve_MAC) = LOWER(d.devMac) AND eve_DateTime >= {period_date_sql} - AND eve_EventType NOT IN ('Connected','Disconnected')) AS devEvents, + WHERE LOWER(eveMac) = LOWER(d.devMac) AND eveDateTime >= {period_date_sql} + AND eveEventType NOT IN ('Connected','Disconnected')) AS devEvents, (SELECT COUNT(*) FROM Events - WHERE LOWER(eve_MAC) = LOWER(d.devMac) AND eve_DateTime >= {period_date_sql} - AND eve_EventType = 'Device Down') AS devDownAlerts, + WHERE LOWER(eveMac) = LOWER(d.devMac) AND eveDateTime >= {period_date_sql} + AND eveEventType = 'Device Down') AS devDownAlerts, (SELECT CAST(MAX(0, SUM( - julianday(IFNULL(ses_DateTimeDisconnection,'{now}')) - - julianday(CASE WHEN ses_DateTimeConnection < {period_date_sql} - THEN {period_date_sql} ELSE ses_DateTimeConnection END) + julianday(IFNULL(sesDateTimeDisconnection,'{now}')) - + julianday(CASE WHEN sesDateTimeConnection < {period_date_sql} + THEN {period_date_sql} ELSE sesDateTimeConnection END) ) * 24) AS INT) FROM Sessions - WHERE LOWER(ses_MAC) = LOWER(d.devMac) - AND ses_DateTimeConnection IS NOT NULL - AND (ses_DateTimeDisconnection IS NOT NULL OR ses_StillConnected = 1) - AND (ses_DateTimeConnection >= {period_date_sql} - OR ses_DateTimeDisconnection >= {period_date_sql} OR ses_StillConnected = 1) + WHERE LOWER(sesMac) = LOWER(d.devMac) + AND sesDateTimeConnection IS NOT NULL + AND (sesDateTimeDisconnection IS NOT NULL OR sesStillConnected = 1) + AND (sesDateTimeConnection >= {period_date_sql} + OR sesDateTimeDisconnection >= {period_date_sql} OR sesStillConnected = 1) ) AS devPresenceHours FROM DevicesView d @@ -797,7 +797,7 @@ class DeviceInstance: """Delete all events for a device.""" conn = get_temp_db_connection() cur = conn.cursor() - cur.execute("DELETE FROM Events WHERE eve_MAC=?", (mac,)) + cur.execute("DELETE FROM Events WHERE eveMac=?", (mac,)) conn.commit() conn.close() return {"success": True} diff --git a/server/models/event_instance.py b/server/models/event_instance.py index 0b166ad6..6222fd3b 100644 --- a/server/models/event_instance.py +++ b/server/models/event_instance.py @@ -21,7 +21,7 @@ class EventInstance: def get_all(self): conn = self._conn() rows = conn.execute( - "SELECT * FROM Events ORDER BY eve_DateTime DESC" + "SELECT * FROM Events ORDER BY eveDateTime DESC" ).fetchall() conn.close() return self._rows_to_list(rows) @@ -31,7 +31,7 @@ class EventInstance: conn = self._conn() rows = conn.execute(""" SELECT * FROM Events - ORDER BY eve_DateTime DESC + ORDER BY eveDateTime DESC LIMIT ? """, (n,)).fetchall() conn.close() @@ -47,8 +47,8 @@ class EventInstance: conn = self._conn() rows = conn.execute(""" SELECT * FROM Events - WHERE eve_DateTime >= ? - ORDER BY eve_DateTime DESC + WHERE eveDateTime >= ? + ORDER BY eveDateTime DESC """, (since,)).fetchall() conn.close() return self._rows_to_list(rows) @@ -63,8 +63,8 @@ class EventInstance: conn = self._conn() rows = conn.execute(""" SELECT * FROM Events - WHERE eve_DateTime >= ? - ORDER BY eve_DateTime DESC + WHERE eveDateTime >= ? + ORDER BY eveDateTime DESC """, (since,)).fetchall() conn.close() return self._rows_to_list(rows) @@ -78,8 +78,8 @@ class EventInstance: conn = self._conn() rows = conn.execute(""" SELECT * FROM Events - WHERE eve_DateTime BETWEEN ? AND ? - ORDER BY eve_DateTime DESC + WHERE eveDateTime BETWEEN ? AND ? + ORDER BY eveDateTime DESC """, (start, end)).fetchall() conn.close() return self._rows_to_list(rows) @@ -89,9 +89,9 @@ class EventInstance: conn = self._conn() conn.execute(""" INSERT OR IGNORE INTO Events ( - eve_MAC, eve_IP, eve_DateTime, - eve_EventType, eve_AdditionalInfo, - eve_PendingAlertEmail, eve_PairEventRowid + eveMac, eveIp, eveDateTime, + eveEventType, eveAdditionalInfo, + evePendingAlertEmail, evePairEventRowid ) VALUES (?,?,?,?,?,?,?) """, (mac, ip, timeNowUTC(), eventType, info, 1 if pendingAlert else 0, pairRow)) @@ -102,7 +102,7 @@ class EventInstance: def delete_older_than(self, days: int): cutoff = timeNowUTC(as_string=False) - timedelta(days=days) conn = self._conn() - result = conn.execute("DELETE FROM Events WHERE eve_DateTime < ?", (cutoff,)) + result = conn.execute("DELETE FROM Events WHERE eveDateTime < ?", (cutoff,)) conn.commit() deleted_count = result.rowcount conn.close() @@ -124,7 +124,7 @@ class EventInstance: cur = conn.cursor() cur.execute( """ - INSERT OR IGNORE INTO Events (eve_MAC, eve_IP, eve_DateTime, eve_EventType, eve_AdditionalInfo, eve_PendingAlertEmail) + INSERT OR IGNORE INTO Events (eveMac, eveIp, eveDateTime, eveEventType, eveAdditionalInfo, evePendingAlertEmail) VALUES (?, ?, ?, ?, ?, ?) """, (mac, ip, start_time, event_type, additional_info, pending_alert), @@ -145,10 +145,10 @@ class EventInstance: cur = conn.cursor() if mac: - sql = "SELECT * FROM Events WHERE eve_MAC=? ORDER BY eve_DateTime DESC" + sql = "SELECT * FROM Events WHERE eveMac=? ORDER BY eveDateTime DESC" cur.execute(sql, (mac,)) else: - sql = "SELECT * FROM Events ORDER BY eve_DateTime DESC" + sql = "SELECT * FROM Events ORDER BY eveDateTime DESC" cur.execute(sql) rows = cur.fetchall() @@ -163,7 +163,7 @@ class EventInstance: cur = conn.cursor() # Use a parameterized query with sqlite date function - sql = "DELETE FROM Events WHERE eve_DateTime <= date('now', ?)" + sql = "DELETE FROM Events WHERE eveDateTime <= date('now', ?)" cur.execute(sql, [f"-{days} days"]) conn.commit() @@ -197,19 +197,19 @@ class EventInstance: sql = f""" SELECT - (SELECT COUNT(*) FROM Events WHERE eve_DateTime >= {period_date_sql}) AS all_events, + (SELECT COUNT(*) FROM Events WHERE eveDateTime >= {period_date_sql}) AS all_events, (SELECT COUNT(*) FROM Sessions WHERE - ses_DateTimeConnection >= {period_date_sql} - OR ses_DateTimeDisconnection >= {period_date_sql} - OR ses_StillConnected = 1 + sesDateTimeConnection >= {period_date_sql} + OR sesDateTimeDisconnection >= {period_date_sql} + OR sesStillConnected = 1 ) AS sessions, (SELECT COUNT(*) FROM Sessions WHERE - (ses_DateTimeConnection IS NULL AND ses_DateTimeDisconnection >= {period_date_sql}) - OR (ses_DateTimeDisconnection IS NULL AND ses_StillConnected = 0 AND ses_DateTimeConnection >= {period_date_sql}) + (sesDateTimeConnection IS NULL AND sesDateTimeDisconnection >= {period_date_sql}) + OR (sesDateTimeDisconnection IS NULL AND sesStillConnected = 0 AND sesDateTimeConnection >= {period_date_sql}) ) AS missing, - (SELECT COUNT(*) FROM Events WHERE eve_DateTime >= {period_date_sql} AND eve_EventType LIKE 'VOIDED%') AS voided, - (SELECT COUNT(*) FROM Events WHERE eve_DateTime >= {period_date_sql} AND eve_EventType LIKE 'New Device') AS new, - (SELECT COUNT(*) FROM Events WHERE eve_DateTime >= {period_date_sql} AND eve_EventType LIKE 'Device Down') AS down + (SELECT COUNT(*) FROM Events WHERE eveDateTime >= {period_date_sql} AND eveEventType LIKE 'VOIDED%') AS voided, + (SELECT COUNT(*) FROM Events WHERE eveDateTime >= {period_date_sql} AND eveEventType LIKE 'New Device') AS new, + (SELECT COUNT(*) FROM Events WHERE eveDateTime >= {period_date_sql} AND eveEventType LIKE 'Device Down') AS down """ cur.execute(sql) @@ -247,11 +247,11 @@ class EventInstance: conn = self._conn() sql = """ - SELECT eve_MAC, COUNT(*) as event_count + SELECT eveMac, COUNT(*) as event_count FROM Events - WHERE eve_EventType IN ('Connected','Disconnected','Device Down','Down Reconnected') - AND eve_DateTime >= datetime('now', ?) - GROUP BY eve_MAC + WHERE eveEventType IN ('Connected','Disconnected','Device Down','Down Reconnected') + AND eveDateTime >= datetime('now', ?) + GROUP BY eveMac HAVING COUNT(*) >= ? """ @@ -262,6 +262,6 @@ class EventInstance: conn.close() if macs_only: - return {row["eve_MAC"] for row in rows} + return {row["eveMac"] for row in rows} return [dict(row) for row in rows] diff --git a/server/models/notification_instance.py b/server/models/notification_instance.py index b75668fe..38095093 100755 --- a/server/models/notification_instance.py +++ b/server/models/notification_instance.py @@ -31,17 +31,17 @@ class NotificationInstance: # Create Notifications table if missing self.db.sql.execute("""CREATE TABLE IF NOT EXISTS "Notifications" ( - "Index" INTEGER, - "GUID" TEXT UNIQUE, - "DateTimeCreated" TEXT, - "DateTimePushed" TEXT, - "Status" TEXT, - "JSON" TEXT, - "Text" TEXT, - "HTML" TEXT, - "PublishedVia" TEXT, - "Extra" TEXT, - PRIMARY KEY("Index" AUTOINCREMENT) + "index" INTEGER, + "guid" TEXT UNIQUE, + "dateTimeCreated" TEXT, + "dateTimePushed" TEXT, + "status" TEXT, + "json" TEXT, + "text" TEXT, + "html" TEXT, + "publishedVia" TEXT, + "extra" TEXT, + PRIMARY KEY("index" AUTOINCREMENT) ); """) @@ -178,7 +178,7 @@ class NotificationInstance: def upsert(self): self.db.sql.execute( """ - INSERT OR REPLACE INTO Notifications (GUID, DateTimeCreated, DateTimePushed, Status, JSON, Text, HTML, PublishedVia, Extra) + INSERT OR REPLACE INTO Notifications (guid, dateTimeCreated, dateTimePushed, "status", "json", "text", html, publishedVia, extra) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( @@ -202,7 +202,7 @@ class NotificationInstance: self.db.sql.execute( """ DELETE FROM Notifications - WHERE GUID = ? + WHERE guid = ? """, (GUID,), ) @@ -212,7 +212,7 @@ class NotificationInstance: def getNew(self): self.db.sql.execute(""" SELECT * FROM Notifications - WHERE Status = "new" + WHERE "status" = 'new' """) return self.db.sql.fetchall() @@ -221,8 +221,8 @@ class NotificationInstance: # Execute an SQL query to update the status of all notifications self.db.sql.execute(""" UPDATE Notifications - SET Status = "processed" - WHERE Status = "new" + SET "status" = 'processed' + WHERE "status" = 'new' """) self.save() @@ -234,15 +234,15 @@ class NotificationInstance: self.db.sql.execute(""" UPDATE Devices SET devLastNotification = ? WHERE devMac IN ( - SELECT eve_MAC FROM Events - WHERE eve_PendingAlertEmail = 1 + SELECT eveMac FROM Events + WHERE evePendingAlertEmail = 1 ) """, (timeNowUTC(),)) self.db.sql.execute(""" - UPDATE Events SET eve_PendingAlertEmail = 0 - WHERE eve_PendingAlertEmail = 1 - AND eve_EventType !='Device Down' """) + UPDATE Events SET evePendingAlertEmail = 0 + WHERE evePendingAlertEmail = 1 + AND eveEventType !='Device Down' """) # Clear down events flag after the reporting window passed minutes = int(get_setting_value("NTFPRCS_alert_down_time") or 0) @@ -250,10 +250,10 @@ class NotificationInstance: self.db.sql.execute( """ UPDATE Events - SET eve_PendingAlertEmail = 0 - WHERE eve_PendingAlertEmail = 1 - AND eve_EventType = 'Device Down' - AND eve_DateTime < datetime('now', ?, ?) + SET evePendingAlertEmail = 0 + WHERE evePendingAlertEmail = 1 + AND eveEventType = 'Device Down' + AND eveDateTime < datetime('now', ?, ?) """, (f"-{minutes} minutes", tz_offset), ) diff --git a/server/models/plugin_object_instance.py b/server/models/plugin_object_instance.py index 30b2bf45..5018b6d5 100755 --- a/server/models/plugin_object_instance.py +++ b/server/models/plugin_object_instance.py @@ -35,18 +35,18 @@ class PluginObjectInstance: def getByGUID(self, ObjectGUID): return self._fetchone( - "SELECT * FROM Plugins_Objects WHERE ObjectGUID = ?", (ObjectGUID,) + "SELECT * FROM Plugins_Objects WHERE objectGuid = ?", (ObjectGUID,) ) def exists(self, ObjectGUID): row = self._fetchone(""" - SELECT COUNT(*) AS count FROM Plugins_Objects WHERE ObjectGUID = ? + SELECT COUNT(*) AS count FROM Plugins_Objects WHERE objectGuid = ? """, (ObjectGUID,)) return row["count"] > 0 if row else False def getByPlugin(self, plugin): return self._fetchall( - "SELECT * FROM Plugins_Objects WHERE Plugin = ?", (plugin,) + "SELECT * FROM Plugins_Objects WHERE plugin = ?", (plugin,) ) def getLastNCreatedPerPlugin(self, plugin, entries=1): @@ -54,8 +54,8 @@ class PluginObjectInstance: """ SELECT * FROM Plugins_Objects - WHERE Plugin = ? - ORDER BY DateTimeCreated DESC + WHERE plugin = ? + ORDER BY dateTimeCreated DESC LIMIT ? """, (plugin, entries), @@ -63,7 +63,7 @@ class PluginObjectInstance: def getByField(self, plugPrefix, matchedColumn, matchedKey, returnFields=None): rows = self._fetchall( - f"SELECT * FROM Plugins_Objects WHERE Plugin = ? AND {matchedColumn} = ?", + f"SELECT * FROM Plugins_Objects WHERE plugin = ? AND {matchedColumn} = ?", (plugPrefix, matchedKey.lower()) ) @@ -75,12 +75,12 @@ class PluginObjectInstance: def getByPrimary(self, plugin, primary_id): return self._fetchall(""" SELECT * FROM Plugins_Objects - WHERE Plugin = ? AND Object_PrimaryID = ? + WHERE plugin = ? AND objectPrimaryId = ? """, (plugin, primary_id)) def getByStatus(self, status): return self._fetchall(""" - SELECT * FROM Plugins_Objects WHERE Status = ? + SELECT * FROM Plugins_Objects WHERE "status" = ? """, (status,)) def updateField(self, ObjectGUID, field, value): @@ -90,7 +90,7 @@ class PluginObjectInstance: raise ValueError(msg) self._execute( - f"UPDATE Plugins_Objects SET {field}=? WHERE ObjectGUID=?", + f"UPDATE Plugins_Objects SET {field}=? WHERE objectGuid=?", (value, ObjectGUID) ) @@ -100,4 +100,4 @@ class PluginObjectInstance: mylog("none", msg) raise ValueError(msg) - self._execute("DELETE FROM Plugins_Objects WHERE ObjectGUID=?", (ObjectGUID,)) + self._execute("DELETE FROM Plugins_Objects WHERE objectGuid=?", (ObjectGUID,)) diff --git a/server/plugin.py b/server/plugin.py index 4d5cf563..0705dcb1 100755 --- a/server/plugin.py +++ b/server/plugin.py @@ -238,11 +238,11 @@ class plugin_manager: if plugin_name: # Only compute for single plugin sql.execute( """ - SELECT MAX(DateTimeChanged) AS last_changed, + SELECT MAX(dateTimeChanged) AS last_changed, COUNT(*) AS total_objects, - SUM(CASE WHEN DateTimeCreated = DateTimeChanged THEN 1 ELSE 0 END) AS new_objects + SUM(CASE WHEN dateTimeCreated = dateTimeChanged THEN 1 ELSE 0 END) AS new_objects FROM Plugins_Objects - WHERE Plugin = ? + WHERE plugin = ? """, (plugin_name,), ) @@ -264,12 +264,12 @@ class plugin_manager: else: # Compute for all plugins (full refresh) sql.execute(""" - SELECT Plugin, - MAX(DateTimeChanged) AS last_changed, + SELECT plugin, + MAX(dateTimeChanged) AS last_changed, COUNT(*) AS total_objects, - SUM(CASE WHEN DateTimeCreated = DateTimeChanged THEN 1 ELSE 0 END) AS new_objects + SUM(CASE WHEN dateTimeCreated = dateTimeChanged THEN 1 ELSE 0 END) AS new_objects FROM Plugins_Objects - GROUP BY Plugin + GROUP BY plugin """) for plugin, last_changed, total_objects, new_objects in sql.fetchall(): new_objects = new_objects or 0 # ensure it's int @@ -496,22 +496,22 @@ def execute_plugin(db, all_plugins, plugin): # Common part of the SQL parameters base_params = [ - 0, # "Index" placeholder + 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 + ], # "plugin" column value from the plugin dictionary + columns[0], # "objectPrimaryId" value from columns list + columns[1], # "objectSecondaryId" value from columns list + "null", # Placeholder for "dateTimeCreated" column + columns[2], # "dateTimeChanged" value from columns list + columns[3], # "watchedValue1" value from columns list + columns[4], # "watchedValue2" value from columns list + columns[5], # "watchedValue3" value from columns list + columns[6], # "watchedValue4" 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 ] @@ -566,26 +566,26 @@ def execute_plugin(db, all_plugins, plugin): # 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. base_params = [ - 0, # "Index" placeholder - plugin["unique_prefix"], # "Plugin" plugin dictionary - row[0], # "Object_PrimaryID" row + 0, # "index" placeholder + plugin["unique_prefix"], # "plugin" plugin dictionary + row[0], # "objectPrimaryId" 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 + ), # "objectSecondaryId" column after handling empty values + "null", # Placeholder "dateTimeCreated" column + row[2], # "dateTimeChanged" row + row[3], # "watchedValue1" row + row[4], # "watchedValue2" row handle_empty( row[5] - ), # "Watched_Value3" column after handling empty values + ), # "watchedValue3" 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 + ), # "watchedValue4" 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 ] @@ -654,41 +654,41 @@ def execute_plugin(db, all_plugins, plugin): # 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. 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 + 0, # "index" placeholder + plugin["unique_prefix"], # "plugin" + row[0], # "objectPrimaryId" + handle_empty(row[1]), # "objectSecondaryId" + "null", # "dateTimeCreated" column (null placeholder) + row[2], # "dateTimeChanged" + row[3], # "watchedValue1" + row[4], # "watchedValue2" + handle_empty(row[5]), # "watchedValue3" + handle_empty(row[6]), # "watchedValue4" + "not-processed", # "status" column (placeholder) + row[7], # "extra" + "null", # "userData" column (null placeholder) + row[8], # "foreignKey" + "null", # syncHubNodeName - 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" + 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" + "null", # "helpVal1" + "null", # "helpVal2" + "null", # "helpVal3" + "null", # "helpVal4" ] ) @@ -749,7 +749,7 @@ def process_plugin_events(db, plugin, plugEventsArr): # Create plugin objects from existing database entries plugObjectsArr = db.get_sql_array( - "SELECT * FROM Plugins_Objects where Plugin = '" + str(pluginPref) + "'" + "SELECT * FROM Plugins_Objects where plugin = '" + str(pluginPref) + "'" ) for obj in plugObjectsArr: @@ -894,11 +894,11 @@ def process_plugin_events(db, plugin, plugEventsArr): sql.executemany( """ INSERT INTO Plugins_Objects - ("Plugin", "Object_PrimaryID", "Object_SecondaryID", "DateTimeCreated", - "DateTimeChanged", "Watched_Value1", "Watched_Value2", "Watched_Value3", - "Watched_Value4", "Status", "Extra", "UserData", "ForeignKey", "SyncHubNodeName", - "HelpVal1", "HelpVal2", "HelpVal3", "HelpVal4", - "ObjectGUID") + ("plugin", "objectPrimaryId", "objectSecondaryId", "dateTimeCreated", + "dateTimeChanged", "watchedValue1", "watchedValue2", "watchedValue3", + "watchedValue4", "status", "extra", "userData", "foreignKey", "syncHubNodeName", + "helpVal1", "helpVal2", "helpVal3", "helpVal4", + "objectGuid") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, objects_to_insert, @@ -909,12 +909,12 @@ def process_plugin_events(db, plugin, plugEventsArr): sql.executemany( """ UPDATE Plugins_Objects - SET "Plugin" = ?, "Object_PrimaryID" = ?, "Object_SecondaryID" = ?, "DateTimeCreated" = ?, - "DateTimeChanged" = ?, "Watched_Value1" = ?, "Watched_Value2" = ?, "Watched_Value3" = ?, - "Watched_Value4" = ?, "Status" = ?, "Extra" = ?, "UserData" = ?, "ForeignKey" = ?, "SyncHubNodeName" = ?, - "HelpVal1" = ?, "HelpVal2" = ?, "HelpVal3" = ?, "HelpVal4" = ?, - "ObjectGUID" = ? - WHERE "Index" = ? + SET "plugin" = ?, "objectPrimaryId" = ?, "objectSecondaryId" = ?, "dateTimeCreated" = ?, + "dateTimeChanged" = ?, "watchedValue1" = ?, "watchedValue2" = ?, "watchedValue3" = ?, + "watchedValue4" = ?, "status" = ?, "extra" = ?, "userData" = ?, "foreignKey" = ?, "syncHubNodeName" = ?, + "helpVal1" = ?, "helpVal2" = ?, "helpVal3" = ?, "helpVal4" = ?, + "objectGuid" = ? + WHERE "index" = ? """, objects_to_update, ) @@ -924,11 +924,11 @@ def process_plugin_events(db, plugin, plugEventsArr): sql.executemany( """ INSERT INTO Plugins_Events - ("Plugin", "Object_PrimaryID", "Object_SecondaryID", "DateTimeCreated", - "DateTimeChanged", "Watched_Value1", "Watched_Value2", "Watched_Value3", - "Watched_Value4", "Status", "Extra", "UserData", "ForeignKey", "SyncHubNodeName", - "HelpVal1", "HelpVal2", "HelpVal3", "HelpVal4", - "ObjectGUID") + ("plugin", "objectPrimaryId", "objectSecondaryId", "dateTimeCreated", + "dateTimeChanged", "watchedValue1", "watchedValue2", "watchedValue3", + "watchedValue4", "status", "extra", "userData", "foreignKey", "syncHubNodeName", + "helpVal1", "helpVal2", "helpVal3", "helpVal4", + "objectGuid") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, events_to_insert, @@ -939,11 +939,11 @@ def process_plugin_events(db, plugin, plugEventsArr): sql.executemany( """ INSERT INTO Plugins_History - ("Plugin", "Object_PrimaryID", "Object_SecondaryID", "DateTimeCreated", - "DateTimeChanged", "Watched_Value1", "Watched_Value2", "Watched_Value3", - "Watched_Value4", "Status", "Extra", "UserData", "ForeignKey", "SyncHubNodeName", - "HelpVal1", "HelpVal2", "HelpVal3", "HelpVal4", - "ObjectGUID") + ("plugin", "objectPrimaryId", "objectSecondaryId", "dateTimeCreated", + "dateTimeChanged", "watchedValue1", "watchedValue2", "watchedValue3", + "watchedValue4", "status", "extra", "userData", "foreignKey", "syncHubNodeName", + "helpVal1", "helpVal2", "helpVal3", "helpVal4", + "objectGuid") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, history_to_insert, @@ -993,41 +993,41 @@ def process_plugin_events(db, plugin, plugEventsArr): tmpList = [] for col in mappedCols: - if col["column"] == "Index": + if col["column"] == "index": tmpList.append(plgEv.index) - elif col["column"] == "Plugin": + elif col["column"] == "plugin": tmpList.append(plgEv.pluginPref) - elif col["column"] == "Object_PrimaryID": + elif col["column"] == "objectPrimaryId": tmpList.append(plgEv.primaryId) - elif col["column"] == "Object_SecondaryID": + elif col["column"] == "objectSecondaryId": tmpList.append(plgEv.secondaryId) - elif col["column"] == "DateTimeCreated": + elif col["column"] == "dateTimeCreated": tmpList.append(plgEv.created) - elif col["column"] == "DateTimeChanged": + elif col["column"] == "dateTimeChanged": tmpList.append(plgEv.changed) - elif col["column"] == "Watched_Value1": + elif col["column"] == "watchedValue1": tmpList.append(plgEv.watched1) - elif col["column"] == "Watched_Value2": + elif col["column"] == "watchedValue2": tmpList.append(plgEv.watched2) - elif col["column"] == "Watched_Value3": + elif col["column"] == "watchedValue3": tmpList.append(plgEv.watched3) - elif col["column"] == "Watched_Value4": + elif col["column"] == "watchedValue4": tmpList.append(plgEv.watched4) - elif col["column"] == "UserData": + elif col["column"] == "userData": tmpList.append(plgEv.userData) - elif col["column"] == "Extra": + elif col["column"] == "extra": tmpList.append(plgEv.extra) - elif col["column"] == "Status": + elif col["column"] == "status": tmpList.append(plgEv.status) - elif col["column"] == "SyncHubNodeName": + elif col["column"] == "syncHubNodeName": tmpList.append(plgEv.syncHubNodeName) - elif col["column"] == "HelpVal1": + elif col["column"] == "helpVal1": tmpList.append(plgEv.helpVal1) - elif col["column"] == "HelpVal2": + elif col["column"] == "helpVal2": tmpList.append(plgEv.helpVal2) - elif col["column"] == "HelpVal3": + elif col["column"] == "helpVal3": tmpList.append(plgEv.helpVal3) - elif col["column"] == "HelpVal4": + elif col["column"] == "helpVal4": tmpList.append(plgEv.helpVal4) # Check if there's a default value specified for this column in the JSON. @@ -1113,10 +1113,10 @@ class plugin_object_class: # hash for comapring watched value changes indexNameColumnMapping = [ - (6, "Watched_Value1"), - (7, "Watched_Value2"), - (8, "Watched_Value3"), - (9, "Watched_Value4"), + (6, "watchedValue1"), + (7, "watchedValue2"), + (8, "watchedValue3"), + (9, "watchedValue4"), ] if setObj is not None: diff --git a/server/scan/device_handling.py b/server/scan/device_handling.py index 7d60c13d..63673eda 100755 --- a/server/scan/device_handling.py +++ b/server/scan/device_handling.py @@ -581,8 +581,8 @@ def print_scan_stats(db): row_dict = dict(row) mylog("trace", f" {row_dict}") - mylog("trace", " ================ Events table content where eve_PendingAlertEmail = 1 ================",) - sql.execute("select * from Events where eve_PendingAlertEmail = 1") + mylog("trace", " ================ Events table content where evePendingAlertEmail = 1 ================",) + sql.execute("select * from Events where evePendingAlertEmail = 1") rows = sql.fetchall() for row in rows: row_dict = dict(row) @@ -611,9 +611,9 @@ def create_new_devices(db): mylog("debug", '[New Devices] Insert "New Device" Events') query_new_device_events = f""" INSERT OR IGNORE INTO Events ( - eve_MAC, eve_IP, eve_DateTime, - eve_EventType, eve_AdditionalInfo, - eve_PendingAlertEmail + eveMac, eveIp, eveDateTime, + eveEventType, eveAdditionalInfo, + evePendingAlertEmail ) SELECT DISTINCT scanMac, scanLastIP, '{startTime}', 'New Device', scanVendor, 1 FROM CurrentScan @@ -630,9 +630,9 @@ def create_new_devices(db): mylog("debug", "[New Devices] Insert Connection into session table") sql.execute(f"""INSERT INTO Sessions ( - ses_MAC, ses_IP, ses_EventTypeConnection, ses_DateTimeConnection, - ses_EventTypeDisconnection, ses_DateTimeDisconnection, - ses_StillConnected, ses_AdditionalInfo + sesMac, sesIp, sesEventTypeConnection, sesDateTimeConnection, + sesEventTypeDisconnection, sesDateTimeDisconnection, + sesStillConnected, sesAdditionalInfo ) SELECT scanMac, scanLastIP, 'Connected', '{startTime}', NULL, NULL, 1, scanVendor FROM CurrentScan @@ -642,7 +642,7 @@ def create_new_devices(db): ) AND NOT EXISTS ( SELECT 1 FROM Sessions - WHERE ses_MAC = scanMac AND ses_StillConnected = 1 + WHERE sesMac = scanMac AND sesStillConnected = 1 ) """) diff --git a/server/scan/name_resolution.py b/server/scan/name_resolution.py index 8a9e226c..e05b4a15 100755 --- a/server/scan/name_resolution.py +++ b/server/scan/name_resolution.py @@ -24,8 +24,8 @@ class NameResolver: # Check by MAC sql.execute(f""" - SELECT Watched_Value2 FROM Plugins_Objects - WHERE Plugin = '{plugin}' AND Object_PrimaryID = '{pMAC}' + SELECT watchedValue2 FROM Plugins_Objects + WHERE plugin = '{plugin}' AND objectPrimaryId = '{pMAC}' """) result = sql.fetchall() # self.db.commitDB() # Issue #1251: Optimize name resolution lookup @@ -37,8 +37,8 @@ class NameResolver: if get_setting_value('NEWDEV_IP_MATCH_NAME'): sql.execute(f""" - SELECT Watched_Value2 FROM Plugins_Objects - WHERE Plugin = '{plugin}' AND Object_SecondaryID = '{pIP}' + SELECT watchedValue2 FROM Plugins_Objects + WHERE plugin = '{plugin}' AND objectSecondaryId = '{pIP}' """) result = sql.fetchall() # self.db.commitDB() # Issue #1251: Optimize name resolution lookup diff --git a/server/scan/session_events.py b/server/scan/session_events.py index 6fe39ebf..4a0cf588 100755 --- a/server/scan/session_events.py +++ b/server/scan/session_events.py @@ -120,27 +120,27 @@ def pair_sessions_events(db): mylog("debug", "[Pair Session] - 1 Connections / New Devices") sql.execute("""UPDATE Events - SET eve_PairEventRowid = + SET evePairEventRowid = (SELECT ROWID FROM Events AS EVE2 - WHERE EVE2.eve_EventType IN ('New Device', 'Connected', 'Down Reconnected', + WHERE EVE2.eveEventType IN ('New Device', 'Connected', 'Down Reconnected', 'Device Down', 'Disconnected') - AND EVE2.eve_MAC = Events.eve_MAC - AND EVE2.eve_Datetime > Events.eve_DateTime - ORDER BY EVE2.eve_DateTime ASC LIMIT 1) - WHERE eve_EventType IN ('New Device', 'Connected', 'Down Reconnected') - AND eve_PairEventRowid IS NULL + AND EVE2.eveMac = Events.eveMac + AND EVE2.eveDateTime > Events.eveDateTime + ORDER BY EVE2.eveDateTime ASC LIMIT 1) + WHERE eveEventType IN ('New Device', 'Connected', 'Down Reconnected') + AND evePairEventRowid IS NULL """) # Pair Disconnection / Device Down mylog("debug", "[Pair Session] - 2 Disconnections") sql.execute("""UPDATE Events - SET eve_PairEventRowid = + SET evePairEventRowid = (SELECT ROWID FROM Events AS EVE2 - WHERE EVE2.eve_PairEventRowid = Events.ROWID) - WHERE eve_EventType IN ('Device Down', 'Disconnected') - AND eve_PairEventRowid IS NULL + WHERE EVE2.evePairEventRowid = Events.ROWID) + WHERE eveEventType IN ('Device Down', 'Disconnected') + AND evePairEventRowid IS NULL """) mylog("debug", "[Pair Session] Pair session end") @@ -171,9 +171,9 @@ def insert_events(db): # Check device down – non-sleeping devices (immediate on first absence) mylog("debug", "[Events] - 1a - Devices down (non-sleeping)") - sql.execute(f"""INSERT OR IGNORE INTO Events (eve_MAC, eve_IP, eve_DateTime, - eve_EventType, eve_AdditionalInfo, - eve_PendingAlertEmail) + sql.execute(f"""INSERT OR IGNORE INTO Events (eveMac, eveIp, eveDateTime, + eveEventType, eveAdditionalInfo, + evePendingAlertEmail) SELECT devMac, devLastIP, '{startTime}', 'Device Down', '', 1 FROM DevicesView WHERE devAlertDown != 0 @@ -185,9 +185,9 @@ def insert_events(db): # Check device down – sleeping devices whose sleep window has expired mylog("debug", "[Events] - 1b - Devices down (sleep expired)") - sql.execute(f"""INSERT OR IGNORE INTO Events (eve_MAC, eve_IP, eve_DateTime, - eve_EventType, eve_AdditionalInfo, - eve_PendingAlertEmail) + sql.execute(f"""INSERT OR IGNORE INTO Events (eveMac, eveIp, eveDateTime, + eveEventType, eveAdditionalInfo, + evePendingAlertEmail) SELECT devMac, devLastIP, '{startTime}', 'Device Down', '', 1 FROM DevicesView WHERE devAlertDown != 0 @@ -197,33 +197,33 @@ def insert_events(db): AND NOT EXISTS (SELECT 1 FROM CurrentScan WHERE devMac = scanMac) AND NOT EXISTS (SELECT 1 FROM Events - WHERE eve_MAC = devMac - AND eve_EventType = 'Device Down' - AND eve_DateTime >= devLastConnection + WHERE eveMac = devMac + AND eveEventType = 'Device Down' + AND eveDateTime >= devLastConnection ) """) # Check new Connections or Down Reconnections mylog("debug", "[Events] - 2 - New Connections") - sql.execute(f""" INSERT OR IGNORE INTO Events (eve_MAC, eve_IP, eve_DateTime, - eve_EventType, eve_AdditionalInfo, - eve_PendingAlertEmail) + sql.execute(f""" INSERT OR IGNORE INTO Events (eveMac, eveIp, eveDateTime, + eveEventType, eveAdditionalInfo, + evePendingAlertEmail) SELECT DISTINCT c.scanMac, c.scanLastIP, '{startTime}', CASE - WHEN last_event.eve_EventType = 'Device Down' and last_event.eve_PendingAlertEmail = 0 THEN 'Down Reconnected' + WHEN last_event.eveEventType = 'Device Down' and last_event.evePendingAlertEmail = 0 THEN 'Down Reconnected' ELSE 'Connected' END, '', 1 FROM CurrentScan AS c - LEFT JOIN LatestEventsPerMAC AS last_event ON c.scanMac = last_event.eve_MAC - WHERE last_event.devPresentLastScan = 0 OR last_event.eve_MAC IS NULL + LEFT JOIN LatestEventsPerMAC AS last_event ON c.scanMac = last_event.eveMac + WHERE last_event.devPresentLastScan = 0 OR last_event.eveMac IS NULL """) # Check disconnections mylog("debug", "[Events] - 3 - Disconnections") - sql.execute(f"""INSERT OR IGNORE INTO Events (eve_MAC, eve_IP, eve_DateTime, - eve_EventType, eve_AdditionalInfo, - eve_PendingAlertEmail) + sql.execute(f"""INSERT OR IGNORE INTO Events (eveMac, eveIp, eveDateTime, + eveEventType, eveAdditionalInfo, + evePendingAlertEmail) SELECT devMac, devLastIP, '{startTime}', 'Disconnected', '', devAlertEvents FROM Devices @@ -235,9 +235,9 @@ def insert_events(db): # Check IP Changed mylog("debug", "[Events] - 4 - IP Changes") - sql.execute(f"""INSERT OR IGNORE INTO Events (eve_MAC, eve_IP, eve_DateTime, - eve_EventType, eve_AdditionalInfo, - eve_PendingAlertEmail) + sql.execute(f"""INSERT OR IGNORE INTO Events (eveMac, eveIp, eveDateTime, + eveEventType, eveAdditionalInfo, + evePendingAlertEmail) SELECT scanMac, scanLastIP, '{startTime}', 'IP Changed', 'Previous IP: '|| devLastIP, devAlertEvents FROM Devices, CurrentScan @@ -279,7 +279,7 @@ def insertOnlineHistory(db): # Prepare the insert query using parameterized inputs insert_query = """ - INSERT INTO Online_History (Scan_Date, Online_Devices, Down_Devices, All_Devices, Archived_Devices, Offline_Devices) + INSERT INTO Online_History (scanDate, onlineDevices, downDevices, allDevices, archivedDevices, offlineDevices) VALUES (?, ?, ?, ?, ?, ?) """ diff --git a/server/utils/plugin_utils.py b/server/utils/plugin_utils.py index 8f28932f..0b50454e 100755 --- a/server/utils/plugin_utils.py +++ b/server/utils/plugin_utils.py @@ -245,7 +245,7 @@ def handle_empty(value): # ------------------------------------------------------------------------------- # Get and return a plugin object based on key-value pairs -# keyValues example: getPluginObject({"Plugin":"MQTT", "Watched_Value4":"someValue"}) +# keyValues example: getPluginObject({"plugin":"MQTT", "watchedValue4":"someValue"}) def getPluginObject(keyValues): plugins_objects = apiPath + "table_plugins_objects.json" diff --git a/server/workflows/actions.py b/server/workflows/actions.py index da90aced..429da0f5 100755 --- a/server/workflows/actions.py +++ b/server/workflows/actions.py @@ -40,10 +40,10 @@ class UpdateFieldAction(Action): processed = False # currently unused - if isinstance(obj, dict) and "ObjectGUID" in obj: + if isinstance(obj, dict) and "objectGuid" in obj: mylog("debug", f"[WF] Updating Object '{obj}' ") plugin_instance = PluginObjectInstance() - plugin_instance.updateField(obj["ObjectGUID"], self.field, self.value) + plugin_instance.updateField(obj["objectGuid"], self.field, self.value) processed = True elif isinstance(obj, dict) and "devGUID" in obj: @@ -77,10 +77,10 @@ class DeleteObjectAction(Action): processed = False # currently unused - if isinstance(obj, dict) and "ObjectGUID" in obj: + if isinstance(obj, dict) and "objectGuid" in obj: mylog("debug", f"[WF] Updating Object '{obj}' ") plugin_instance = PluginObjectInstance() - plugin_instance.delete(obj["ObjectGUID"]) + plugin_instance.delete(obj["objectGuid"]) processed = True elif isinstance(obj, dict) and "devGUID" in obj: diff --git a/server/workflows/app_events.py b/server/workflows/app_events.py index 6396e30a..236b1663 100755 --- a/server/workflows/app_events.py +++ b/server/workflows/app_events.py @@ -23,29 +23,29 @@ class AppEvent_obj: self.object_mapping = { "Devices": { "fields": { - "ObjectGUID": "NEW.devGUID", - "ObjectPrimaryID": "NEW.devMac", - "ObjectSecondaryID": "NEW.devLastIP", - "ObjectForeignKey": "NEW.devGUID", - "ObjectStatus": "CASE WHEN NEW.devPresentLastScan = 1 THEN 'online' ELSE 'offline' END", - "ObjectStatusColumn": "'devPresentLastScan'", - "ObjectIsNew": "NEW.devIsNew", - "ObjectIsArchived": "NEW.devIsArchived", - "ObjectPlugin": "'DEVICES'", + "objectGuid": "NEW.devGUID", + "objectPrimaryId": "NEW.devMac", + "objectSecondaryId": "NEW.devLastIP", + "objectForeignKey": "NEW.devGUID", + "objectStatus": "CASE WHEN NEW.devPresentLastScan = 1 THEN 'online' ELSE 'offline' END", + "objectStatusColumn": "'devPresentLastScan'", + "objectIsNew": "NEW.devIsNew", + "objectIsArchived": "NEW.devIsArchived", + "objectPlugin": "'DEVICES'", } } # , # "Plugins_Objects": { # "fields": { - # "ObjectGUID": "NEW.ObjectGUID", - # "ObjectPrimaryID": "NEW.Plugin", - # "ObjectSecondaryID": "NEW.Object_PrimaryID", - # "ObjectForeignKey": "NEW.ForeignKey", - # "ObjectStatus": "NEW.Status", - # "ObjectStatusColumn": "'Status'", - # "ObjectIsNew": "CASE WHEN NEW.Status = 'new' THEN 1 ELSE 0 END", - # "ObjectIsArchived": "0", # Default value - # "ObjectPlugin": "NEW.Plugin" + # "objectGuid": "NEW.objectGuid", + # "objectPrimaryId": "NEW.plugin", + # "objectSecondaryId": "NEW.objectPrimaryId", + # "objectForeignKey": "NEW.foreignKey", + # "objectStatus": "NEW.status", + # "objectStatusColumn": "'status'", + # "objectIsNew": "CASE WHEN NEW.status = 'new' THEN 1 ELSE 0 END", + # "objectIsArchived": "0", # Default value + # "objectPlugin": "NEW.plugin" # } # } } @@ -79,26 +79,26 @@ class AppEvent_obj: """Creates the AppEvents table if it doesn't exist.""" self.db.sql.execute(""" CREATE TABLE IF NOT EXISTS "AppEvents" ( - "Index" INTEGER PRIMARY KEY AUTOINCREMENT, - "GUID" TEXT UNIQUE, - "AppEventProcessed" BOOLEAN, - "DateTimeCreated" TEXT, - "ObjectType" TEXT, - "ObjectGUID" TEXT, - "ObjectPlugin" TEXT, - "ObjectPrimaryID" TEXT, - "ObjectSecondaryID" TEXT, - "ObjectForeignKey" TEXT, - "ObjectIndex" TEXT, - "ObjectIsNew" BOOLEAN, - "ObjectIsArchived" BOOLEAN, - "ObjectStatusColumn" TEXT, - "ObjectStatus" TEXT, - "AppEventType" TEXT, - "Helper1" TEXT, - "Helper2" TEXT, - "Helper3" TEXT, - "Extra" TEXT + "index" INTEGER PRIMARY KEY AUTOINCREMENT, + "guid" TEXT UNIQUE, + "appEventProcessed" BOOLEAN, + "dateTimeCreated" TEXT, + "objectType" TEXT, + "objectGuid" TEXT, + "objectPlugin" TEXT, + "objectPrimaryId" TEXT, + "objectSecondaryId" TEXT, + "objectForeignKey" TEXT, + "objectIndex" TEXT, + "objectIsNew" BOOLEAN, + "objectIsArchived" BOOLEAN, + "objectStatusColumn" TEXT, + "objectStatus" TEXT, + "appEventType" TEXT, + "helper1" TEXT, + "helper2" TEXT, + "helper3" TEXT, + "extra" TEXT ); """) @@ -111,43 +111,43 @@ class AppEvent_obj: AFTER {event.upper()} ON "{table_name}" WHEN NOT EXISTS ( SELECT 1 FROM AppEvents - WHERE AppEventProcessed = 0 - AND ObjectType = '{table_name}' - AND ObjectGUID = {manage_prefix(config["fields"]["ObjectGUID"], event)} - AND ObjectStatus = {manage_prefix(config["fields"]["ObjectStatus"], event)} - AND AppEventType = '{event.lower()}' + WHERE appEventProcessed = 0 + AND objectType = '{table_name}' + AND objectGuid = {manage_prefix(config["fields"]["objectGuid"], event)} + AND objectStatus = {manage_prefix(config["fields"]["objectStatus"], event)} + AND appEventType = '{event.lower()}' ) BEGIN INSERT INTO "AppEvents" ( - "GUID", - "DateTimeCreated", - "AppEventProcessed", - "ObjectType", - "ObjectGUID", - "ObjectPrimaryID", - "ObjectSecondaryID", - "ObjectStatus", - "ObjectStatusColumn", - "ObjectIsNew", - "ObjectIsArchived", - "ObjectForeignKey", - "ObjectPlugin", - "AppEventType" + "guid", + "dateTimeCreated", + "appEventProcessed", + "objectType", + "objectGuid", + "objectPrimaryId", + "objectSecondaryId", + "objectStatus", + "objectStatusColumn", + "objectIsNew", + "objectIsArchived", + "objectForeignKey", + "objectPlugin", + "appEventType" ) VALUES ( {sql_generateGuid}, DATETIME('now'), FALSE, '{table_name}', - {manage_prefix(config["fields"]["ObjectGUID"], event)}, -- ObjectGUID - {manage_prefix(config["fields"]["ObjectPrimaryID"], event)}, -- ObjectPrimaryID - {manage_prefix(config["fields"]["ObjectSecondaryID"], event)}, -- ObjectSecondaryID - {manage_prefix(config["fields"]["ObjectStatus"], event)}, -- ObjectStatus - {manage_prefix(config["fields"]["ObjectStatusColumn"], event)}, -- ObjectStatusColumn - {manage_prefix(config["fields"]["ObjectIsNew"], event)}, -- ObjectIsNew - {manage_prefix(config["fields"]["ObjectIsArchived"], event)}, -- ObjectIsArchived - {manage_prefix(config["fields"]["ObjectForeignKey"], event)}, -- ObjectForeignKey - {manage_prefix(config["fields"]["ObjectPlugin"], event)}, -- ObjectForeignKey + {manage_prefix(config["fields"]["objectGuid"], event)}, -- objectGuid + {manage_prefix(config["fields"]["objectPrimaryId"], event)}, -- objectPrimaryId + {manage_prefix(config["fields"]["objectSecondaryId"], event)}, -- objectSecondaryId + {manage_prefix(config["fields"]["objectStatus"], event)}, -- objectStatus + {manage_prefix(config["fields"]["objectStatusColumn"], event)}, -- objectStatusColumn + {manage_prefix(config["fields"]["objectIsNew"], event)}, -- objectIsNew + {manage_prefix(config["fields"]["objectIsArchived"], event)}, -- objectIsArchived + {manage_prefix(config["fields"]["objectForeignKey"], event)}, -- objectForeignKey + {manage_prefix(config["fields"]["objectPlugin"], event)}, -- objectPlugin '{event.lower()}' ); END; diff --git a/server/workflows/manager.py b/server/workflows/manager.py index 52ede363..77ffa71c 100755 --- a/server/workflows/manager.py +++ b/server/workflows/manager.py @@ -33,8 +33,8 @@ class WorkflowManager: """Get new unprocessed events from the AppEvents table.""" result = self.db.sql.execute(""" SELECT * FROM AppEvents - WHERE AppEventProcessed = 0 - ORDER BY DateTimeCreated ASC + WHERE appEventProcessed = 0 + ORDER BY dateTimeCreated ASC """).fetchall() mylog("none", [f"[WF] get_new_app_events - new events count: {len(result)}"]) @@ -44,7 +44,7 @@ class WorkflowManager: def process_event(self, event): """Process the events. Check if events match a workflow trigger""" - evGuid = event["GUID"] + evGuid = event["guid"] mylog("verbose", [f"[WF] Processing event with GUID {evGuid}"]) @@ -67,10 +67,10 @@ class WorkflowManager: self.db.sql.execute( """ UPDATE AppEvents - SET AppEventProcessed = 1 - WHERE "Index" = ? + SET appEventProcessed = 1 + WHERE "index" = ? """, - (event["Index"],), + (event["index"],), ) # Pass the event's unique identifier self.db.commitDB() diff --git a/server/workflows/triggers.py b/server/workflows/triggers.py index 33e7ab2b..ed8ec4b9 100755 --- a/server/workflows/triggers.py +++ b/server/workflows/triggers.py @@ -21,7 +21,7 @@ class Trigger: self.event_type = triggerJson["event_type"] self.event = event # Store the triggered event context, if provided self.triggered = ( - self.object_type == event["ObjectType"] and self.event_type == event["AppEventType"] + self.object_type == event["objectType"] and self.event_type == event["appEventType"] ) mylog("debug", f"""[WF] self.triggered '{self.triggered}' for event '{get_array_from_sql_rows(event)} and trigger {json.dumps(triggerJson)}' """) @@ -33,7 +33,7 @@ class Trigger: if db_table == "Devices": refField = "devGUID" elif db_table == "Plugins_Objects": - refField = "ObjectGUID" + refField = "objectGuid" else: m = f"[WF] Unsupported object_type: {self.object_type}" mylog("none", [m]) @@ -42,7 +42,7 @@ class Trigger: query = f""" SELECT * FROM {db_table} - WHERE {refField} = '{event["ObjectGUID"]}' + WHERE {refField} = '{event["objectGuid"]}' """ mylog("debug", [query]) diff --git a/test/api_endpoints/test_events_endpoints.py b/test/api_endpoints/test_events_endpoints.py index 2c5978f8..8d97efee 100644 --- a/test/api_endpoints/test_events_endpoints.py +++ b/test/api_endpoints/test_events_endpoints.py @@ -61,7 +61,7 @@ def test_create_event(client, api_token, test_mac): resp = list_events(client, api_token, test_mac) assert resp.status_code == 200 events = resp.get_json().get("events", []) - assert any(ev.get("eve_MAC") == test_mac for ev in events) + assert any(ev.get("eveMac") == test_mac for ev in events) def test_delete_events_for_mac(client, api_token, test_mac): @@ -73,7 +73,7 @@ def test_delete_events_for_mac(client, api_token, test_mac): resp = list_events(client, api_token, test_mac) assert resp.status_code == 200 events = resp.json.get("events", []) - assert any(ev["eve_MAC"] == test_mac for ev in events) + assert any(ev["eveMac"] == test_mac for ev in events) # delete resp = client.delete(f"/events/{test_mac}", headers=auth_headers(api_token)) @@ -143,10 +143,10 @@ def test_delete_events_dynamic_days(client, api_token, test_mac): thirty_days_ago = timeNowUTC(as_string=False) - timedelta(days=30) initial_younger_count = 0 for ev in initial_events: - if ev.get("eve_MAC") == test_mac and ev.get("eve_DateTime"): + if ev.get("eveMac") == test_mac and ev.get("eveDateTime"): try: # Parse event datetime (handle ISO format) - ev_time_str = ev["eve_DateTime"] + ev_time_str = ev["eveDateTime"] # Try parsing with timezone info try: ev_time = datetime.fromisoformat(ev_time_str.replace("Z", "+00:00")) @@ -176,6 +176,6 @@ def test_delete_events_dynamic_days(client, api_token, test_mac): # confirm only recent events remain (pre-existing younger + newly created 5-day-old) resp = list_events(client, api_token, test_mac) events = resp.get_json().get("events", []) - mac_events = [ev for ev in events if ev.get("eve_MAC") == test_mac] + mac_events = [ev for ev in events if ev.get("eveMac") == test_mac] expected_remaining = initial_younger_count + 1 # 1 for the 5-day-old event we created assert len(mac_events) == expected_remaining diff --git a/test/api_endpoints/test_mcp_tools_endpoints.py b/test/api_endpoints/test_mcp_tools_endpoints.py index cdf2a94a..312f4549 100644 --- a/test/api_endpoints/test_mcp_tools_endpoints.py +++ b/test/api_endpoints/test_mcp_tools_endpoints.py @@ -116,7 +116,7 @@ def test_get_open_ports_ip(mock_device_db_conn, mock_plugin_db_conn, client, api mock_execute_result = MagicMock() # Mock for PluginObjectInstance.getByField (returns port data) - mock_execute_result.fetchall.return_value = [{"Object_SecondaryID": "22", "Watched_Value2": "ssh"}, {"Object_SecondaryID": "80", "Watched_Value2": "http"}] + mock_execute_result.fetchall.return_value = [{"objectSecondaryId": "22", "watchedValue2": "ssh"}, {"objectSecondaryId": "80", "watchedValue2": "http"}] # Mock for DeviceInstance.getByIP (returns device with MAC) mock_execute_result.fetchone.return_value = {"devMac": "aa:bb:cc:dd:ee:ff"} @@ -141,7 +141,7 @@ def test_get_open_ports_mac_resolve(mock_plugin_db_conn, client, api_token): # Mock database connection for MAC-based open ports query mock_conn = MagicMock() mock_execute_result = MagicMock() - mock_execute_result.fetchall.return_value = [{"Object_SecondaryID": "80", "Watched_Value2": "http"}] + mock_execute_result.fetchall.return_value = [{"objectSecondaryId": "80", "watchedValue2": "http"}] mock_conn.execute.return_value = mock_execute_result mock_plugin_db_conn.return_value = mock_conn @@ -189,7 +189,7 @@ def test_get_recent_alerts(mock_db_conn, client, api_token): mock_conn = MagicMock() mock_execute_result = MagicMock() now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - mock_execute_result.fetchall.return_value = [{"eve_DateTime": now, "eve_EventType": "New Device", "eve_MAC": "aa:bb:cc:dd:ee:ff"}] + mock_execute_result.fetchall.return_value = [{"eveDateTime": now, "eveEventType": "New Device", "eveMac": "aa:bb:cc:dd:ee:ff"}] mock_conn.execute.return_value = mock_execute_result mock_db_conn.return_value = mock_conn diff --git a/test/api_endpoints/test_sessions_endpoints.py b/test/api_endpoints/test_sessions_endpoints.py index 72fd47e5..50ed1fd2 100644 --- a/test/api_endpoints/test_sessions_endpoints.py +++ b/test/api_endpoints/test_sessions_endpoints.py @@ -74,7 +74,7 @@ def test_list_sessions(client, api_token, test_mac): assert resp.status_code == 200 assert resp.json.get("success") is True sessions = resp.json.get("sessions") - assert any(ses["ses_MAC"] == test_mac for ses in sessions) + assert any(ses["sesMac"] == test_mac for ses in sessions) def test_device_sessions_by_period(client, api_token, test_mac): @@ -105,7 +105,7 @@ def test_device_sessions_by_period(client, api_token, test_mac): print(test_mac) assert isinstance(sessions, list) - assert any(s["ses_MAC"] == test_mac for s in sessions) + assert any(s["sesMac"] == test_mac for s in sessions) def test_device_session_events(client, api_token, test_mac): @@ -178,7 +178,7 @@ def test_delete_session(client, api_token, test_mac): # Confirm deletion resp = client.get(f"/sessions/list?mac={test_mac}", headers=auth_headers(api_token)) sessions = resp.json.get("sessions") - assert not any(ses["ses_MAC"] == test_mac for ses in sessions) + assert not any(ses["sesMac"] == test_mac for ses in sessions) def test_get_sessions_calendar(client, api_token, test_mac): diff --git a/test/backend/sql_safe_builder.py b/test/backend/sql_safe_builder.py index a1bbc604..80690f5d 100644 --- a/test/backend/sql_safe_builder.py +++ b/test/backend/sql_safe_builder.py @@ -20,10 +20,10 @@ class SafeConditionBuilder: # Whitelist of allowed column names for filtering ALLOWED_COLUMNS = { - "eve_MAC", - "eve_DateTime", - "eve_IP", - "eve_EventType", + "eveMac", + "eveDateTime", + "eveIp", + "eveEventType", "devName", "devComments", "devLastIP", @@ -34,15 +34,15 @@ class SafeConditionBuilder: "devPresentLastScan", "devFavorite", "devIsNew", - "Plugin", - "Object_PrimaryId", - "Object_SecondaryId", - "DateTimeChanged", - "Watched_Value1", - "Watched_Value2", - "Watched_Value3", - "Watched_Value4", - "Status", + "plugin", + "objectPrimaryId", + "objectSecondaryId", + "dateTimeChanged", + "watchedValue1", + "watchedValue2", + "watchedValue3", + "watchedValue4", + "status", } # Whitelist of allowed comparison operators @@ -403,7 +403,7 @@ class SafeConditionBuilder: This method handles basic patterns like: - devName = 'value' (with optional AND/OR prefix) - devComments LIKE '%value%' - - eve_EventType IN ('type1', 'type2') + - eveEventType IN ('type1', 'type2') Args: condition: Single condition string to parse @@ -633,7 +633,7 @@ class SafeConditionBuilder: self.parameters[param_name] = event_type param_names.append(f":{param_name}") - sql_snippet = f"AND eve_EventType IN ({', '.join(param_names)})" + sql_snippet = f"AND eveEventType IN ({', '.join(param_names)})" return sql_snippet, self.parameters def get_safe_condition_legacy( diff --git a/test/backend/test_compound_conditions.py b/test/backend/test_compound_conditions.py index 06367b3a..bde2ca10 100644 --- a/test/backend/test_compound_conditions.py +++ b/test/backend/test_compound_conditions.py @@ -174,7 +174,7 @@ def test_compound_with_like_patterns(builder): def test_compound_with_inequality_operators(builder): """Test compound conditions with various inequality operators.""" - condition = "AND eve_DateTime > '2024-01-01' AND eve_DateTime < '2024-12-31'" + condition = "AND eveDateTime > '2024-01-01' AND eveDateTime < '2024-12-31'" sql, params = builder.build_safe_condition(condition) diff --git a/test/backend/test_notification_templates.py b/test/backend/test_notification_templates.py index 0653493d..f4448fd3 100644 --- a/test/backend/test_notification_templates.py +++ b/test/backend/test_notification_templates.py @@ -32,25 +32,25 @@ def _make_json(section, devices, column_names, title="Test Section"): SAMPLE_NEW_DEVICES = [ { "devName": "MyPhone", - "eve_MAC": "aa:bb:cc:dd:ee:ff", + "eveMac": "aa:bb:cc:dd:ee:ff", "devVendor": "", - "eve_IP": "192.168.1.42", - "eve_DateTime": "2025-01-15 10:30:00", - "eve_EventType": "New Device", + "eveIp": "192.168.1.42", + "eveDateTime": "2025-01-15 10:30:00", + "eveEventType": "New Device", "devComments": "", }, { "devName": "Laptop", - "eve_MAC": "11:22:33:44:55:66", + "eveMac": "11:22:33:44:55:66", "devVendor": "Dell", - "eve_IP": "192.168.1.99", - "eve_DateTime": "2025-01-15 11:00:00", - "eve_EventType": "New Device", + "eveIp": "192.168.1.99", + "eveDateTime": "2025-01-15 11:00:00", + "eveEventType": "New Device", "devComments": "Office", }, ] -NEW_DEVICE_COLUMNS = ["devName", "eve_MAC", "devVendor", "eve_IP", "eve_DateTime", "eve_EventType", "devComments"] +NEW_DEVICE_COLUMNS = ["devName", "eveMac", "devVendor", "eveIp", "eveDateTime", "eveEventType", "devComments"] class TestConstructNotificationsTemplates(unittest.TestCase): @@ -100,7 +100,7 @@ class TestConstructNotificationsTemplates(unittest.TestCase): self.assertIn("---------", text) # Legacy format: each header appears as "Header: \tValue" - self.assertIn("eve_MAC:", text) + self.assertIn("eveMac:", text) self.assertIn("aa:bb:cc:dd:ee:ff", text) self.assertIn("devName:", text) self.assertIn("MyPhone", text) @@ -117,7 +117,7 @@ class TestConstructNotificationsTemplates(unittest.TestCase): mock_setting.side_effect = self._setting_factory({ "NTFPRCS_TEXT_SECTION_HEADERS": True, - "NTFPRCS_TEXT_TEMPLATE_new_devices": "{devName} ({eve_MAC}) - {eve_IP}", + "NTFPRCS_TEXT_TEMPLATE_new_devices": "{devName} ({eveMac}) - {eveIp}", }) json_data = _make_json( @@ -157,7 +157,7 @@ class TestConstructNotificationsTemplates(unittest.TestCase): mock_setting.side_effect = self._setting_factory({ "NTFPRCS_TEXT_SECTION_HEADERS": False, - "NTFPRCS_TEXT_TEMPLATE_new_devices": "{devName} ({eve_MAC})", + "NTFPRCS_TEXT_TEMPLATE_new_devices": "{devName} ({eveMac})", }) json_data = _make_json( @@ -198,7 +198,7 @@ class TestConstructNotificationsTemplates(unittest.TestCase): mock_setting.side_effect = self._setting_factory({ "NTFPRCS_TEXT_SECTION_HEADERS": True, - "NTFPRCS_TEXT_TEMPLATE_new_devices": "{devName} ({BadField}) - {eve_IP}", + "NTFPRCS_TEXT_TEMPLATE_new_devices": "{devName} ({BadField}) - {eveIp}", }) json_data = _make_json( @@ -217,21 +217,21 @@ class TestConstructNotificationsTemplates(unittest.TestCase): mock_setting.side_effect = self._setting_factory({ "NTFPRCS_TEXT_SECTION_HEADERS": True, - "NTFPRCS_TEXT_TEMPLATE_down_devices": "{devName} ({eve_MAC}) down since {eve_DateTime}", + "NTFPRCS_TEXT_TEMPLATE_down_devices": "{devName} ({eveMac}) down since {eveDateTime}", }) down_devices = [ { "devName": "Router", - "eve_MAC": "ff:ee:dd:cc:bb:aa", + "eveMac": "ff:ee:dd:cc:bb:aa", "devVendor": "Cisco", - "eve_IP": "10.0.0.1", - "eve_DateTime": "2025-01-15 08:00:00", - "eve_EventType": "Device Down", + "eveIp": "10.0.0.1", + "eveDateTime": "2025-01-15 08:00:00", + "eveEventType": "Device Down", "devComments": "", } ] - columns = ["devName", "eve_MAC", "devVendor", "eve_IP", "eve_DateTime", "eve_EventType", "devComments"] + columns = ["devName", "eveMac", "devVendor", "eveIp", "eveDateTime", "eveEventType", "devComments"] json_data = _make_json("down_devices", down_devices, columns, "🔴 Down devices") _, text = construct_notifications(json_data, "down_devices") diff --git a/test/backend/test_safe_builder_unit.py b/test/backend/test_safe_builder_unit.py index 38b7c2e2..f2775129 100644 --- a/test/backend/test_safe_builder_unit.py +++ b/test/backend/test_safe_builder_unit.py @@ -15,7 +15,7 @@ sys.modules['logger'] = Mock() class SafeConditionBuilderForTesting: """Minimal SafeConditionBuilder implementation for tests.""" - ALLOWED_COLUMNS = {'devName', 'eve_MAC', 'eve_EventType'} + ALLOWED_COLUMNS = {'devName', 'eveMac', 'eveEventType'} ALLOWED_OPERATORS = {'=', '!=', '<', '>', '<=', '>=', 'LIKE', 'NOT LIKE'} ALLOWED_LOGICAL_OPERATORS = {'AND', 'OR'} diff --git a/test/backend/test_sql_injection_prevention.py b/test/backend/test_sql_injection_prevention.py index 496003d3..6355c8ac 100644 --- a/test/backend/test_sql_injection_prevention.py +++ b/test/backend/test_sql_injection_prevention.py @@ -174,13 +174,13 @@ def test_null_byte_injection(builder): def test_build_condition_with_allowed_values(builder): """Test building condition with specific allowed values.""" conditions = [ - {"column": "eve_EventType", "operator": "=", "value": "Connected"}, + {"column": "eveEventType", "operator": "=", "value": "Connected"}, {"column": "devName", "operator": "LIKE", "value": "%test%"} ] condition, params = builder.build_condition(conditions, "AND") # Should create valid parameterized condition - assert "eve_EventType = :" in condition + assert "eveEventType = :" in condition assert "devName LIKE :" in condition assert len(params) == 2 diff --git a/test/backend/test_sql_security.py b/test/backend/test_sql_security.py index d3a3dc68..101ac3ff 100644 --- a/test/backend/test_sql_security.py +++ b/test/backend/test_sql_security.py @@ -58,9 +58,9 @@ class TestSafeConditionBuilder(unittest.TestCase): def test_validate_column_name(self): """Test column name validation against whitelist.""" # Valid columns - self.assertTrue(self.builder._validate_column_name('eve_MAC')) + self.assertTrue(self.builder._validate_column_name('eveMac')) self.assertTrue(self.builder._validate_column_name('devName')) - self.assertTrue(self.builder._validate_column_name('eve_EventType')) + self.assertTrue(self.builder._validate_column_name('eveEventType')) # Invalid columns self.assertFalse(self.builder._validate_column_name('malicious_column')) @@ -103,9 +103,9 @@ class TestSafeConditionBuilder(unittest.TestCase): def test_build_in_condition_valid(self): """Test building valid IN conditions.""" - sql, params = self.builder._build_in_condition('AND', 'eve_EventType', 'IN', "'Connected', 'Disconnected'") + sql, params = self.builder._build_in_condition('AND', 'eveEventType', 'IN', "'Connected', 'Disconnected'") - self.assertIn('AND eve_EventType IN', sql) + self.assertIn('AND eveEventType IN', sql) self.assertEqual(len(params), 2) self.assertIn('Connected', params.values()) self.assertIn('Disconnected', params.values()) @@ -162,7 +162,7 @@ class TestSafeConditionBuilder(unittest.TestCase): event_types = ['Connected', 'Disconnected'] sql, params = self.builder.build_event_type_filter(event_types) - self.assertIn('AND eve_EventType IN', sql) + self.assertIn('AND eveEventType IN', sql) self.assertEqual(len(params), 2) self.assertIn('Connected', params.values()) self.assertIn('Disconnected', params.values()) @@ -354,9 +354,9 @@ class TestSecurityBenchmarks(unittest.TestCase): """Test coverage of condition patterns.""" patterns_tested = [ "AND devName = 'value'", - "OR eve_EventType LIKE '%test%'", + "OR eveEventType LIKE '%test%'", "AND devComments IS NULL", - "AND eve_EventType IN ('Connected', 'Disconnected')", + "AND eveEventType IN ('Connected', 'Disconnected')", ] for pattern in patterns_tested: diff --git a/test/db/test_camelcase_migration.py b/test/db/test_camelcase_migration.py new file mode 100644 index 00000000..9e0a4ccb --- /dev/null +++ b/test/db/test_camelcase_migration.py @@ -0,0 +1,307 @@ +""" +Unit tests for migrate_to_camelcase() in db_upgrade. + +Covers: +- Already-migrated schema (eveMac present) → skip, return True +- Unrecognised schema (neither eveMac nor eve_MAC) → skip, return True +- Legacy Events columns renamed to camelCase equivalents +- Legacy Sessions columns renamed to camelCase equivalents +- Legacy Online_History columns renamed to camelCase equivalents +- Legacy Plugins_Objects columns renamed to camelCase equivalents +- Legacy Plugins_Language_Strings columns renamed to camelCase equivalents +- Missing tables are silently skipped without error +- Existing row data is preserved through the column rename +- Views referencing old column names are dropped before ALTER TABLE runs +- Migration is idempotent (second call detects eveMac and returns early) +""" + +import sys +import os +import sqlite3 + +INSTALL_PATH = os.getenv('NETALERTX_APP', '/app') +sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"]) + +from db.db_upgrade import migrate_to_camelcase # noqa: E402 + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +def _make_cursor(): + """Return an in-memory SQLite cursor and its parent connection.""" + conn = sqlite3.connect(":memory:") + return conn.cursor(), conn + + +def _col_names(cursor, table): + """Return the set of column names for a given table.""" + cursor.execute(f'PRAGMA table_info("{table}")') + return {row[1] for row in cursor.fetchall()} + + +# --------------------------------------------------------------------------- +# Legacy DDL fixtures (pre-migration schema with old column names) +# --------------------------------------------------------------------------- + +_LEGACY_EVENTS_DDL = """ + CREATE TABLE Events ( + eve_MAC TEXT NOT NULL, + eve_IP TEXT NOT NULL, + eve_DateTime DATETIME NOT NULL, + eve_EventType TEXT NOT NULL, + eve_AdditionalInfo TEXT DEFAULT '', + eve_PendingAlertEmail INTEGER NOT NULL DEFAULT 1, + eve_PairEventRowid INTEGER + ) +""" + +_LEGACY_SESSIONS_DDL = """ + CREATE TABLE Sessions ( + ses_MAC TEXT, + ses_IP TEXT, + ses_EventTypeConnection TEXT, + ses_DateTimeConnection DATETIME, + ses_EventTypeDisconnection TEXT, + ses_DateTimeDisconnection DATETIME, + ses_StillConnected INTEGER, + ses_AdditionalInfo TEXT + ) +""" + +_LEGACY_ONLINE_HISTORY_DDL = """ + CREATE TABLE Online_History ( + "Index" INTEGER PRIMARY KEY AUTOINCREMENT, + "Scan_Date" TEXT, + "Online_Devices" INTEGER, + "Down_Devices" INTEGER, + "All_Devices" INTEGER, + "Archived_Devices" INTEGER, + "Offline_Devices" INTEGER + ) +""" + +_LEGACY_PLUGINS_OBJECTS_DDL = """ + CREATE TABLE Plugins_Objects ( + "Index" INTEGER PRIMARY KEY AUTOINCREMENT, + Plugin TEXT NOT NULL, + Object_PrimaryID TEXT NOT NULL, + Object_SecondaryID TEXT NOT NULL, + DateTimeCreated TEXT NOT NULL, + DateTimeChanged TEXT NOT NULL, + Watched_Value1 TEXT NOT NULL, + Watched_Value2 TEXT NOT NULL, + Watched_Value3 TEXT NOT NULL, + Watched_Value4 TEXT NOT NULL, + Status TEXT NOT NULL, + Extra TEXT NOT NULL, + UserData TEXT NOT NULL, + ForeignKey TEXT NOT NULL, + SyncHubNodeName TEXT, + HelpVal1 TEXT, + HelpVal2 TEXT, + HelpVal3 TEXT, + HelpVal4 TEXT, + ObjectGUID TEXT + ) +""" + +_LEGACY_PLUGINS_LANG_DDL = """ + CREATE TABLE Plugins_Language_Strings ( + "Index" INTEGER PRIMARY KEY AUTOINCREMENT, + Language_Code TEXT NOT NULL, + String_Key TEXT NOT NULL, + String_Value TEXT NOT NULL, + Extra TEXT NOT NULL + ) +""" + + +# --------------------------------------------------------------------------- +# Tests +# --------------------------------------------------------------------------- + +class TestMigrateToCamelCase: + + def test_returns_true_if_already_camelcase(self): + """DB already on camelCase schema → skip silently, return True.""" + cur, conn = _make_cursor() + cur.execute(""" + CREATE TABLE Events ( + eveMac TEXT NOT NULL, eveIp TEXT NOT NULL, + eveDateTime DATETIME NOT NULL, eveEventType TEXT NOT NULL, + eveAdditionalInfo TEXT, evePendingAlertEmail INTEGER, + evePairEventRowid INTEGER + ) + """) + conn.commit() + + result = migrate_to_camelcase(cur) + + assert result is True + assert "eveMac" in _col_names(cur, "Events") + + def test_returns_true_if_unknown_schema(self): + """Events exists but has neither eve_MAC nor eveMac → skip, return True.""" + cur, conn = _make_cursor() + cur.execute("CREATE TABLE Events (someOtherCol TEXT)") + conn.commit() + + result = migrate_to_camelcase(cur) + + assert result is True + + def test_events_legacy_columns_renamed(self): + """All legacy eve_* columns are renamed to their camelCase equivalents.""" + cur, conn = _make_cursor() + cur.execute(_LEGACY_EVENTS_DDL) + conn.commit() + + result = migrate_to_camelcase(cur) + + assert result is True + cols = _col_names(cur, "Events") + expected_new = { + "eveMac", "eveIp", "eveDateTime", "eveEventType", + "eveAdditionalInfo", "evePendingAlertEmail", "evePairEventRowid", + } + old_names = { + "eve_MAC", "eve_IP", "eve_DateTime", "eve_EventType", + "eve_AdditionalInfo", "eve_PendingAlertEmail", "eve_PairEventRowid", + } + assert expected_new.issubset(cols), f"Missing new columns: {expected_new - cols}" + assert not old_names & cols, f"Old columns still present: {old_names & cols}" + + def test_sessions_legacy_columns_renamed(self): + """All legacy ses_* columns are renamed to their camelCase equivalents.""" + cur, conn = _make_cursor() + cur.execute(_LEGACY_EVENTS_DDL) + cur.execute(_LEGACY_SESSIONS_DDL) + conn.commit() + + result = migrate_to_camelcase(cur) + + assert result is True + cols = _col_names(cur, "Sessions") + assert { + "sesMac", "sesIp", "sesEventTypeConnection", "sesDateTimeConnection", + "sesEventTypeDisconnection", "sesDateTimeDisconnection", + "sesStillConnected", "sesAdditionalInfo", + }.issubset(cols) + assert not {"ses_MAC", "ses_IP", "ses_DateTimeConnection"} & cols + + def test_online_history_legacy_columns_renamed(self): + """Quoted legacy Online_History column names are renamed to camelCase.""" + cur, conn = _make_cursor() + cur.execute(_LEGACY_EVENTS_DDL) + cur.execute(_LEGACY_ONLINE_HISTORY_DDL) + conn.commit() + + migrate_to_camelcase(cur) + + cols = _col_names(cur, "Online_History") + assert { + "scanDate", "onlineDevices", "downDevices", + "allDevices", "archivedDevices", "offlineDevices", + }.issubset(cols) + assert not {"Scan_Date", "Online_Devices", "Down_Devices"} & cols + + def test_plugins_objects_legacy_columns_renamed(self): + """All renamed Plugins_Objects columns receive their camelCase names.""" + cur, conn = _make_cursor() + cur.execute(_LEGACY_EVENTS_DDL) + cur.execute(_LEGACY_PLUGINS_OBJECTS_DDL) + conn.commit() + + migrate_to_camelcase(cur) + + cols = _col_names(cur, "Plugins_Objects") + assert { + "plugin", "objectPrimaryId", "objectSecondaryId", + "dateTimeCreated", "dateTimeChanged", + "watchedValue1", "watchedValue2", "watchedValue3", "watchedValue4", + "status", "extra", "userData", "foreignKey", "syncHubNodeName", + "helpVal1", "helpVal2", "helpVal3", "helpVal4", "objectGuid", + }.issubset(cols) + assert not { + "Object_PrimaryID", "Watched_Value1", "ObjectGUID", + "ForeignKey", "UserData", "Plugin", + } & cols + + def test_plugins_language_strings_renamed(self): + """Plugins_Language_Strings legacy column names are renamed to camelCase.""" + cur, conn = _make_cursor() + cur.execute(_LEGACY_EVENTS_DDL) + cur.execute(_LEGACY_PLUGINS_LANG_DDL) + conn.commit() + + migrate_to_camelcase(cur) + + cols = _col_names(cur, "Plugins_Language_Strings") + assert {"languageCode", "stringKey", "stringValue", "extra"}.issubset(cols) + assert not {"Language_Code", "String_Key", "String_Value"} & cols + + def test_missing_table_silently_skipped(self): + """Tables in the migration map that don't exist are skipped without error.""" + cur, conn = _make_cursor() + # Only Events (legacy) exists — all other mapped tables are absent + cur.execute(_LEGACY_EVENTS_DDL) + conn.commit() + + result = migrate_to_camelcase(cur) + + assert result is True + assert "eveMac" in _col_names(cur, "Events") + + def test_data_preserved_after_rename(self): + """Existing rows remain accessible under the new camelCase column names.""" + cur, conn = _make_cursor() + cur.execute(_LEGACY_EVENTS_DDL) + cur.execute( + "INSERT INTO Events (eve_MAC, eve_IP, eve_DateTime, eve_EventType) " + "VALUES ('aa:bb:cc:dd:ee:ff', '192.168.1.1', '2025-01-01 12:00:00', 'Connected')" + ) + conn.commit() + + migrate_to_camelcase(cur) + + cur.execute( + "SELECT eveMac, eveIp, eveEventType FROM Events WHERE eveMac = 'aa:bb:cc:dd:ee:ff'" + ) + row = cur.fetchone() + assert row is not None, "Row missing after camelCase migration" + assert row[0] == "aa:bb:cc:dd:ee:ff" + assert row[1] == "192.168.1.1" + assert row[2] == "Connected" + + def test_views_dropped_before_migration(self): + """Views referencing old column names do not block ALTER TABLE RENAME COLUMN.""" + cur, conn = _make_cursor() + cur.execute(_LEGACY_EVENTS_DDL) + # A view that references old column names would normally block the rename + cur.execute("CREATE VIEW Events_Devices AS SELECT eve_MAC, eve_IP FROM Events") + conn.commit() + + result = migrate_to_camelcase(cur) + + assert result is True + assert "eveMac" in _col_names(cur, "Events") + # View is dropped (ensure_views() is responsible for recreation separately) + cur.execute("SELECT name FROM sqlite_master WHERE type='view' AND name='Events_Devices'") + assert cur.fetchone() is None + + def test_idempotent_second_run(self): + """Running migration twice is safe — second call detects eveMac and exits early.""" + cur, conn = _make_cursor() + cur.execute(_LEGACY_EVENTS_DDL) + conn.commit() + + first = migrate_to_camelcase(cur) + second = migrate_to_camelcase(cur) + + assert first is True + assert second is True + cols = _col_names(cur, "Events") + assert "eveMac" in cols + assert "eve_MAC" not in cols diff --git a/test/db/test_db_cleanup.py b/test/db/test_db_cleanup.py index 941161b3..6a915632 100644 --- a/test/db/test_db_cleanup.py +++ b/test/db/test_db_cleanup.py @@ -25,26 +25,26 @@ def _make_db(): cur.execute(""" CREATE TABLE Events ( - eve_MAC TEXT NOT NULL, - eve_IP TEXT NOT NULL, - eve_DateTime DATETIME NOT NULL, - eve_EventType TEXT NOT NULL, - eve_AdditionalInfo TEXT DEFAULT '', - eve_PendingAlertEmail INTEGER NOT NULL DEFAULT 1, - eve_PairEventRowid INTEGER + eveMac TEXT NOT NULL, + eveIp TEXT NOT NULL, + eveDateTime DATETIME NOT NULL, + eveEventType TEXT NOT NULL, + eveAdditionalInfo TEXT DEFAULT '', + evePendingAlertEmail INTEGER NOT NULL DEFAULT 1, + evePairEventRowid INTEGER ) """) cur.execute(""" CREATE TABLE Sessions ( - ses_MAC TEXT, - ses_IP TEXT, - ses_EventTypeConnection TEXT, - ses_DateTimeConnection DATETIME, - ses_EventTypeDisconnection TEXT, - ses_DateTimeDisconnection DATETIME, - ses_StillConnected INTEGER, - ses_AdditionalInfo TEXT + sesMac TEXT, + sesIp TEXT, + sesEventTypeConnection TEXT, + sesDateTimeConnection DATETIME, + sesEventTypeDisconnection TEXT, + sesDateTimeDisconnection DATETIME, + sesStillConnected INTEGER, + sesAdditionalInfo TEXT ) """) @@ -59,13 +59,13 @@ def _seed_sessions(cur, old_count: int, recent_count: int, days: int): """ for i in range(old_count): cur.execute( - "INSERT INTO Sessions (ses_MAC, ses_DateTimeConnection) " + "INSERT INTO Sessions (sesMac, sesDateTimeConnection) " "VALUES (?, date('now', ?))", (f"AA:BB:CC:DD:EE:{i:02X}", f"-{days + 1} day"), ) for i in range(recent_count): cur.execute( - "INSERT INTO Sessions (ses_MAC, ses_DateTimeConnection) " + "INSERT INTO Sessions (sesMac, sesDateTimeConnection) " "VALUES (?, date('now'))", (f"11:22:33:44:55:{i:02X}",), ) @@ -75,7 +75,7 @@ def _run_sessions_trim(cur, days: int) -> int: """Execute the exact DELETE used by db_cleanup and return rowcount.""" cur.execute( f"DELETE FROM Sessions " - f"WHERE ses_DateTimeConnection <= date('now', '-{days} day')" + f"WHERE sesDateTimeConnection <= date('now', '-{days} day')" ) return cur.rowcount @@ -126,20 +126,20 @@ class TestSessionsTrim: cur = conn.cursor() # Row exactly AT the boundary (date = 'now' - days exactly) cur.execute( - "INSERT INTO Sessions (ses_MAC, ses_DateTimeConnection) " + "INSERT INTO Sessions (sesMac, sesDateTimeConnection) " "VALUES (?, date('now', ?))", ("AA:BB:CC:00:00:01", "-30 day"), ) # Row just inside the window cur.execute( - "INSERT INTO Sessions (ses_MAC, ses_DateTimeConnection) " + "INSERT INTO Sessions (sesMac, sesDateTimeConnection) " "VALUES (?, date('now', '-29 day'))", ("AA:BB:CC:00:00:02",), ) _run_sessions_trim(cur, days=30) - cur.execute("SELECT ses_MAC FROM Sessions") + cur.execute("SELECT sesMac FROM Sessions") remaining_macs = {row[0] for row in cur.fetchall()} # Boundary row (== threshold) is deleted; inside row survives assert "AA:BB:CC:00:00:02" in remaining_macs, "Row inside window was wrongly deleted" @@ -157,8 +157,8 @@ class TestSessionsTrim: with open(script_path) as fh: source = fh.read() - events_expr = "DELETE FROM Events WHERE eve_DateTime <= date('now', '-{str(DAYS_TO_KEEP_EVENTS)} day')" - sessions_expr = "DELETE FROM Sessions WHERE ses_DateTimeConnection <= date('now', '-{str(DAYS_TO_KEEP_EVENTS)} day')" + events_expr = "DELETE FROM Events WHERE eveDateTime <= date('now', '-{str(DAYS_TO_KEEP_EVENTS)} day')" + sessions_expr = "DELETE FROM Sessions WHERE sesDateTimeConnection <= date('now', '-{str(DAYS_TO_KEEP_EVENTS)} day')" assert events_expr in source, "Events DELETE expression changed unexpectedly" assert sessions_expr in source, "Sessions DELETE is not aligned with Events DELETE" @@ -181,7 +181,7 @@ class TestAnalyze: # Seed some rows so ANALYZE has something to measure for i in range(20): cur.execute( - "INSERT INTO Events (eve_MAC, eve_IP, eve_DateTime, eve_EventType) " + "INSERT INTO Events (eveMac, eveIp, eveDateTime, eveEventType) " "VALUES (?, '1.2.3.4', date('now'), 'Connected')", (f"AA:BB:CC:DD:EE:{i:02X}",), ) @@ -238,7 +238,7 @@ class TestPragmaOptimize: for i in range(50): cur.execute( - "INSERT INTO Sessions (ses_MAC, ses_DateTimeConnection) " + "INSERT INTO Sessions (sesMac, sesDateTimeConnection) " "VALUES (?, date('now', '-60 day'))", (f"AA:BB:CC:DD:EE:{i:02X}",), ) @@ -247,7 +247,7 @@ class TestPragmaOptimize: # Mirror the tail sequence from cleanup_database. # WAL checkpoints are omitted: they require no open transaction and are # not supported on :memory: databases (SQLite raises OperationalError). - cur.execute("DELETE FROM Sessions WHERE ses_DateTimeConnection <= date('now', '-30 day')") + cur.execute("DELETE FROM Sessions WHERE sesDateTimeConnection <= date('now', '-30 day')") conn.commit() cur.execute("ANALYZE;") conn.execute("VACUUM;") diff --git a/test/db_test_helpers.py b/test/db_test_helpers.py index f27ee559..27bd9b7e 100644 --- a/test/db_test_helpers.py +++ b/test/db_test_helpers.py @@ -76,16 +76,16 @@ CREATE_DEVICES = """ ) """ -# Includes eve_PairEventRowid — required by insert_events(). +# Includes evePairEventRowid — required by insert_events(). CREATE_EVENTS = """ CREATE TABLE IF NOT EXISTS Events ( - eve_MAC TEXT, - eve_IP TEXT, - eve_DateTime TEXT, - eve_EventType TEXT, - eve_AdditionalInfo TEXT, - eve_PendingAlertEmail INTEGER, - eve_PairEventRowid INTEGER + eveMac TEXT, + eveIp TEXT, + eveDateTime TEXT, + eveEventType TEXT, + eveAdditionalInfo TEXT, + evePendingAlertEmail INTEGER, + evePairEventRowid INTEGER ) """ @@ -327,8 +327,8 @@ def sync_insert_devices( def down_event_macs(cur) -> set: """Return the set of MACs that have a 'Device Down' event row (lowercased).""" - cur.execute("SELECT eve_MAC FROM Events WHERE eve_EventType = 'Device Down'") - return {r["eve_MAC"].lower() for r in cur.fetchall()} + cur.execute("SELECT eveMac FROM Events WHERE eveEventType = 'Device Down'") + return {r["eveMac"].lower() for r in cur.fetchall()} # --------------------------------------------------------------------------- diff --git a/test/integration/integration_test.py b/test/integration/integration_test.py index b4e28f06..818add9d 100755 --- a/test/integration/integration_test.py +++ b/test/integration/integration_test.py @@ -39,15 +39,15 @@ def test_db(test_db_path): # Minimal schema for integration testing cur.execute(''' CREATE TABLE IF NOT EXISTS Events_Devices ( - eve_MAC TEXT, - eve_DateTime TEXT, + eveMac TEXT, + eveDateTime TEXT, devLastIP TEXT, - eve_IP TEXT, - eve_EventType TEXT, + eveIp TEXT, + eveEventType TEXT, devName TEXT, devVendor TEXT, devComments TEXT, - eve_PendingAlertEmail INTEGER + evePendingAlertEmail INTEGER ) ''') @@ -63,24 +63,24 @@ def test_db(test_db_path): cur.execute(''' CREATE TABLE IF NOT EXISTS Events ( - eve_MAC TEXT, - eve_DateTime TEXT, - eve_EventType TEXT, - eve_PendingAlertEmail INTEGER + eveMac TEXT, + eveDateTime TEXT, + eveEventType TEXT, + evePendingAlertEmail INTEGER ) ''') cur.execute(''' CREATE TABLE IF NOT EXISTS Plugins_Events ( - Plugin TEXT, - Object_PrimaryId TEXT, - Object_SecondaryId TEXT, - DateTimeChanged TEXT, - Watched_Value1 TEXT, - Watched_Value2 TEXT, - Watched_Value3 TEXT, - Watched_Value4 TEXT, - Status TEXT + plugin TEXT, + objectPrimaryId TEXT, + objectSecondaryId TEXT, + dateTimeChanged TEXT, + watchedValue1 TEXT, + watchedValue2 TEXT, + watchedValue3 TEXT, + watchedValue4 TEXT, + "status" TEXT ) ''') @@ -91,7 +91,7 @@ def test_db(test_db_path): ('77:88:99:aa:bb:cc', '2024-01-01 12:02:00', '192.168.1.102', '192.168.1.102', 'Disconnected', 'Test Device 3', 'Cisco', 'Third Comment', 1), ] cur.executemany(''' - INSERT INTO Events_Devices (eve_MAC, eve_DateTime, devLastIP, eve_IP, eve_EventType, devName, devVendor, devComments, eve_PendingAlertEmail) + INSERT INTO Events_Devices (eveMac, eveDateTime, devLastIP, eveIp, eveEventType, devName, devVendor, devComments, evePendingAlertEmail) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ''', test_data) @@ -117,7 +117,7 @@ def test_fresh_install_compatibility(builder): def test_existing_db_compatibility(): mock_db = Mock() mock_result = Mock() - mock_result.columnNames = ['devName', 'eve_MAC', 'devVendor', 'eve_IP', 'eve_DateTime', 'eve_EventType', 'devComments'] + mock_result.columnNames = ['devName', 'eveMac', 'devVendor', 'eveIp', 'eveDateTime', 'eveEventType', 'devComments'] mock_result.json = {'data': []} mock_db.get_table_as_json.return_value = mock_result @@ -145,9 +145,9 @@ def test_notification_system_integration(builder): assert "devName = :" in condition assert 'EmailTestDevice' in params.values() - apprise_condition = "AND eve_EventType = 'Connected'" + apprise_condition = "AND eveEventType = 'Connected'" condition, params = builder.get_safe_condition_legacy(apprise_condition) - assert "eve_EventType = :" in condition + assert "eveEventType = :" in condition assert 'Connected' in params.values() webhook_condition = "AND devComments LIKE '%webhook%'" @@ -155,9 +155,9 @@ def test_notification_system_integration(builder): assert "devComments LIKE :" in condition assert '%webhook%' in params.values() - mqtt_condition = "AND eve_MAC = 'aa:bb:cc:dd:ee:ff'" + mqtt_condition = "AND eveMac = 'aa:bb:cc:dd:ee:ff'" condition, params = builder.get_safe_condition_legacy(mqtt_condition) - assert "eve_MAC = :" in condition + assert "eveMac = :" in condition assert 'aa:bb:cc:dd:ee:ff' in params.values() @@ -165,7 +165,7 @@ def test_settings_persistence(builder): test_settings = [ "AND devName = 'Persistent Device'", "AND devComments = {s-quote}Legacy Quote{s-quote}", - "AND eve_EventType IN ('Connected', 'Disconnected')", + "AND eveEventType IN ('Connected', 'Disconnected')", "AND devLastIP = '192.168.1.1'", "" ] @@ -190,9 +190,9 @@ def test_device_operations(builder): def test_plugin_functionality(builder): plugin_conditions = [ - "AND Plugin = 'TestPlugin'", - "AND Object_PrimaryId = 'primary123'", - "AND Status = 'Active'" + "AND plugin = 'TestPlugin'", + "AND objectPrimaryId = 'primary123'", + "AND status = 'Active'" ] for cond in plugin_conditions: safe_condition, params = builder.get_safe_condition_legacy(cond) diff --git a/test/scan/test_down_sleep_events.py b/test/scan/test_down_sleep_events.py index 295f06b6..be43a8b7 100644 --- a/test/scan/test_down_sleep_events.py +++ b/test/scan/test_down_sleep_events.py @@ -258,8 +258,8 @@ class TestInsertEventsSleepSuppression: can_sleep=1, last_connection=last_conn) # Simulate: a Device Down event already exists for this absence cur.execute( - "INSERT INTO Events (eve_MAC, eve_IP, eve_DateTime, eve_EventType, " - "eve_AdditionalInfo, eve_PendingAlertEmail) " + "INSERT INTO Events (eveMac, eveIp, eveDateTime, eveEventType, " + "eveAdditionalInfo, evePendingAlertEmail) " "VALUES (?, '192.168.1.1', ?, 'Device Down', '', 1)", ("bb:00:00:00:00:04", _minutes_ago(15)), ) @@ -269,7 +269,7 @@ class TestInsertEventsSleepSuppression: cur.execute( "SELECT COUNT(*) as cnt FROM Events " - "WHERE eve_MAC = 'bb:00:00:00:00:04' AND eve_EventType = 'Device Down'" + "WHERE eveMac = 'bb:00:00:00:00:04' AND eveEventType = 'Device Down'" ) count = cur.fetchone()["cnt"] assert count == 1, ( diff --git a/test/scan/test_field_lock_scan_integration.py b/test/scan/test_field_lock_scan_integration.py index 01cbdbda..8036ad22 100644 --- a/test/scan/test_field_lock_scan_integration.py +++ b/test/scan/test_field_lock_scan_integration.py @@ -117,12 +117,12 @@ def scan_db_for_new_devices(): cur.execute( """ CREATE TABLE Events ( - eve_MAC TEXT, - eve_IP TEXT, - eve_DateTime TEXT, - eve_EventType TEXT, - eve_AdditionalInfo TEXT, - eve_PendingAlertEmail INTEGER + eveMac TEXT, + eveIp TEXT, + eveDateTime TEXT, + eveEventType TEXT, + eveAdditionalInfo TEXT, + evePendingAlertEmail INTEGER ) """ ) @@ -130,14 +130,14 @@ def scan_db_for_new_devices(): cur.execute( """ CREATE TABLE Sessions ( - ses_MAC TEXT, - ses_IP TEXT, - ses_EventTypeConnection TEXT, - ses_DateTimeConnection TEXT, - ses_EventTypeDisconnection TEXT, - ses_DateTimeDisconnection TEXT, - ses_StillConnected INTEGER, - ses_AdditionalInfo TEXT + sesMac TEXT, + sesIp TEXT, + sesEventTypeConnection TEXT, + sesDateTimeConnection TEXT, + sesEventTypeDisconnection TEXT, + sesDateTimeDisconnection TEXT, + sesStillConnected INTEGER, + sesAdditionalInfo TEXT ) """ ) From 0a7ecb5b7c2ff91fd5e3c2900b4e5359d48d26a4 Mon Sep 17 00:00:00 2001 From: "Jokob @NetAlertX" <96159884+jokob-sk@users.noreply.github.com> Date: Tue, 17 Mar 2026 09:22:25 +0000 Subject: [PATCH 004/189] Update config.json files to add 'ordeable' option and refactor cacheStrings function for consistency --- front/js/cache.js | 4 ++-- front/plugins/arp_scan/config.json | 4 ++-- front/plugins/ddns_update/config.json | 4 ++-- front/plugins/dhcp_leases/config.json | 4 ++-- front/plugins/dhcp_servers/config.json | 4 ++-- front/plugins/internet_ip/config.json | 4 ++-- front/plugins/internet_speedtest/config.json | 4 ++-- front/plugins/nmap_scan/config.json | 4 ++-- front/plugins/omada_sdn_imp/config.json | 4 ++-- front/plugins/omada_sdn_openapi/config.json | 4 ++-- front/plugins/pihole_scan/config.json | 4 ++-- front/plugins/snmp_discovery/config.json | 4 ++-- front/plugins/unifi_import/config.json | 4 ++-- front/plugins/vendor_update/config.json | 4 ++-- front/plugins/website_monitor/config.json | 4 ++-- front/report.php | 2 +- server/db/db_upgrade.py | 2 +- server/initialise.py | 4 ++++ server/scan/name_resolution.py | 12 ++++++------ test/backend/test_notification_templates.py | 14 +++++++------- 20 files changed, 49 insertions(+), 45 deletions(-) diff --git a/front/js/cache.js b/front/js/cache.js index 12c881d3..575a40d4 100644 --- a/front/js/cache.js +++ b/front/js/cache.js @@ -290,7 +290,7 @@ function getSetting (key) { // ----------------------------------------------------------------------------- function cacheStrings() { return new Promise((resolve, reject) => { - if(getCache(CACHE_KEYS.initFlag('cacheStrings')) === "true") + if(getCache(CACHE_KEYS.initFlag('cacheStrings_v2')) === "true") { // Core strings are cached, but plugin strings may have failed silently on // the first load (non-fatal fetch). Always re-fetch them so that plugin @@ -370,7 +370,7 @@ function cacheStrings() { }) .catch((error) => { // Handle failure in any of the language processing - handleFailure('cacheStrings'); + handleFailure('cacheStrings_v2'); reject(error); }); diff --git a/front/plugins/arp_scan/config.json b/front/plugins/arp_scan/config.json index 973927c4..484144ab 100755 --- a/front/plugins/arp_scan/config.json +++ b/front/plugins/arp_scan/config.json @@ -338,7 +338,7 @@ "elements": [ { "elementType": "select", - "elementOptions": [{ "multiple": "true" }], + "elementOptions": [{ "multiple": "true", "ordeable": "true"}], "transformers": [] } ] @@ -387,7 +387,7 @@ "elements": [ { "elementType": "select", - "elementOptions": [{ "multiple": "true" }], + "elementOptions": [{ "multiple": "true", "ordeable": "true"}], "transformers": [] } ] diff --git a/front/plugins/ddns_update/config.json b/front/plugins/ddns_update/config.json index 2337f809..e0d267ed 100755 --- a/front/plugins/ddns_update/config.json +++ b/front/plugins/ddns_update/config.json @@ -432,7 +432,7 @@ "elements": [ { "elementType": "select", - "elementOptions": [{ "multiple": "true" }], + "elementOptions": [{ "multiple": "true", "ordeable": "true"}], "transformers": [] } ] @@ -477,7 +477,7 @@ "elements": [ { "elementType": "select", - "elementOptions": [{ "multiple": "true" }], + "elementOptions": [{ "multiple": "true", "ordeable": "true"}], "transformers": [] } ] diff --git a/front/plugins/dhcp_leases/config.json b/front/plugins/dhcp_leases/config.json index b760f8d3..5beab1e7 100755 --- a/front/plugins/dhcp_leases/config.json +++ b/front/plugins/dhcp_leases/config.json @@ -753,7 +753,7 @@ "elements": [ { "elementType": "select", - "elementOptions": [{ "multiple": "true" }], + "elementOptions": [{ "multiple": "true", "ordeable": "true"}], "transformers": [] } ] @@ -802,7 +802,7 @@ "elements": [ { "elementType": "select", - "elementOptions": [{ "multiple": "true" }], + "elementOptions": [{ "multiple": "true", "ordeable": "true"}], "transformers": [] } ] diff --git a/front/plugins/dhcp_servers/config.json b/front/plugins/dhcp_servers/config.json index 049ec58d..313aea76 100755 --- a/front/plugins/dhcp_servers/config.json +++ b/front/plugins/dhcp_servers/config.json @@ -478,7 +478,7 @@ "elements": [ { "elementType": "select", - "elementOptions": [{ "multiple": "true" }], + "elementOptions": [{ "multiple": "true", "ordeable": "true"}], "transformers": [] } ] @@ -519,7 +519,7 @@ "elements": [ { "elementType": "select", - "elementOptions": [{ "multiple": "true" }], + "elementOptions": [{ "multiple": "true", "ordeable": "true"}], "transformers": [] } ] diff --git a/front/plugins/internet_ip/config.json b/front/plugins/internet_ip/config.json index 679913d5..65a0c5c2 100755 --- a/front/plugins/internet_ip/config.json +++ b/front/plugins/internet_ip/config.json @@ -324,7 +324,7 @@ "elements": [ { "elementType": "select", - "elementOptions": [{ "multiple": "true" }], + "elementOptions": [{ "multiple": "true", "ordeable": "true"}], "transformers": [] } ] @@ -369,7 +369,7 @@ "elements": [ { "elementType": "select", - "elementOptions": [{ "multiple": "true" }], + "elementOptions": [{ "multiple": "true", "ordeable": "true"}], "transformers": [] } ] diff --git a/front/plugins/internet_speedtest/config.json b/front/plugins/internet_speedtest/config.json index 49df80eb..71a6e77d 100755 --- a/front/plugins/internet_speedtest/config.json +++ b/front/plugins/internet_speedtest/config.json @@ -561,7 +561,7 @@ "elements": [ { "elementType": "select", - "elementOptions": [{ "multiple": "true" }], + "elementOptions": [{ "multiple": "true", "ordeable": "true"}], "transformers": [] } ] @@ -610,7 +610,7 @@ "elements": [ { "elementType": "select", - "elementOptions": [{ "multiple": "true" }], + "elementOptions": [{ "multiple": "true", "ordeable": "true"}], "transformers": [] } ] diff --git a/front/plugins/nmap_scan/config.json b/front/plugins/nmap_scan/config.json index b5326075..301fc4d8 100755 --- a/front/plugins/nmap_scan/config.json +++ b/front/plugins/nmap_scan/config.json @@ -553,7 +553,7 @@ "elements": [ { "elementType": "select", - "elementOptions": [{ "multiple": "true" }], + "elementOptions": [{ "multiple": "true", "ordeable": "true"}], "transformers": [] } ] @@ -594,7 +594,7 @@ "elements": [ { "elementType": "select", - "elementOptions": [{ "multiple": "true" }], + "elementOptions": [{ "multiple": "true", "ordeable": "true"}], "transformers": [] } ] diff --git a/front/plugins/omada_sdn_imp/config.json b/front/plugins/omada_sdn_imp/config.json index e4560f44..a4cbd014 100755 --- a/front/plugins/omada_sdn_imp/config.json +++ b/front/plugins/omada_sdn_imp/config.json @@ -429,7 +429,7 @@ "elements": [ { "elementType": "select", - "elementOptions": [{ "multiple": "true" }], + "elementOptions": [{ "multiple": "true", "ordeable": "true"}], "transformers": [] } ] @@ -462,7 +462,7 @@ "elements": [ { "elementType": "select", - "elementOptions": [{ "multiple": "true" }], + "elementOptions": [{ "multiple": "true", "ordeable": "true"}], "transformers": [] } ] diff --git a/front/plugins/omada_sdn_openapi/config.json b/front/plugins/omada_sdn_openapi/config.json index b3737b78..c56eaded 100755 --- a/front/plugins/omada_sdn_openapi/config.json +++ b/front/plugins/omada_sdn_openapi/config.json @@ -402,7 +402,7 @@ "elements": [ { "elementType": "select", - "elementOptions": [{ "multiple": "true" }], + "elementOptions": [{ "multiple": "true", "ordeable": "true"}], "transformers": [] } ] @@ -435,7 +435,7 @@ "elements": [ { "elementType": "select", - "elementOptions": [{ "multiple": "true" }], + "elementOptions": [{ "multiple": "true", "ordeable": "true"}], "transformers": [] } ] diff --git a/front/plugins/pihole_scan/config.json b/front/plugins/pihole_scan/config.json index c8a371de..722723a0 100755 --- a/front/plugins/pihole_scan/config.json +++ b/front/plugins/pihole_scan/config.json @@ -290,7 +290,7 @@ "elements": [ { "elementType": "select", - "elementOptions": [{ "multiple": "true" }], + "elementOptions": [{ "multiple": "true", "ordeable": "true"}], "transformers": [] } ] @@ -331,7 +331,7 @@ "elements": [ { "elementType": "select", - "elementOptions": [{ "multiple": "true" }], + "elementOptions": [{ "multiple": "true", "ordeable": "true"}], "transformers": [] } ] diff --git a/front/plugins/snmp_discovery/config.json b/front/plugins/snmp_discovery/config.json index 1c2ea990..95240621 100755 --- a/front/plugins/snmp_discovery/config.json +++ b/front/plugins/snmp_discovery/config.json @@ -658,7 +658,7 @@ "elements": [ { "elementType": "select", - "elementOptions": [{ "multiple": "true" }], + "elementOptions": [{ "multiple": "true", "ordeable": "true"}], "transformers": [] } ] @@ -699,7 +699,7 @@ "elements": [ { "elementType": "select", - "elementOptions": [{ "multiple": "true" }], + "elementOptions": [{ "multiple": "true", "ordeable": "true"}], "transformers": [] } ] diff --git a/front/plugins/unifi_import/config.json b/front/plugins/unifi_import/config.json index b5274514..c9cf3c0a 100755 --- a/front/plugins/unifi_import/config.json +++ b/front/plugins/unifi_import/config.json @@ -1026,7 +1026,7 @@ "elements": [ { "elementType": "select", - "elementOptions": [{ "multiple": "true" }], + "elementOptions": [{ "multiple": "true", "ordeable": "true"}], "transformers": [] } ] @@ -1067,7 +1067,7 @@ "elements": [ { "elementType": "select", - "elementOptions": [{ "multiple": "true" }], + "elementOptions": [{ "multiple": "true", "ordeable": "true"}], "transformers": [] } ] diff --git a/front/plugins/vendor_update/config.json b/front/plugins/vendor_update/config.json index 17f1f925..6cba3f85 100755 --- a/front/plugins/vendor_update/config.json +++ b/front/plugins/vendor_update/config.json @@ -299,7 +299,7 @@ "elements": [ { "elementType": "select", - "elementOptions": [{ "multiple": "true" }], + "elementOptions": [{ "multiple": "true", "ordeable": "true"}], "transformers": [] } ] @@ -344,7 +344,7 @@ "elements": [ { "elementType": "select", - "elementOptions": [{ "multiple": "true" }], + "elementOptions": [{ "multiple": "true", "ordeable": "true"}], "transformers": [] } ] diff --git a/front/plugins/website_monitor/config.json b/front/plugins/website_monitor/config.json index 251605fe..26db306f 100755 --- a/front/plugins/website_monitor/config.json +++ b/front/plugins/website_monitor/config.json @@ -538,7 +538,7 @@ "elements": [ { "elementType": "select", - "elementOptions": [{ "multiple": "true" }], + "elementOptions": [{ "multiple": "true", "ordeable": "true"}], "transformers": [] } ] @@ -579,7 +579,7 @@ "elements": [ { "elementType": "select", - "elementOptions": [{ "multiple": "true" }], + "elementOptions": [{ "multiple": "true", "ordeable": "true"}], "transformers": [] } ] diff --git a/front/report.php b/front/report.php index d27efdc8..6f6c5bf6 100755 --- a/front/report.php +++ b/front/report.php @@ -174,7 +174,7 @@ } else { // Initial data load - updateData('HTML', -1); // Default format to HTML and load the latest report + updateData('html', -1); // Default format to HTML and load the latest report } }); diff --git a/server/db/db_upgrade.py b/server/db/db_upgrade.py index 83739315..03ca83a4 100755 --- a/server/db/db_upgrade.py +++ b/server/db/db_upgrade.py @@ -817,7 +817,7 @@ def migrate_to_camelcase(sql) -> bool: for table, column_map in _CAMELCASE_COLUMN_MAP.items(): # Check table exists - sql.execute(f"SELECT name FROM sqlite_master WHERE type='table' AND name=?", (table,)) + sql.execute("SELECT name FROM sqlite_master WHERE type='table' AND name=?", (table,)) if not sql.fetchone(): mylog("verbose", [f"[db_upgrade] Table '{table}' does not exist — skipping"]) continue diff --git a/server/initialise.py b/server/initialise.py index 49e50b05..fcd48152 100755 --- a/server/initialise.py +++ b/server/initialise.py @@ -865,6 +865,10 @@ _column_replacements = { # Session columns r"\bses_MAC\b": "sesMac", r"\bses_IP\b": "sesIp", + r"\bses_DateTimeConnection\b": "sesDateTimeConnection", + r"\bses_DateTimeDisconnection\b": "sesDateTimeDisconnection", + r"\bses_EventTypeConnection\b": "sesEventTypeConnection", + r"\bses_EventTypeDisconnection\b": "sesEventTypeDisconnection", r"\bses_StillConnected\b": "sesStillConnected", r"\bses_AdditionalInfo\b": "sesAdditionalInfo", # Plugin columns (templates + WATCH values) diff --git a/server/scan/name_resolution.py b/server/scan/name_resolution.py index e05b4a15..d3cf654d 100755 --- a/server/scan/name_resolution.py +++ b/server/scan/name_resolution.py @@ -23,10 +23,10 @@ class NameResolver: nameNotFound = ResolvedName() # Check by MAC - sql.execute(f""" + sql.execute(""" SELECT watchedValue2 FROM Plugins_Objects - WHERE plugin = '{plugin}' AND objectPrimaryId = '{pMAC}' - """) + WHERE plugin = ? AND objectPrimaryId = ? + """, (plugin, pMAC)) result = sql.fetchall() # self.db.commitDB() # Issue #1251: Optimize name resolution lookup if result: @@ -36,10 +36,10 @@ class NameResolver: # Check name by IP if enabled if get_setting_value('NEWDEV_IP_MATCH_NAME'): - sql.execute(f""" + sql.execute(""" SELECT watchedValue2 FROM Plugins_Objects - WHERE plugin = '{plugin}' AND objectSecondaryId = '{pIP}' - """) + WHERE plugin = ? AND objectSecondaryId = ? + """, (plugin, pIP)) result = sql.fetchall() # self.db.commitDB() # Issue #1251: Optimize name resolution lookup if result: diff --git a/test/backend/test_notification_templates.py b/test/backend/test_notification_templates.py index f4448fd3..3a17237f 100644 --- a/test/backend/test_notification_templates.py +++ b/test/backend/test_notification_templates.py @@ -247,21 +247,21 @@ class TestConstructNotificationsTemplates(unittest.TestCase): mock_setting.side_effect = self._setting_factory({ "NTFPRCS_TEXT_SECTION_HEADERS": True, - "NTFPRCS_TEXT_TEMPLATE_down_reconnected": "{devName} ({eve_MAC}) reconnected at {eve_DateTime}", + "NTFPRCS_TEXT_TEMPLATE_down_reconnected": "{devName} ({eveMac}) reconnected at {eveDateTime}", }) reconnected = [ { "devName": "Switch", - "eve_MAC": "aa:11:bb:22:cc:33", + "eveMac": "aa:11:bb:22:cc:33", "devVendor": "Netgear", - "eve_IP": "10.0.0.2", - "eve_DateTime": "2025-01-15 09:30:00", - "eve_EventType": "Down Reconnected", + "eveIp": "10.0.0.2", + "eveDateTime": "2025-01-15 09:30:00", + "eveEventType": "Down Reconnected", "devComments": "", } ] - columns = ["devName", "eve_MAC", "devVendor", "eve_IP", "eve_DateTime", "eve_EventType", "devComments"] + columns = ["devName", "eveMac", "devVendor", "eveIp", "eveDateTime", "eveEventType", "devComments"] json_data = _make_json("down_reconnected", reconnected, columns, "🔁 Reconnected down devices") _, text = construct_notifications(json_data, "down_reconnected") @@ -288,7 +288,7 @@ class TestConstructNotificationsTemplates(unittest.TestCase): # Get HTML with template mock_setting.side_effect = self._setting_factory({ "NTFPRCS_TEXT_SECTION_HEADERS": True, - "NTFPRCS_TEXT_TEMPLATE_new_devices": "{devName} ({eve_MAC})", + "NTFPRCS_TEXT_TEMPLATE_new_devices": "{devName} ({eveMac})", }) html_with, _ = construct_notifications(json_data, "new_devices") From 43984132c49a065e4fff5378db27a255bc19dc0a Mon Sep 17 00:00:00 2001 From: "Jokob @NetAlertX" <96159884+jokob-sk@users.noreply.github.com> Date: Tue, 17 Mar 2026 09:46:27 +0000 Subject: [PATCH 005/189] Fix Spanish translations in config.json files for internet_speedtest, nmap_scan, and snmp_discovery plugins --- front/plugins/internet_speedtest/config.json | 2 +- front/plugins/nmap_scan/config.json | 2 +- front/plugins/snmp_discovery/config.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/front/plugins/internet_speedtest/config.json b/front/plugins/internet_speedtest/config.json index 71a6e77d..4aad632f 100755 --- a/front/plugins/internet_speedtest/config.json +++ b/front/plugins/internet_speedtest/config.json @@ -644,7 +644,7 @@ }, { "language_code": "es_es", - "string": "Envíe una notificación solo en estos estados. new significa que se descubrió un nuevo objeto único (combinación única de PrimaryId y SecondaryId). watched-changed significa que seleccionó watchedValueN Las columnas cambiaron." + "string": "Envíe una notificación solo en estos estados. new significa que se descubrió un nuevo objeto único (combinación única de PrimaryId y SecondaryId). watched-changed significa que las columnas watchedValueN seleccionadas cambiaron." }, { "language_code": "de_de", diff --git a/front/plugins/nmap_scan/config.json b/front/plugins/nmap_scan/config.json index 301fc4d8..5eb8e5f6 100755 --- a/front/plugins/nmap_scan/config.json +++ b/front/plugins/nmap_scan/config.json @@ -619,7 +619,7 @@ }, { "language_code": "es_es", - "string": "Envíe una notificación solo en estos estados. new significa que se descubrió un nuevo objeto único (combinación única de PrimaryId y SecondaryId). watched-changed significa que seleccionó watchedValueN Las columnas cambiaron." + "string": "Envíe una notificación solo en estos estados. new significa que se descubrió un nuevo objeto único (combinación única de PrimaryId y SecondaryId). watched-changed significa que las columnas watchedValueN seleccionadas cambiaron." } ] } diff --git a/front/plugins/snmp_discovery/config.json b/front/plugins/snmp_discovery/config.json index 95240621..ab71bc5e 100755 --- a/front/plugins/snmp_discovery/config.json +++ b/front/plugins/snmp_discovery/config.json @@ -688,7 +688,7 @@ }, { "language_code": "es_es", - "string": "Envíe una notificación si los valores seleccionados cambian. Utilice CTRL + clic para seleccionar/deseleccionar.
  • watchedValue1 es el nombre de host (no detectable)
  • watchedValue2 es la IP del enrutador
  • watchedValue3< /code> no se utiliza
  • watchedValue4 no se utiliza
" + "string": "Envíe una notificación si los valores seleccionados cambian. Utilice CTRL + clic para seleccionar/deseleccionar.
  • watchedValue1 es el nombre de host (no detectable)
  • watchedValue2 es la IP del enrutador
  • watchedValue3 no se utiliza
  • watchedValue4 no se utiliza
" } ] }, From b311113575fc62e1b0dbef9077953e99998fbf3f Mon Sep 17 00:00:00 2001 From: "Jokob @NetAlertX" <96159884+jokob-sk@users.noreply.github.com> Date: Tue, 17 Mar 2026 11:58:53 +0000 Subject: [PATCH 006/189] Fix Spanish translations and improve HTML attributes in config files and report --- front/plugins/vendor_update/config.json | 2 +- front/plugins/website_monitor/config.json | 2 +- front/report.php | 2 +- server/initialise.py | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/front/plugins/vendor_update/config.json b/front/plugins/vendor_update/config.json index 6cba3f85..3625550d 100755 --- a/front/plugins/vendor_update/config.json +++ b/front/plugins/vendor_update/config.json @@ -378,7 +378,7 @@ }, { "language_code": "es_es", - "string": "Envíe una notificación solo en estos estados. new significa que se descubrió un nuevo objeto único (combinación única de PrimaryId y SecondaryId). watched-changed significa que seleccionó watchedValueN Las columnas cambiaron." + "string": "Envíe una notificación solo en estos estados. new significa que se descubrió un nuevo objeto único (combinación única de PrimaryId y SecondaryId). watched-changed significa que las columnas watchedValueN seleccionadas cambiaron." }, { "language_code": "de_de", diff --git a/front/plugins/website_monitor/config.json b/front/plugins/website_monitor/config.json index 26db306f..0e4c7e7f 100755 --- a/front/plugins/website_monitor/config.json +++ b/front/plugins/website_monitor/config.json @@ -605,7 +605,7 @@ "description": [ { "language_code": "en_us", - "string": "Send a notification only on these statuses. new means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. watched-changed means that selected watchedValueN columns changed." + "string": "Send a notification only on these statuses. new means a new unique (unique combination of objectPrimaryId and objectSecondaryId) object was discovered. watched-changed means that selected watchedValueN columns changed." }, { "language_code": "es_es", diff --git a/front/report.php b/front/report.php index 6f6c5bf6..33afb736 100755 --- a/front/report.php +++ b/front/report.php @@ -109,7 +109,7 @@ `; break; case 'text': - notificationData.innerHTML = `
${formatData}
`; + notificationData.innerHTML = `
${formatData}
`; break; } diff --git a/server/initialise.py b/server/initialise.py index fcd48152..740d7cef 100755 --- a/server/initialise.py +++ b/server/initialise.py @@ -862,6 +862,7 @@ _column_replacements = { r"\beve_AdditionalInfo\b": "eveAdditionalInfo", r"\beve_PendingAlertEmail\b": "evePendingAlertEmail", r"\beve_PairEventRowid\b": "evePairEventRowid", + r"\beve_PairEventRowID\b": "evePairEventRowid", # Session columns r"\bses_MAC\b": "sesMac", r"\bses_IP\b": "sesIp", From d40d1795d2d831f4927ee2ce252fcf6f6f46969a Mon Sep 17 00:00:00 2001 From: mid Date: Tue, 17 Mar 2026 08:29:51 +0100 Subject: [PATCH 007/189] Translated using Weblate (Japanese) Currently translated at 100.0% (806 of 806 strings) Translation: NetAlertX/core Translate-URL: https://hosted.weblate.org/projects/pialert/core/ja/ --- front/php/templates/language/ja_jp.json | 116 ++++++++++++------------ 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/front/php/templates/language/ja_jp.json b/front/php/templates/language/ja_jp.json index 31cfb382..43f2a891 100644 --- a/front/php/templates/language/ja_jp.json +++ b/front/php/templates/language/ja_jp.json @@ -1,7 +1,7 @@ { - "API_CUSTOM_SQL_description": "カスタムSQLクエリを指定するとJSONファイルが生成され、table_custom_endpoint.jsonファイルエンドポイント経由で公開できます。", + "API_CUSTOM_SQL_description": "カスタムSQLクエリを指定するとJSONファイルが生成され、table_custom_endpoint.json ファイルエンドポイント 経由で公開できます。", "API_CUSTOM_SQL_name": "カスタムエンドポイント", - "API_TOKEN_description": "安全な通信のための API トークン。生成するか、任意の値を入力してください。リクエストヘッダーで送信され、SYNCプラグイン、GraphQLサーバー、その他のAPIエンドポイントで使用されます。APIドキュメントに記載の通り、API エンドポイントを使用して独自の連携機能を作成できます。", + "API_TOKEN_description": "安全な通信のための API トークン。生成するか、任意の値を入力してください。リクエストヘッダーで送信され、SYNC プラグイン、GraphQLサーバー、その他のAPIエンドポイントで使用されます。APIドキュメント に記載の通り、API エンドポイントを使用して独自の連携機能を作成できます。", "API_TOKEN_name": "APIトークン", "API_display_name": "API", "API_icon": "", @@ -27,7 +27,7 @@ "AppEvents_ObjectType": "オブジェクトタイプ", "AppEvents_Plugin": "プラグイン", "AppEvents_Type": "種別", - "BACKEND_API_URL_description": "フロントエンドからバックエンドに通信するために使用します。 デフォルトでは/serverに設定されており、通常変更する必要はありません。", + "BACKEND_API_URL_description": "フロントエンドからバックエンドに通信するために使用します。デフォルトでは /server に設定されており、通常変更する必要はありません。", "BACKEND_API_URL_name": "バックエンド API URL", "BackDevDetail_Actions_Ask_Run": "このアクションを実行してよろしいですか?", "BackDevDetail_Actions_Not_Registered": "登録されていないアクション: ", @@ -61,12 +61,12 @@ "BackDevices_Restore_okay": "復元が正常に完了しました。", "BackDevices_darkmode_disabled": "ダークモード無効化", "BackDevices_darkmode_enabled": "ダークモード有効化", - "CLEAR_NEW_FLAG_description": "有効にした場合(0で無効)、新規デバイスのフラグは初回検知時刻から指定された時間(1時間単位)が経過すると自動的に解除されます。", + "CLEAR_NEW_FLAG_description": "有効にした場合(0 で無効)、新規デバイス のフラグは 初回検知時刻 から指定された時間(1時間単位)が経過すると自動的に解除されます。", "CLEAR_NEW_FLAG_name": "新規フラグの解除", "CustProps_cant_remove": "削除できません。少なくとも1つのプロパティが必要です。", "DAYS_TO_KEEP_EVENTS_description": "これはメンテナンス設定です。イベントエントリを保持する日数を指定します。それより古いイベントは定期的に削除されます。プラグインイベント履歴にも適用されます。", "DAYS_TO_KEEP_EVENTS_name": "古いイベントの削除", - "DISCOVER_PLUGINS_description": "このオプションを無効にすると、初期化と設定の保存が高速化されます。無効にした場合、プラグインは検出されず、LOADED_PLUGINS設定に新しいプラグインを追加することはできません。", + "DISCOVER_PLUGINS_description": "このオプションを無効にすると、初期化と設定の保存が高速化されます。無効にした場合、プラグインは検出されず、LOADED_PLUGINS 設定に新しいプラグインを追加することはできません。", "DISCOVER_PLUGINS_name": "プラグインの検出", "DevDetail_Children_Title": "親子関係", "DevDetail_Copy_Device_Title": "デバイスから詳細をコピー", @@ -95,8 +95,8 @@ "DevDetail_MainInfo_Group": "グループ", "DevDetail_MainInfo_Location": "位置", "DevDetail_MainInfo_Name": "名前", - "DevDetail_MainInfo_Network": "ノード(MAC)", - "DevDetail_MainInfo_Network_Port": "ポート", + "DevDetail_MainInfo_Network": " ノード(MAC)", + "DevDetail_MainInfo_Network_Port": " ポート", "DevDetail_MainInfo_Network_Site": "サイト", "DevDetail_MainInfo_Network_Title": "ネットワーク詳細", "DevDetail_MainInfo_Owner": "所有者", @@ -118,7 +118,7 @@ "DevDetail_Nmap_buttonFast_text": "高速スキャン: デフォルトのスキャンよりも少ないポート数(100)をスキャンする(数秒)", "DevDetail_Nmap_buttonSkipDiscovery": "ホスト検出をスキップ", "DevDetail_Nmap_buttonSkipDiscovery_text": "ホスト検出をスキップ(-Pnオプション): ホスト検出なしのデフォルトスキャン", - "DevDetail_Nmap_resultsLink": "スキャン開始後、このページを離れても構いません。結果はapp_front.logファイルにも記録されます。", + "DevDetail_Nmap_resultsLink": "スキャン開始後、このページを離れても構いません。結果は app_front.log ファイルにも記録されます。", "DevDetail_Owner_hover": "このデバイスを所有者は誰ですか。自由入力欄。", "DevDetail_Periodselect_All": "全件", "DevDetail_Periodselect_LastMonth": "先月", @@ -185,7 +185,7 @@ "DevDetail_Vendor_hover": "ベンダーは自動検出されるべきです。カスタム値を上書きまたは追加できます。", "DevDetail_WOL_Title": " Wake-on-LAN", "DevDetail_button_AddIcon": "アイコンの追加", - "DevDetail_button_AddIcon_Help": "SVG HTMLタグまたはFont Awesome HTMLタグのアイコンを貼り付けてください。詳細はアイコンのドキュメントを参照してください。", + "DevDetail_button_AddIcon_Help": "SVG HTMLタグまたはFont Awesome HTMLタグのアイコンを貼り付けてください。詳細は アイコンのドキュメント を参照してください。", "DevDetail_button_AddIcon_Tooltip": "このデバイスに、ドロップダウンにない新しいアイコンを追加します。", "DevDetail_button_Delete": "デバイスの削除", "DevDetail_button_DeleteEvents": "イベントの削除", @@ -196,19 +196,19 @@ "DevDetail_button_OverwriteIcons_Warning": "このデバイスと同じデバイス種別を持つすべてのデバイスにアイコンを上書きしてもよろしいですか?", "DevDetail_button_Reset": "変更をリセット", "DevDetail_button_Save": "保存", - "DeviceEdit_ValidMacIp": "有効なMacIPアドレスを入力します.", + "DeviceEdit_ValidMacIp": "有効な MacIP アドレスを入力します.", "Device_MultiEdit": "マルチエディタ", - "Device_MultiEdit_Backup": "注意:以下の項目に誤った値を入力すると設定が破損します。まずデータベースまたはデバイスの設定をバックアップしてください(クリックしてダウンロード)。このファイルからデバイスを復元する方法については、バックアップのドキュメントを参照してください。変更を適用するには、更新したい各フィールドの保存アイコンをクリックしてください。", + "Device_MultiEdit_Backup": "注意:以下の項目に誤った値を入力すると設定が破損します。まずデータベースまたはデバイスの設定をバックアップしてください(クリックしてダウンロード )。このファイルからデバイスを復元する方法については、バックアップのドキュメント を参照してください。変更を適用するには、更新したい各フィールドの 保存 アイコンをクリックしてください。", "Device_MultiEdit_Fields": "フィールドの編集:", "Device_MultiEdit_MassActions": "大量のアクション:", "Device_MultiEdit_No_Devices": "デバイスが選択されていません。", "Device_MultiEdit_Tooltip": "注意。これをクリックすると、左側の値が上記で選択したすべてのデバイスに適用されます。", "Device_NextScan_Imminent": "まもなく...", "Device_NextScan_In": "次のスキャンまでおよそ ", - "Device_NoData_Help": "スキャン後にデバイスが表示されない場合は、SCAN_SUBNETS設定とドキュメントを確認してください。", + "Device_NoData_Help": "スキャン後にデバイスが表示されない場合は、SCAN_SUBNETS設定と ドキュメント を確認してください。", "Device_NoData_Scanning": "最初のスキャンを待機中 - 初期設定後、数分かかる場合があります。", "Device_NoData_Title": "デバイスが見つかりません", - "Device_NoMatch_Title": "", + "Device_NoMatch_Title": "このフィルタ条件に一致するデバイスはありません", "Device_Save_Failed": "デバイスの保存に失敗しました", "Device_Save_Unauthorized": "許可されていない - 無効なAPIトークン", "Device_Saved_Success": "デバイスが正常に保存されました", @@ -269,7 +269,7 @@ "Device_Tablelenght_all": "全件", "Device_Title": "デバイス", "Devices_Filters": "フィルター", - "ENABLE_PLUGINS_description": "プラグイン機能を有効にします。プラグインの読み込みにはより多くのハードウェアリソースを必要とするため、リソースが限られているシステムでは無効にすることをお勧めします。", + "ENABLE_PLUGINS_description": "プラグイン 機能を有効にします。プラグインの読み込みにはより多くのハードウェアリソースを必要とするため、リソースが限られているシステムでは無効にすることをお勧めします。", "ENABLE_PLUGINS_name": "有効プラグイン", "ENCRYPTION_KEY_description": "データ暗号化キー。", "ENCRYPTION_KEY_name": "暗号化キー", @@ -372,21 +372,21 @@ "Gen_Warning": "警告", "Gen_Work_In_Progress": "作業中、https://github.com/netalertx/NetAlertX/issues へのフィードバックの好機です", "Gen_create_new_device": "新規デバイス", - "Gen_create_new_device_info": "デバイスは通常、プラグインを使用して検出されます。ただし、特定のケースでは手動でデバイスを追加する必要がある場合があります。具体的なシナリオについては、リモートネットワークドキュメントを参照してください。", + "Gen_create_new_device_info": "デバイスは通常、プラグイン を使用して検出されます。ただし、特定のケースでは手動でデバイスを追加する必要がある場合があります。具体的なシナリオについては、リモートネットワークドキュメント を参照してください。", "General_display_name": "一般", "General_icon": "", - "HRS_TO_KEEP_NEWDEV_description": "これはデバイスを削除するメンテナンス設定です。有効にした場合(0で無効)、新規デバイスとしてマークされたデバイスの内、初回検知時刻が指定された時間より古いものは削除されます。新規デバイスX時間後に自動削除したい場合に使用してください。", + "HRS_TO_KEEP_NEWDEV_description": "これは デバイスを削除 するメンテナンス設定です。有効にした場合(0 で無効)、新規デバイス としてマークされたデバイスの内、初回検知時刻 が指定された時間より古いものは削除されます。新規デバイスX 時間後に自動削除したい場合に使用してください。", "HRS_TO_KEEP_NEWDEV_name": "新規デバイスの削除", - "HRS_TO_KEEP_OFFDEV_description": "これはデバイスを削除するメンテナンス設定です。有効にした場合(0で無効)、オフライン状態のデバイスの内、最終接続日時が指定された時間より古いものは削除されます。オフラインデバイスX時間経過後に自動削除したい場合に使用してください。", + "HRS_TO_KEEP_OFFDEV_description": "これは デバイスを削除 するメンテナンス設定です。有効にした場合(0 で無効)、オフライン 状態のデバイスの内、最終接続日時 が指定された時間より古いものは削除されます。オフラインデバイスX 時間経過後に自動削除したい場合に使用してください。", "HRS_TO_KEEP_OFFDEV_name": "オフラインデバイスを削除する", - "LOADED_PLUGINS_description": "読み込まれたプラグイン。プラグインの追加はアプリケーションの速度を低下させる可能性があります。有効化が必要なプラグインの種類やスキャンオプションについては、プラグインのドキュメントを参照してください。読み込まれなかったプラグインの設定は失われます。読み込まない設定にできるのは無効化されたプラグインのみです。", + "LOADED_PLUGINS_description": "読み込まれたプラグイン。プラグインの追加はアプリケーションの速度を低下させる可能性があります。有効化が必要なプラグインの種類やスキャンオプションについては、プラグインのドキュメント を参照してください。読み込まれなかったプラグインの設定は失われます。読み込まない設定にできるのは 無効化 されたプラグインのみです。", "LOADED_PLUGINS_name": "読み込まれたプラグイン", "LOG_LEVEL_description": "この設定により、より詳細なログ出力が有効になります。データベースへのイベント書き込みのデバッグに有用です。", "LOG_LEVEL_name": "追加のログ出力", "Loading": "読み込み中…", "Login_Box": "パスワードを入力してください", "Login_Default_PWD": "デフォルトパスワード「123456」は有効なままです。", - "Login_Info": "パスワードはSet Passwordプラグインで設定されます。ログインに問題がある場合はSETPWDのドキュメントを確認してください。", + "Login_Info": "パスワードはSet Passwordプラグインで設定されます。ログインに問題がある場合は SETPWDのドキュメント を確認してください。", "Login_Psw-box": "パスワード", "Login_Psw_alert": "パスワードアラート!", "Login_Psw_folder": "config フォルダ内。", @@ -403,31 +403,31 @@ "Maint_Restart_Server_noti_text": "バックエンドサーバーを再起動してもよろしいですか?アプリの不整合が発生する可能性があります。まず設定のバックアップを行ってください。

注:この操作には数分かかる場合があります。", "Maintenance_InitCheck": "初期化チェック", "Maintenance_InitCheck_Checking": "確認中…", - "Maintenance_InitCheck_QuickSetupGuide": "クイックセットアップガイドに従ったことを確認してください。", + "Maintenance_InitCheck_QuickSetupGuide": "クイックセットアップガイド に従ったことを確認してください。", "Maintenance_InitCheck_Success": "アプリケーションの初期化に成功!", "Maintenance_ReCheck": "再チェック", "Maintenance_Running_Version": "インストールバージョン", "Maintenance_Status": "状態", "Maintenance_Title": "メンテナンスツール", "Maintenance_Tool_DownloadConfig": "設定エクスポート", - "Maintenance_Tool_DownloadConfig_text": "app.confファイルに保存されている構成設定の完全なバックアップをダウンロードしてください。", + "Maintenance_Tool_DownloadConfig_text": "app.conf ファイルに保存されている構成設定の完全なバックアップをダウンロードしてください。", "Maintenance_Tool_DownloadWorkflows": "ワークフローのエクスポート", - "Maintenance_Tool_DownloadWorkflows_text": "workflows.jsonファイルに保存されているワークフロー設定の完全なバックアップをダウンロードしてください。", + "Maintenance_Tool_DownloadWorkflows_text": "workflows.json ファイルに保存されているワークフロー設定の完全なバックアップをダウンロードしてください。", "Maintenance_Tool_ExportCSV": "デバイスエクスポート(csv)", "Maintenance_Tool_ExportCSV_noti": "デバイスエクスポート(csv)", "Maintenance_Tool_ExportCSV_noti_text": "CSVファイルを生成してよろしいですか?", - "Maintenance_Tool_ExportCSV_text": "ネットワークノードとデバイス間の接続関係を含むデバイス一覧を記載したCSV(カンマ区切り値)ファイルを生成します。この操作は、CSVバックアッププラグインを有効化することで実行できます。", + "Maintenance_Tool_ExportCSV_text": "ネットワークノードとデバイス間の接続関係を含むデバイス一覧を記載したCSV(カンマ区切り値)ファイルを生成します。この操作は、CSVバックアップ プラグインを有効化することで実行できます。", "Maintenance_Tool_ImportCSV": "デバイスインポート(csv)", "Maintenance_Tool_ImportCSV_noti": "デバイスインポート(csv)", - "Maintenance_Tool_ImportCSV_noti_text": "CSVファイルを本当にインポートしますか?これによりデータベース内のデバイスが完全に上書きされます。", - "Maintenance_Tool_ImportCSV_text": "この機能を使用する前に、必ずバックアップを作成してください。ネットワークノードとデバイス間の接続関係を含むデバイス一覧を記載したCSV(カンマ区切り値)ファイルをインポートします。そのためには、devices.csv という名前のCSVファイルを/configフォルダに配置してください。", + "Maintenance_Tool_ImportCSV_noti_text": "CSVファイルを本当にインポートしますか?これによりデータベース内のデバイスが完全に 上書き されます。", + "Maintenance_Tool_ImportCSV_text": "この機能を使用する前に、必ずバックアップを作成してください。ネットワークノードとデバイス間の接続関係を含むデバイス一覧を記載したCSV(カンマ区切り値)ファイルをインポートします。そのためには、devices.csv という名前のCSVファイルを /config フォルダに配置してください。", "Maintenance_Tool_ImportConfig_noti": "設定のインポート (app.conf)", "Maintenance_Tool_ImportPastedCSV": "デバイスのインポート(csv貼り付け)", - "Maintenance_Tool_ImportPastedCSV_noti_text": "貼り付けたCSVを本当にインポートしますか?これによりデータベース内のデバイスが完全に上書きされます。", + "Maintenance_Tool_ImportPastedCSV_noti_text": "貼り付けたCSVを本当にインポートしますか?これによりデータベース内のデバイスが完全に 上書き されます。", "Maintenance_Tool_ImportPastedCSV_text": "この機能を使用する前に、必ずバックアップを作成してください。ネットワークノードとデバイス間の接続関係を含むデバイス一覧が記載されたCSV(カンマ区切り値)ファイルをインポートします。", "Maintenance_Tool_ImportPastedConfig": "設定のインポート(貼り付け)", - "Maintenance_Tool_ImportPastedConfig_noti_text": "貼り付けた設定を本当にインポートしますか?これによりapp.confファイルが完全に上書きされます。", - "Maintenance_Tool_ImportPastedConfig_text": "アプリケーション設定をすべて含むapp.confファイルをインポートします。まず設定のエクスポートで現在のapp.confファイルをダウンロードすることをお勧めします。", + "Maintenance_Tool_ImportPastedConfig_noti_text": "貼り付けた設定を本当にインポートしますか?これにより app.conf ファイルが完全に 上書き されます。", + "Maintenance_Tool_ImportPastedConfig_text": "アプリケーション設定をすべて含む app.conf ファイルをインポートします。まず 設定のエクスポート で現在の app.conf ファイルをダウンロードすることをお勧めします。", "Maintenance_Tool_UnlockFields": "デバイスフィールドのロック解除", "Maintenance_Tool_UnlockFields_noti": "デバイスフィールドのロック解除", "Maintenance_Tool_UnlockFields_noti_text": "全デバイスのデバイスフィールドのソース値(LOCKED/USER)をすべてクリアしてもよろしいですか? この操作は元に戻せません。", @@ -435,7 +435,7 @@ "Maintenance_Tool_arpscansw": "arpスキャンの切り替え(オン/オフ)", "Maintenance_Tool_arpscansw_noti": "arpスキャンをオンまたはオフにする", "Maintenance_Tool_arpscansw_noti_text": "スキャンをオフにした場合、再度有効化されるまでオフのままとなります。", - "Maintenance_Tool_arpscansw_text": "ARPスキャンの有効化または無効化。スキャンを無効化した場合、再度有効化されるまで無効状態が維持されます。アクティブなスキャンはキャンセルされません。", + "Maintenance_Tool_arpscansw_text": "arpスキャンの有効化または無効化。スキャンを無効化した場合、再度有効化されるまで無効状態が維持されます。アクティブなスキャンはキャンセルされません。", "Maintenance_Tool_backup": "DBバックアップ", "Maintenance_Tool_backup_noti": "DBバックアップ", "Maintenance_Tool_backup_noti_text": "データベースのバックアップを実行してもよろしいですか? 現在スキャンが実行されていないことを確認してください。", @@ -475,7 +475,7 @@ "Maintenance_Tool_del_unknowndev_noti_text": "すべての(Unknown)のデバイスと(name not found)のデバイスを削除してもよろしいですか?", "Maintenance_Tool_del_unknowndev_text": "この機能を使用する前に、必ずバックアップを作成してください。削除操作は元に戻せません。データベースから(Unknown)という名前のデバイスをすべて削除します。", "Maintenance_Tool_del_unlockFields_selecteddev_text": "これにより、選択したデバイスの「LOCKED/USER」フィールドのロックが解除されます。この操作は取り消せません。", - "Maintenance_Tool_displayed_columns_text": "デバイスページの列の表示状態と順序を変更します。", + "Maintenance_Tool_displayed_columns_text": " デバイス ページの列の表示状態と順序を変更します。", "Maintenance_Tool_drag_me": "ドラッグして列を並べ替え。", "Maintenance_Tool_order_columns_text": "Maintenance_Tool_order_columns_text", "Maintenance_Tool_purgebackup": "バックアップ除去", @@ -500,7 +500,7 @@ "Maintenance_arp_status_off": "無効化中", "Maintenance_arp_status_on": "スキャン中", "Maintenance_built_on": "ビルド日", - "Maintenance_current_version": "最新です。現在の取り組みをご覧ください。", + "Maintenance_current_version": "最新です。現在の取り組み をご覧ください。", "Maintenance_database_backup": "DBバックアップ", "Maintenance_database_backup_found": "バックアップが見つかりました", "Maintenance_database_backup_total": "総ディスク使用量", @@ -512,13 +512,13 @@ "Maintenance_lang_selector_empty": "言語を選択", "Maintenance_lang_selector_lable": "言語を選択", "Maintenance_lang_selector_text": "変更はクライアント側で行われるため、現在のブラウザにのみ影響します。", - "Maintenance_new_version": "新しいバージョンが利用可能です。リリースノートを確認してください。", + "Maintenance_new_version": "新しいバージョンが利用可能です。リリースノート を確認してください。", "Maintenance_themeselector_apply": "適用", "Maintenance_themeselector_empty": "スキンを選択", "Maintenance_themeselector_lable": "スキンを選択", "Maintenance_themeselector_text": "変更はサーバー側で行われるため、使用中のすべてのデバイスに影響します。", "Maintenance_version": "アプリのアップデート", - "NETWORK_DEVICE_TYPES_description": "ネットワークビューにおいてネットワーク機器として使用できるデバイス種別。デバイス種別は、デバイス詳細の特定のデバイスにおける種別設定と一致する必要があります。デバイスに追加するには+ボタンを使用してください。既存の種別を削除せず、新しい種別のみを追加してください。", + "NETWORK_DEVICE_TYPES_description": "ネットワークビューにおいてネットワーク機器として使用できるデバイス種別。デバイス種別は、デバイス詳細の特定のデバイスにおける 種別 設定と一致する必要があります。デバイスに追加するには + ボタンを使用してください。既存の種別を削除せず、新しい種別のみを追加してください。", "NETWORK_DEVICE_TYPES_name": "ネットワーク機器の種別", "Navigation_About": "概要", "Navigation_AppEvents": "アプリイベント", @@ -536,7 +536,7 @@ "Navigation_Settings": "設定", "Navigation_SystemInfo": "システム情報", "Navigation_Workflows": "ワークフロー", - "Network_Assign": "上記ネットワークノードに接続", + "Network_Assign": "上記 ネットワークノードに接続", "Network_Cant_Assign": "ルートインターネットノードを子リーフノードとして割り当てることはできません.", "Network_Cant_Assign_No_Node_Selected": "割り当てられません、親ノードが選択されていません。", "Network_Configuration_Error": "設定エラー", @@ -546,7 +546,7 @@ "Network_ManageAdd_Name": "デバイス名", "Network_ManageAdd_Name_text": "特殊文字を含まない名前", "Network_ManageAdd_Port": "ポート数", - "Network_ManageAdd_Port_text": "Wi-FiおよびPLCの場合は空欄にしてください", + "Network_ManageAdd_Port_text": "wifiおよびplcの場合は空欄にしてください", "Network_ManageAdd_Submit": "デバイス追加", "Network_ManageAdd_Type": "デバイス種別", "Network_ManageAdd_Type_text": "-- 種別選択 --", @@ -562,19 +562,19 @@ "Network_ManageEdit_Name": "新規デバイス名", "Network_ManageEdit_Name_text": "特殊文字を含まない名前", "Network_ManageEdit_Port": " 新規ポート数", - "Network_ManageEdit_Port_text": "Wi-FiおよびPLCの場合は空欄にしてください", + "Network_ManageEdit_Port_text": "wifiおよびplcの場合は空欄にしてください", "Network_ManageEdit_Submit": "変更を保存", "Network_ManageEdit_Type": "新規デバイス種別", "Network_ManageEdit_Type_text": "-- 種別選択 --", "Network_ManageLeaf": "割り当ての管理", "Network_ManageUnassign": "割り当て解除", - "Network_NoAssignedDevices": "このネットワークノードには割り当てられたデバイス(リーフノード)がありません。以下のデバイスから1つを割り当てるか、デバイス内の任意のデバイスの詳細タブに移動し、そこでネットワークノード(MAC)ポートに割り当ててください。", + "Network_NoAssignedDevices": "このネットワークノードには割り当てられたデバイス(リーフノード)がありません。以下のデバイスから1つを割り当てるか、 デバイス 内の任意のデバイスの 詳細 タブに移動し、そこでネットワーク ノード(MAC) ポート に割り当ててください。", "Network_NoDevices": "設定するデバイスがありません", "Network_Node": "ネットワークノード", "Network_Node_Name": "ノード名", "Network_Parent": "上位のネットワーク機器", "Network_Root": "ルートノード", - "Network_Root_Not_Configured": "インターネットルートデバイス種別フィールドで、ゲートウェイなどのネットワーク機器種別を選択し、この画面の設定を開始してください。

詳細なドキュメントはネットワークの設定方法ページガイドでご覧いただけます", + "Network_Root_Not_Configured": "インターネットルートデバイス種別 フィールドで、ゲートウェイ などのネットワーク機器種別を選択し、この画面の設定を開始してください。

詳細なドキュメントは ネットワークの設定方法ページ ガイドでご覧いただけます", "Network_Root_Unconfigurable": "設定不可のルート", "Network_ShowArchived": "アーカイブを表示", "Network_ShowOffline": "オフラインを表示", @@ -585,13 +585,13 @@ "Network_UnassignedDevices": "未割り当てデバイス", "Notifications_All": "すべての通知", "Notifications_Mark_All_Read": "すべて既読にする", - "PIALERT_WEB_PASSWORD_description": "デフォルトのパスワードは123456です。パスワードを変更するには、コンテナ内で/app/back/pialert-cliを実行するか、SETPWD_RUNパスワード設定プラグインを使用してください。", + "PIALERT_WEB_PASSWORD_description": "デフォルトのパスワードは 123456 です。パスワードを変更するには、コンテナ内で /app/back/pialert-cli を実行するか、SETPWD_RUN パスワード設定プラグイン を使用してください。", "PIALERT_WEB_PASSWORD_name": "ログインパスワード", "PIALERT_WEB_PROTECTION_description": "有効にするとログインダイアログが表示されます。インスタンスにロックアウトされた場合は、以下をよくご確認ください。", "PIALERT_WEB_PROTECTION_name": "ログインを有効化", "PLUGINS_KEEP_HIST_description": "プラグイン履歴スキャン結果のエントリをいくつ保持すべきか(デバイス固有ではなく、プラグインごとに)。", "PLUGINS_KEEP_HIST_name": "プラグイン履歴", - "PRAGMA_JOURNAL_SIZE_LIMIT_description": "SQLite WAL(Write-Ahead Log)の自動チェックポイント発生前の最大サイズ(MB単位)。低い値(10~20 MB)ではディスク/ストレージ使用量を削減しますが、スキャン時のCPU使用率が増加します。高い値(50~100 MB)は操作中のCPUスパイクを軽減しますが、RAMとディスク容量をより多く消費する可能性があります。デフォルトの50 MBは両者のバランスを取ります。SDカードを搭載したNASデバイスなどのリソース制約のあるシステムで有用です。設定保存後、変更を有効にするにはサーバーを再起動してください。", + "PRAGMA_JOURNAL_SIZE_LIMIT_description": "SQLite WAL(Write-Ahead Log)の自動チェックポイント発生前の最大サイズ(MB単位)。低い値(10~20 MB)ではディスク/ストレージ使用量を削減しますが、スキャン時のCPU使用率が増加します。高い値(50~100 MB)は操作中のCPUスパイクを軽減しますが、RAMとディスク容量をより多く消費する可能性があります。デフォルトの 50 MB は両者のバランスを取ります。SDカードを搭載したNASデバイスなどのリソース制約のあるシステムで有用です。設定保存後、変更を有効にするにはサーバーを再起動してください。", "PRAGMA_JOURNAL_SIZE_LIMIT_name": "WALサイズ制限(MB)", "Plugins_DeleteAll": "すべて削除(フィルターは無視されます)", "Plugins_Filters_Mac": "Macフィルター", @@ -628,12 +628,12 @@ "REPORT_DASHBOARD_URL_description": "このURLは、HTMLレポート(例:メール)内のリンク生成のベースとして使用されます。ポート番号を含め、http:// で始まる完全なURLを入力してください(末尾のスラッシュ / は不要です)。", "REPORT_DASHBOARD_URL_name": "NetAlertX URL", "REPORT_ERROR": "お探しのページは一時的に利用できません、数秒後に再度お試しください", - "REPORT_MAIL_description": "有効化すると、購読した変更点のリストが記載されたメールが送信されます。以下のSMTP設定に関連する残りの設定もすべて入力してください。問題が発生した場合は、LOG_LEVELdebugに設定し、エラーログを確認してください。", + "REPORT_MAIL_description": "有効化すると、購読した変更点のリストが記載されたメールが送信されます。以下のSMTP設定に関連する残りの設定もすべて入力してください。問題が発生した場合は、LOG_LEVELdebug に設定し、エラーログ を確認してください。", "REPORT_MAIL_name": "メールを有効化", "REPORT_TITLE": "レポート", "RandomMAC_hover": "このデバイスはランダムなMACアドレスを使用しています", "Reports_Sent_Log": "送信レポートログ", - "SCAN_SUBNETS_description": "ほとんどのネットワーク内スキャナー(ARP-SCAN、NMAP、NSLOOKUP、DIG)は、特定のネットワークインターフェースとサブネットをスキャンすることに依存しています。この設定に関するヘルプについては、サブネットのドキュメントを確認してください。特にVLAN、サポートされているVLANの種類、ネットワークマスクとインターフェースの確認方法についてです。

ネットワーク内スキャナーの代替手段として、NetAlertXがネットワークにアクセスする必要のない他のデバイススキャナー/インポーター(UNIFI、dhcp.leases、PiHoleなど)を有効化できます。

注:スキャン時間自体は確認するIPアドレス数に依存するため、適切なネットワークマスクとインターフェースで慎重に設定してください。", + "SCAN_SUBNETS_description": "ほとんどのネットワーク内スキャナー(ARP-SCAN、NMAP、NSLOOKUP、DIG)は、特定のネットワークインターフェースとサブネットをスキャンすることに依存しています。この設定に関するヘルプについては、サブネットのドキュメント を確認してください。特にVLAN、サポートされているVLANの種類、ネットワークマスクとインターフェースの確認方法についてです。

ネットワーク内スキャナーの代替手段として、NetAlertX がネットワークにアクセスする必要のない他のデバイススキャナー/インポーター(UNIFI、dhcp.leases、PiHoleなど)を有効化できます。

注:スキャン時間自体は確認するIPアドレス数に依存するため、適切なネットワークマスクとインターフェースで慎重に設定してください。", "SCAN_SUBNETS_name": "スキャン対象ネットワーク", "SYSTEM_TITLE": "システム情報", "Setting_Override": "上書き値", @@ -641,7 +641,7 @@ "Settings_Metadata_Toggle": "指定された設定のメタデータを表示/非表示にする。", "Settings_Show_Description": "説明を表示", "Settings_device_Scanners_desync": "⚠デバイススキャナーのスケジュールが同期されていません。", - "Settings_device_Scanners_desync_popup": "デバイススキャナーのスケジュール(*_RUN_SCHD)は同一ではありません。これにより、デバイスのオンライン/オフライン通知に一貫性が生じます。意図的な場合を除き、有効化されているすべての🔍デバイススキャナーで同一のスケジュールを使用してください。", + "Settings_device_Scanners_desync_popup": "デバイススキャナーのスケジュール(*_RUN_SCHD)は同一ではありません。これにより、デバイスのオンライン/オフライン通知に一貫性が生じます。意図的な場合を除き、有効化されているすべての 🔍デバイススキャナー で同一のスケジュールを使用してください。", "Speedtest_Results": "スピードテスト結果", "Systeminfo_AvailableIps": "利用可能なIP", "Systeminfo_CPU": "CPU", @@ -718,22 +718,22 @@ "Systeminfo_System_Uptime": "稼働時間:", "Systeminfo_This_Client": "使用中のクライアント", "Systeminfo_USB_Devices": "USBデバイス", - "TICKER_MIGRATE_TO_NETALERTX": "⚠古いマウント位置が検出されました。新しい/data/configおよび/data/dbフォルダとnetalertxコンテナへの移行については、このガイドに従ってください。", - "TIMEZONE_description": "統計情報を正しく表示するためのタイムゾーン。タイムゾーンはこちらで確認してください。", + "TICKER_MIGRATE_TO_NETALERTX": "⚠古いマウント位置が検出されました。新しい /data/config および /data/db フォルダと netalertx コンテナへの移行については、このガイド に従ってください。", + "TIMEZONE_description": "統計情報を正しく表示するためのタイムゾーン。タイムゾーンは こちら で確認してください。", "TIMEZONE_name": "タイムゾーン", "UI_DEV_SECTIONS_description": "デバイスページで非表示にするUI要素を選択してください。", "UI_DEV_SECTIONS_name": "デバイスセクションを非表示", - "UI_ICONS_description": "事前定義済みアイコンの一覧。注意して操作してください。アイコン追加の推奨方法は、アイコンのドキュメントに記載されています。base64エンコードされたSVG HTMLまたはFont Awesome HTMLタグを追加できます。", + "UI_ICONS_description": "事前定義済みアイコンの一覧。注意して操作してください。アイコン追加の推奨方法は、アイコンのドキュメント に記載されています。base64エンコードされたSVG HTMLまたはFont Awesome HTMLタグを追加できます。", "UI_ICONS_name": "事前定義済みアイコン", - "UI_LANG_description": "お好みのUI言語を選択してください。翻訳のお手伝いや言語の提案は、Weblateのオンラインポータルで行えます。", + "UI_LANG_description": "お好みのUI言語を選択してください。翻訳のお手伝いや言語の提案は、Weblate のオンラインポータルで行えます。", "UI_LANG_name": "UI言語", - "UI_MY_DEVICES_description": "デフォルトのマイデバイスビューに表示すべきデバイスの状態。", + "UI_MY_DEVICES_description": "デフォルトの マイデバイス ビューに表示すべきデバイスの状態。", "UI_MY_DEVICES_name": "自分のデバイスビューに表示", - "UI_NOT_RANDOM_MAC_description": "ランダムデバイスとしてマークすべきでないMACプレフィックス。例えば52と入力すると、52:xx:xx:xx:xx:xxで始まるデバイスがランダムMACアドレスを持つデバイスとしてマークされるのを除外します。", + "UI_NOT_RANDOM_MAC_description": "ランダムデバイスとしてマークすべきでないMACプレフィックス。例えば 52 と入力すると、52:xx:xx:xx:xx:xx で始まるデバイスがランダムMACアドレスを持つデバイスとしてマークされるのを除外します。", "UI_NOT_RANDOM_MAC_name": "ランダムとしてマークしない", - "UI_PRESENCE_description": "デバイスページ内のデバイス状態チャートに表示するステータスを選択してください。", + "UI_PRESENCE_description": "デバイス ページ内の デバイス状態 チャートに表示するステータスを選択してください。", "UI_PRESENCE_name": "検出チャートの表示", - "UI_REFRESH_description": "UIが再読み込みされるまでの秒数を指定します。無効にするには0を設定してください。", + "UI_REFRESH_description": "UIが再読み込みされるまでの秒数を指定します。無効にするには 0 を設定してください。", "UI_REFRESH_name": "UI自動更新", "VERSION_description": "バージョンまたはタイムスタンプヘルパー値で、アプリがアップグレードされたかどうかを確認します。", "VERSION_name": "バージョンまたはタイムスタンプ", @@ -767,13 +767,13 @@ "add_option_event_tooltip": "値の追加", "copy_icons_event_tooltip": "同じ種別の全デバイスにアイコンを上書き", "devices_old": "リフレッシュ中…", - "general_event_description": "トリガーされたイベントは、バックグラウンド処理が完了するまで時間がかかる場合があります。以下の実行キューが空になると処理は終了します(問題が発生した場合はエラーログを確認してください)。

実行キュー:", + "general_event_description": "トリガーされたイベントは、バックグラウンド処理が完了するまで時間がかかる場合があります。以下の実行キューが空になると処理は終了します(問題が発生した場合は エラーログ を確認してください)。

実行キュー:", "general_event_title": "アドホックイベントの実行", "go_to_device_event_tooltip": "デバイスに移動", "go_to_node_event_tooltip": "指定されたノードのネットワークページに移動する", "new_version_available": "新しいバージョンが利用可能です。", - "report_guid": "通知GUID:", - "report_guid_missing": "リンクされた通知が見つかりません。送信された通知が利用可能になるまで、わずかな遅延が生じます。数秒後にページとキャッシュを更新してください。また、DBCLNP_NOTIFI_HIST設定で指定されているメンテナンス中に、選択した通知が削除された可能性もあります。

代わりに最新の通知が表示されます。欠落している通知のGUIDは以下の通りです:", + "report_guid": "通知guid:", + "report_guid_missing": "リンクされた通知が見つかりません。送信された通知が利用可能になるまで、わずかな遅延が生じます。数秒後にページとキャッシュを更新してください。また、DBCLNP_NOTIFI_HIST 設定で指定されているメンテナンス中に、選択した通知が削除された可能性もあります。

代わりに最新の通知が表示されます。欠落している通知のGUIDは以下の通りです:", "report_select_format": "フォーマット選択:", "report_time": "通知時刻:", "run_event_tooltip": "設定を有効にし、実行する前にまず変更を保存してください。", @@ -782,7 +782,7 @@ "settings_core_label": "Core", "settings_device_scanners": "デバイススキャナーは、CurrentScanデータベーステーブルに書き込みを行うデバイスを発見するために使用されます。", "settings_device_scanners_icon": "fa-solid fa-magnifying-glass-plus", - "settings_device_scanners_info": "LOADED_PLUGINS設定でより多くのデバイススキャナーを読み込みます", + "settings_device_scanners_info": "LOADED_PLUGINS 設定でより多くのデバイススキャナーを読み込みます", "settings_device_scanners_label": "デバイススキャナー", "settings_enabled": "有効な設定", "settings_enabled_icon": "fa-solid fa-toggle-on", @@ -797,10 +797,10 @@ "settings_other_scanners_label": "その他のスキャナー", "settings_publishers": "有効化された通知ゲートウェイ - 設定に応じて通知を送信する発行元。", "settings_publishers_icon": "fa-solid fa-paper-plane", - "settings_publishers_info": "LOADED_PLUGINS設定でさらに多くのパブリッシャーを読み込みます", + "settings_publishers_info": "LOADED_PLUGINS 設定でさらに多くのパブリッシャーを読み込みます", "settings_publishers_label": "パブリッシャー", - "settings_readonly": "app.confの読み取りまたは書き込みができません。コンテナを再起動し、ファイルの権限に関するドキュメントを参照してください", - "settings_saved": "
設定が保存されました。
再読込中…

", + "settings_readonly": "app.conf の読み取りまたは書き込みができません。コンテナを再起動し、ファイルの権限に関するドキュメント を参照してください", + "settings_saved": "
設定が保存されました。
再読込中…

", "settings_system_icon": "fa-solid fa-gear", "settings_system_label": "システム", "settings_update_item_warning": "以下の値を更新してください。以前のフォーマットに従うよう注意してください。検証は行われません。", From d7c7bd2cd2944188905ef8df5437ac0fef10a005 Mon Sep 17 00:00:00 2001 From: "Jokob @NetAlertX" <96159884+jokob-sk@users.noreply.github.com> Date: Wed, 18 Mar 2026 09:57:20 +0000 Subject: [PATCH 008/189] Enhance SQL templates to prevent duplicate notifications for 'Down Reconnected' devices in event section --- server/messaging/notification_sections.py | 17 ++++++++++++----- server/messaging/reporting.py | 13 +++++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/server/messaging/notification_sections.py b/server/messaging/notification_sections.py index f88821b2..031d54b2 100644 --- a/server/messaging/notification_sections.py +++ b/server/messaging/notification_sections.py @@ -83,15 +83,22 @@ SQL_TEMPLATES = { "down_reconnected": """ SELECT devName, - eveMac, + reconnected_devices.eveMac, devVendor, - eveIp, - eveDateTime, - eveEventType, + reconnected_devices.eveIp, + reconnected_devices.eveDateTime, + reconnected_devices.eveEventType, devComments FROM Events_Devices AS reconnected_devices WHERE reconnected_devices.eveEventType = 'Down Reconnected' AND reconnected_devices.evePendingAlertEmail = 1 + AND NOT EXISTS ( + SELECT 1 FROM Events AS newer + WHERE newer.eveMac = reconnected_devices.eveMac + AND newer.eveEventType = 'Down Reconnected' + AND newer.evePendingAlertEmail = 1 + AND newer.eveDateTime > reconnected_devices.eveDateTime + ) ORDER BY reconnected_devices.eveDateTime """, "events": """ @@ -105,7 +112,7 @@ SQL_TEMPLATES = { devComments FROM Events_Devices WHERE evePendingAlertEmail = 1 - AND eveEventType IN ('Connected', 'Down Reconnected', 'Disconnected','IP Changed') {condition} + AND eveEventType IN ({event_types}) {condition} ORDER BY eveDateTime """, "plugins": """ diff --git a/server/messaging/reporting.py b/server/messaging/reporting.py index 45a37453..dfe9f087 100755 --- a/server/messaging/reporting.py +++ b/server/messaging/reporting.py @@ -198,6 +198,14 @@ def get_notifications(db): format_vars = {"condition": safe_condition} if section == "down_devices": format_vars["alert_down_minutes"] = alert_down_minutes + if section == "events": + # 'Down Reconnected' has its own dedicated section; exclude it + # from events when that section is also active to prevent the + # same device appearing twice with different IP sources. + if "down_reconnected" in sections: + format_vars["event_types"] = "'Connected', 'Disconnected','IP Changed'" + else: + format_vars["event_types"] = "'Connected', 'Down Reconnected', 'Disconnected','IP Changed'" sqlQuery = template.format(**format_vars) except Exception as e: @@ -205,6 +213,11 @@ def get_notifications(db): fallback_vars = {"condition": ""} if section == "down_devices": fallback_vars["alert_down_minutes"] = alert_down_minutes + if section == "events": + if "down_reconnected" in sections: + fallback_vars["event_types"] = "'Connected', 'Disconnected','IP Changed'" + else: + fallback_vars["event_types"] = "'Connected', 'Down Reconnected', 'Disconnected','IP Changed'" sqlQuery = template.format(**fallback_vars) parameters = {} From 4a81a4594c4e6f7c6c65abd736eb3f1083190f77 Mon Sep 17 00:00:00 2001 From: Safeguard Date: Thu, 19 Mar 2026 18:35:13 +0100 Subject: [PATCH 009/189] Translated using Weblate (Russian) Currently translated at 100.0% (806 of 806 strings) Translation: NetAlertX/core Translate-URL: https://hosted.weblate.org/projects/pialert/core/ru/ --- front/php/templates/language/ru_ru.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/php/templates/language/ru_ru.json b/front/php/templates/language/ru_ru.json index 22e67343..502f2a29 100644 --- a/front/php/templates/language/ru_ru.json +++ b/front/php/templates/language/ru_ru.json @@ -208,7 +208,7 @@ "Device_NoData_Help": "Если устройства не отображаются после сканирования, проверьте настройку SCAN_SUBNETS и документацию.", "Device_NoData_Scanning": "Ожидание первого сканирования — это может занять несколько минут после первоначальной настройки.", "Device_NoData_Title": "Устройства пока не найдены", - "Device_NoMatch_Title": "", + "Device_NoMatch_Title": "Нет устройств, соответствующих текущему фильтру", "Device_Save_Failed": "Не удалось сохранить устройство", "Device_Save_Unauthorized": "Не авторизован - недействительный токен API", "Device_Saved_Success": "Устройство успешно сохранено", From 7569923481aed30deaab513c428bdb36a8d82a3f Mon Sep 17 00:00:00 2001 From: "Jokob @NetAlertX" <96159884+jokob-sk@users.noreply.github.com> Date: Sat, 21 Mar 2026 20:55:24 +0000 Subject: [PATCH 010/189] Refactor column name replacements to include variations for ObjectPrimaryID and ObjectSecondaryID --- server/initialise.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/initialise.py b/server/initialise.py index 740d7cef..a7aa1883 100755 --- a/server/initialise.py +++ b/server/initialise.py @@ -875,8 +875,10 @@ _column_replacements = { # Plugin columns (templates + WATCH values) r"\bObject_PrimaryID\b": "objectPrimaryId", r"\bObject_PrimaryId\b": "objectPrimaryId", + r"\bObjectPrimaryID\b": "objectPrimaryId", r"\bObject_SecondaryID\b": "objectSecondaryId", r"\bObject_SecondaryId\b": "objectSecondaryId", + r"\bObjectSecondaryID\b": "objectSecondaryId", r"\bWatched_Value1\b": "watchedValue1", r"\bWatched_Value2\b": "watchedValue2", r"\bWatched_Value3\b": "watchedValue3", From fa22523a0bffd83cf1feeaaab22acef9d89292d7 Mon Sep 17 00:00:00 2001 From: "Jokob @NetAlertX" <96159884+jokob-sk@users.noreply.github.com> Date: Sat, 21 Mar 2026 21:10:37 +0000 Subject: [PATCH 011/189] Refactor device tiles SQL logic to use get_sql_devices_tiles function for improved maintainability Feature Request - Flapping and Sleeping nuances Fixes #1567 --- server/api.py | 4 ++-- server/const.py | 35 ------------------------------ server/db/db_helper.py | 49 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 37 deletions(-) diff --git a/server/api.py b/server/api.py index 71d226ba..2ed27e37 100755 --- a/server/api.py +++ b/server/api.py @@ -18,9 +18,9 @@ from const import ( sql_language_strings, sql_notifications_all, sql_online_history, - sql_devices_tiles, sql_devices_filters, ) +from db.db_helper import get_sql_devices_tiles from logger import mylog from helper import write_file, get_setting_value from utils.datetime_utils import timeNowUTC @@ -67,7 +67,7 @@ def update_api( ["plugins_language_strings", sql_language_strings], ["notifications", sql_notifications_all], ["online_history", sql_online_history], - ["devices_tiles", sql_devices_tiles], + ["devices_tiles", get_sql_devices_tiles()], ["devices_filters", sql_devices_filters], ["custom_endpoint", conf.API_CUSTOM_SQL], ] diff --git a/server/const.py b/server/const.py index 135102ec..e07eda06 100755 --- a/server/const.py +++ b/server/const.py @@ -68,41 +68,6 @@ sql_devices_all = """ """ sql_appevents = """select * from AppEvents order by dateTimeCreated desc""" -# The below query calculates counts of devices in various categories: -# (connected/online, offline, down, new, archived), -# as well as a combined count for devices that match any status listed in the UI_MY_DEVICES setting -sql_devices_tiles = """ - WITH Statuses AS ( - SELECT setValue - FROM Settings - WHERE setKey = 'UI_MY_DEVICES' - ), - MyDevicesFilter AS ( - SELECT - -- Build a dynamic filter for devices matching any status in UI_MY_DEVICES - devPresentLastScan, devAlertDown, devIsNew, devIsArchived - FROM Devices - WHERE - (instr((SELECT setValue FROM Statuses), 'online') > 0 AND devPresentLastScan = 1) OR - (instr((SELECT setValue FROM Statuses), 'offline') > 0 AND devPresentLastScan = 0 AND devIsArchived = 0) OR - (instr((SELECT setValue FROM Statuses), 'down') > 0 AND devPresentLastScan = 0 AND devAlertDown = 1) OR - (instr((SELECT setValue FROM Statuses), 'new') > 0 AND devIsNew = 1) OR - (instr((SELECT setValue FROM Statuses), 'archived') > 0 AND devIsArchived = 1) - ) - SELECT - -- Counts for each individual status - (SELECT COUNT(*) FROM Devices WHERE devPresentLastScan = 1) AS connected, - (SELECT COUNT(*) FROM Devices WHERE devPresentLastScan = 0) AS offline, - (SELECT COUNT(*) FROM Devices WHERE devPresentLastScan = 0 AND devAlertDown = 1) AS down, - (SELECT COUNT(*) FROM Devices WHERE devIsNew = 1) AS new, - (SELECT COUNT(*) FROM Devices WHERE devIsArchived = 1) AS archived, - (SELECT COUNT(*) FROM Devices WHERE devFavorite = 1) AS favorites, - (SELECT COUNT(*) FROM Devices) AS "all", - (SELECT COUNT(*) FROM Devices) AS "all_devices", - -- My Devices count - (SELECT COUNT(*) FROM MyDevicesFilter) AS my_devices - FROM Statuses; - """ sql_devices_filters = """ SELECT DISTINCT 'devSite' AS columnName, devSite AS columnValue FROM Devices WHERE devSite NOT IN ('', 'null') AND devSite IS NOT NULL diff --git a/server/db/db_helper.py b/server/db/db_helper.py index a962ef2d..c828de00 100755 --- a/server/db/db_helper.py +++ b/server/db/db_helper.py @@ -66,6 +66,55 @@ def get_device_condition_by_status(device_status): return get_device_conditions().get(device_status, "WHERE 1=0") +# ------------------------------------------------------------------------------- +def get_sql_devices_tiles(): + """Build the device tiles count SQL using get_device_conditions() to avoid duplicating filter logic.""" + conds = get_device_conditions() + + def f(key): + """Strip 'WHERE ' prefix for use inside SELECT subqueries.""" + return conds[key][len("WHERE "):] + + # UI_MY_DEVICES setting values mapped to their device_conditions keys + my_devices_setting_map = [ + ("online", "connected"), + ("offline", "offline"), + ("down", "down"), + ("new", "new"), + ("archived", "archived"), + ] + + my_devices_clauses = "\n OR ".join( + f"(instr((SELECT setValue FROM Statuses), '{sk}') > 0 AND {f(ck)})" + for sk, ck in my_devices_setting_map + ) + + return f""" + WITH Statuses AS ( + SELECT setValue + FROM Settings + WHERE setKey = 'UI_MY_DEVICES' + ), + MyDevicesFilter AS ( + SELECT devMac + FROM Devices + WHERE + {my_devices_clauses} + ) + SELECT + (SELECT COUNT(*) FROM Devices WHERE {f('connected')}) AS connected, + (SELECT COUNT(*) FROM Devices WHERE {f('offline')}) AS offline, + (SELECT COUNT(*) FROM Devices WHERE {f('down')}) AS down, + (SELECT COUNT(*) FROM Devices WHERE {f('new')}) AS new, + (SELECT COUNT(*) FROM Devices WHERE {f('archived')}) AS archived, + (SELECT COUNT(*) FROM Devices WHERE {f('favorites')}) AS favorites, + (SELECT COUNT(*) FROM Devices WHERE {f('all')}) AS "all", + (SELECT COUNT(*) FROM Devices) AS "all_devices", + (SELECT COUNT(*) FROM MyDevicesFilter) AS my_devices + FROM Statuses; + """ + + # ------------------------------------------------------------------------------- # Creates a JSON-like dictionary from a database row def row_to_json(names, row): From 7278ee8cfa20e7c51f02361749eb771e6ee524a4 Mon Sep 17 00:00:00 2001 From: "Jokob @NetAlertX" <96159884+jokob-sk@users.noreply.github.com> Date: Sat, 21 Mar 2026 21:28:42 +0000 Subject: [PATCH 012/189] Refactor getTotals method to clarify API contract and ensure stable response structure #1569 #1561 --- server/models/device_instance.py | 28 +++++++++++++------- test/api_endpoints/test_devices_endpoints.py | 19 ++++++------- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/server/models/device_instance.py b/server/models/device_instance.py index 1384a75f..cc8d9d89 100755 --- a/server/models/device_instance.py +++ b/server/models/device_instance.py @@ -327,20 +327,30 @@ class DeviceInstance: return {"success": True, "inserted": row_count, "skipped_lines": skipped} def getTotals(self): - """Get device totals by status.""" + """Get device totals by status. + + Returns a list of 6 counts in the documented positional order: + [all, connected, favorites, new, down, archived] + + IMPORTANT: This order is a public API contract consumed by: + - presence.php (reads indices 0-5) + - /devices/totals/named (maps indices 0-5 to named fields) + - homepage widget datav2 (reads /devices/totals indices) + DO NOT change the order or add/remove fields without a breaking-change release. + """ conn = get_temp_db_connection() sql = conn.cursor() - conditions = get_device_conditions() + all_conditions = get_device_conditions() - # Build sub-selects dynamically for all dictionary entries - sub_queries = [] - for key, condition in conditions.items(): - # Make sure the alias is SQL-safe (no spaces or special chars) - alias = key.replace(" ", "_").lower() - sub_queries.append(f'(SELECT COUNT(*) FROM DevicesView {condition}) AS "{alias}"') + # Only the 6 public fields, in documented positional order. + # DO NOT change this order — it is a stable API contract. + keys = ["all", "connected", "favorites", "new", "down", "archived"] + sub_queries = [ + f'(SELECT COUNT(*) FROM DevicesView {all_conditions[key]}) AS "{key}"' + for key in keys + ] - # Join all sub-selects with commas query = "SELECT\n " + ",\n ".join(sub_queries) sql.execute(query) row = sql.fetchone() diff --git a/test/api_endpoints/test_devices_endpoints.py b/test/api_endpoints/test_devices_endpoints.py index 62a3725c..6517feb6 100644 --- a/test/api_endpoints/test_devices_endpoints.py +++ b/test/api_endpoints/test_devices_endpoints.py @@ -8,7 +8,6 @@ import pytest from helper import get_setting_value from api_server.api_server_start import app -from db.db_helper import get_device_conditions @pytest.fixture(scope="session") @@ -163,17 +162,15 @@ def test_devices_totals(client, api_token, test_mac): data = resp.json assert isinstance(data, list) - # 3. Dynamically get expected length - conditions = get_device_conditions() - expected_length = len(conditions) - assert len(data) == expected_length + # 3. Verify the response has exactly 6 elements in documented order: + # [all, connected, favorites, new, down, archived] + expected_length = 6 + assert len(data) == expected_length, ( + f"Expected 6 totals (all, connected, favorites, new, down, archived), got {len(data)}" + ) - # 4. Check that at least 1 device exists when there are any conditions - if expected_length > 0: - assert data[0] >= 1 # 'devices' count includes the dummy device - else: - # no conditions defined; data should be an empty list - assert data == [] + # 4. Check that at least 1 device exists (all count includes the dummy device) + assert data[0] >= 1 # index 0 = 'all' finally: delete_dummy(client, api_token, test_mac) From 37730301f4a13186d23d621e34c9aaf00d9cc7bc Mon Sep 17 00:00:00 2001 From: jokob-sk Date: Mon, 23 Mar 2026 09:55:45 +1100 Subject: [PATCH 013/189] BE: lazy SQL execution caused devIsSleeping to be missing and tiles not show #1569 #1250 Signed-off-by: jokob-sk --- server/db/db_helper.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/server/db/db_helper.py b/server/db/db_helper.py index c828de00..16e172e0 100755 --- a/server/db/db_helper.py +++ b/server/db/db_helper.py @@ -96,20 +96,20 @@ def get_sql_devices_tiles(): WHERE setKey = 'UI_MY_DEVICES' ), MyDevicesFilter AS ( - SELECT devMac - FROM Devices + SELECT devMac, devIsSleeping + FROM DevicesView WHERE {my_devices_clauses} ) SELECT - (SELECT COUNT(*) FROM Devices WHERE {f('connected')}) AS connected, - (SELECT COUNT(*) FROM Devices WHERE {f('offline')}) AS offline, - (SELECT COUNT(*) FROM Devices WHERE {f('down')}) AS down, - (SELECT COUNT(*) FROM Devices WHERE {f('new')}) AS new, - (SELECT COUNT(*) FROM Devices WHERE {f('archived')}) AS archived, - (SELECT COUNT(*) FROM Devices WHERE {f('favorites')}) AS favorites, - (SELECT COUNT(*) FROM Devices WHERE {f('all')}) AS "all", - (SELECT COUNT(*) FROM Devices) AS "all_devices", + (SELECT COUNT(*) FROM DevicesView WHERE {f('connected')}) AS connected, + (SELECT COUNT(*) FROM DevicesView WHERE {f('offline')}) AS offline, + (SELECT COUNT(*) FROM DevicesView WHERE {f('down')}) AS down, + (SELECT COUNT(*) FROM DevicesView WHERE {f('new')}) AS new, + (SELECT COUNT(*) FROM DevicesView WHERE {f('archived')}) AS archived, + (SELECT COUNT(*) FROM DevicesView WHERE {f('favorites')}) AS favorites, + (SELECT COUNT(*) FROM DevicesView WHERE {f('all')}) AS "all", + (SELECT COUNT(*) FROM DevicesView) AS "all_devices", (SELECT COUNT(*) FROM MyDevicesFilter) AS my_devices FROM Statuses; """ From 2bcb77f29348a79cbb5964f3d742a30e36245282 Mon Sep 17 00:00:00 2001 From: Sylvain Pichon Date: Sun, 22 Mar 2026 07:38:19 +0100 Subject: [PATCH 014/189] Translated using Weblate (French) Currently translated at 99.7% (804 of 806 strings) Translation: NetAlertX/core Translate-URL: https://hosted.weblate.org/projects/pialert/core/fr/ --- front/php/templates/language/fr_fr.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/front/php/templates/language/fr_fr.json b/front/php/templates/language/fr_fr.json index 6c9cbd39..601446c0 100644 --- a/front/php/templates/language/fr_fr.json +++ b/front/php/templates/language/fr_fr.json @@ -208,7 +208,7 @@ "Device_NoData_Help": "Si les appareils n'apparaissent pas après le scan, vérifiez vos paramètres SCAN_SUBNETS et la documentation.", "Device_NoData_Scanning": "En attente du premier scan - cela peut prendre quelques minutes après le premier paramétrage.", "Device_NoData_Title": "Aucun appareil trouvé pour le moment", - "Device_NoMatch_Title": "", + "Device_NoMatch_Title": "Aucun appareil ne correspond au filtre actuel", "Device_Save_Failed": "Erreur à l'enregistrement de l'appareil", "Device_Save_Unauthorized": "Non autorisé - Jeton d'API invalide", "Device_Saved_Success": "Appareil enregistré avec succès", @@ -232,7 +232,7 @@ "Device_TableHead_FQDN": "Nom de domaine FQDN", "Device_TableHead_Favorite": "Favori", "Device_TableHead_FirstSession": "Première session", - "Device_TableHead_Flapping": "", + "Device_TableHead_Flapping": "Flapping", "Device_TableHead_GUID": "GUID", "Device_TableHead_Group": "Groupe", "Device_TableHead_IPv4": "IPv4", @@ -337,7 +337,7 @@ "Gen_Down": "En panne", "Gen_Error": "Erreur", "Gen_Filter": "Filtrer", - "Gen_Flapping": "", + "Gen_Flapping": "Flapping", "Gen_Generate": "Générer", "Gen_InvalidMac": "Adresse MAC invalide.", "Gen_Invalid_Value": "Une valeur invalide a été renseignée", @@ -591,8 +591,8 @@ "PIALERT_WEB_PROTECTION_name": "Activer la connexion par login", "PLUGINS_KEEP_HIST_description": "Combien d'entrées de résultats de scan doivent être conservés dans l'historique des plugins (par plugin, pas par appareil).", "PLUGINS_KEEP_HIST_name": "Historique des plugins", - "PRAGMA_JOURNAL_SIZE_LIMIT_description": "", - "PRAGMA_JOURNAL_SIZE_LIMIT_name": "", + "PRAGMA_JOURNAL_SIZE_LIMIT_description": "Taille maximale du SQLite WAL (Write-Ahead Log) en Mo avant le déclenchement automatique des points de contrôle. Des valeurs basses (10-20 Mo) réduisent l'utilisation du disque/stockage mais augmentent l'utilisation du CPU durant ces scans. Des valeurs élevées (50-100 Mo) réduisent les pics CPU durant les opérations mais peuvent utiliser plus de RAM et d'espace disque. Par défaut, 50 Mo est un compromis entre ces 2. Utilise pour les systèmes à ressources limitées comme des NAS avec des cartes SD. Redémarrer le serveur pour que le changement soit effective après avoir sauvegardé ce paramètre.", + "PRAGMA_JOURNAL_SIZE_LIMIT_name": "Limite de taille du WAL (Mo)", "Plugins_DeleteAll": "Tout supprimer (ne prend pas en compte les filtres)", "Plugins_Filters_Mac": "Filtrer par MAC", "Plugins_History": "Historique des événements", From 250e5336558b5ca342d2e15b2dec7261cc350296 Mon Sep 17 00:00:00 2001 From: jokob-sk Date: Wed, 25 Mar 2026 06:26:57 +1100 Subject: [PATCH 015/189] DOCS: pin mkdocs version Signed-off-by: jokob-sk --- .github/workflows/mkdocs.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/mkdocs.yml b/.github/workflows/mkdocs.yml index ab1ca052..64bc73d8 100755 --- a/.github/workflows/mkdocs.yml +++ b/.github/workflows/mkdocs.yml @@ -22,8 +22,10 @@ jobs: - name: Install MkDocs run: | - pip install mkdocs mkdocs-material - pip install mkdocs-github-admonitions-plugin + pip install \ + mkdocs==1.6.0 \ + mkdocs-material==9.5.21 \ + mkdocs-github-admonitions-plugin==0.0.4 - name: Build MkDocs run: mkdocs build From 84eb4d2c92a89e9edab230efd6d007e2f463fbae Mon Sep 17 00:00:00 2001 From: jokob-sk Date: Wed, 25 Mar 2026 06:43:06 +1100 Subject: [PATCH 016/189] DOCS: pin mkdocs version Signed-off-by: jokob-sk --- .github/workflows/mkdocs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/mkdocs.yml b/.github/workflows/mkdocs.yml index 64bc73d8..c67a00bd 100755 --- a/.github/workflows/mkdocs.yml +++ b/.github/workflows/mkdocs.yml @@ -18,14 +18,14 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.9' + python-version: '3.11' - name: Install MkDocs run: | pip install \ mkdocs==1.6.0 \ mkdocs-material==9.5.21 \ - mkdocs-github-admonitions-plugin==0.0.4 + mkdocs-github-admonitions-plugin==0.1.1 - name: Build MkDocs run: mkdocs build From 21f6a538304b0ba5c1f7a25b4737257e483bfba5 Mon Sep 17 00:00:00 2001 From: Alvise Bruniera Date: Thu, 26 Mar 2026 16:03:45 +0100 Subject: [PATCH 017/189] freebox plugin version 2 --- front/plugins/freebox/README.md | 16 +-------- front/plugins/freebox/freebox.py | 34 ++++++++++--------- .../aiofreebox/freebox_certificate.pem | 15 -------- requirements.txt | 2 +- 4 files changed, 20 insertions(+), 47 deletions(-) delete mode 100755 install/production-filesystem/opt/venv/lib/python3.12/site-packages/aiofreebox/freebox_certificate.pem diff --git a/front/plugins/freebox/README.md b/front/plugins/freebox/README.md index 527ad047..e6e84722 100755 --- a/front/plugins/freebox/README.md +++ b/front/plugins/freebox/README.md @@ -28,31 +28,17 @@ Limitations: - The Freebox must be your gateway - The device must be in the same lan as the Freebox -### Offline setup (recommended) - -Use this configuration if you wish to connect to your Freebox even when you are offline, or the Freebox is not your gateway. - -Find the local IP address of your Freebox, if it is your gateway, you can find the address on your computer/smartphone network configuration (usually it's `192.168.1.1`). Go in the plugin settings and set the IP as address and `80` as the port (do *not* use `443` as the port). This configuration works regardless of your internet connection and poses little limitations. - -Limitations: -- *If* there is no internet connection, the plugin will fallback to HTTP (not HTTPS) - -For more detail: the plugin will connect to the specified address and port to fetch information about the Freebox, then it will either connect in HTTPS through the Freebox's unique domain name, or connect over HTTP if there is no internet connection. The freebox does offer an HTTPS port on the local network, but the certificate will be invalid for the local IP, and the connection will be aborted. - ### Remote setup Use this configuration if you wish to connect to your Freebox through the internet. You still need to pair from the local network. -If the Freebox is not your gateway, configure a NAT and follow the [offline setup](#offline-setup-recommended). - If the Freebox is your gateway you need to find its HTTPS (or HTTP if you prefer) public port. This can be found either in the Freeboxe's web interface and by navigating to `settings>access management`, or (just for the HTTPS port) by visiting http://mafreebox.freebox.fr:80/api_version from the local network (you can use the local ip as well). This is the port you need to access your Freebox through the internet As address, you can either use the public IP of the Freebox, or the unique domain name you found on http://mafreebox.freebox.fr:80/api_version listed as `api_domain`. - ## Other info -- Version: 1.0 +- Version: 2.0 - Author: [KayJay7](https://github.com/KayJay7), [Lucide](https://github.com/Lucide) - Maintainers: [mathoudebine](https://github.com/mathoudebine) - Release Date: 2-Dec-2024 \ No newline at end of file diff --git a/front/plugins/freebox/freebox.py b/front/plugins/freebox/freebox.py index eb81eb80..a31fe10c 100755 --- a/front/plugins/freebox/freebox.py +++ b/front/plugins/freebox/freebox.py @@ -8,10 +8,11 @@ from datetime import datetime from pathlib import Path from typing import cast import socket -import aiofreepybox -from aiofreepybox import Freepybox -from aiofreepybox.api.lan import Lan -from aiofreepybox.exceptions import NotOpenError, AuthorizationError +import freebox_api +from freebox_api import Freepybox +from freebox_api.api.lan import Lan +from freebox_api.api.system import System +from freebox_api.exceptions import NotOpenError, AuthorizationError # Define the installation path and extend the system path for plugin imports INSTALL_PATH = os.getenv('NETALERTX_APP', '/app') @@ -83,8 +84,7 @@ def map_device_type(type: str): async def get_device_data(api_version: int, api_address: str, api_port: int): # ensure existence of db path - config_base = Path(os.getenv("NETALERTX_CONFIG", "/data/config")) - data_dir = config_base / "freeboxdb" + data_dir = Path(os.getenv("NETALERTX_CONFIG", "/data/config")) / "freeboxdb" data_dir.mkdir(parents=True, exist_ok=True) # Instantiate Freepybox class using default application descriptor @@ -93,25 +93,27 @@ async def get_device_data(api_version: int, api_address: str, api_port: int): app_desc={ "app_id": "netalertx", "app_name": "NetAlertX", - "app_version": aiofreepybox.__version__, + "app_version": freebox_api.__version__, "device_name": socket.gethostname(), }, api_version="v" + str(api_version), - data_dir=data_dir, + token_file=data_dir / "token", ) # Connect to the freebox # Be ready to authorize the application on the Freebox if you run this # for the first time try: - await fbx.open(host=api_address, port=api_port) + await fbx.open(host=api_address, port=str(api_port)) except NotOpenError as e: mylog("verbose", [f"[{pluginName}] Error connecting to freebox: {e}"]) + return (),() except AuthorizationError as e: mylog("verbose", [f"[{pluginName}] Auth error: {str(e)}"]) + return (),() # get also info of the freebox itself - config = await fbx.system.get_config() + config = await cast(System, fbx.system).get_config() freebox = await cast(Lan, fbx.lan).get_config() hosts = await cast(Lan, fbx.lan).get_hosts_list() assert config is not None @@ -146,14 +148,14 @@ def main(): mylog("verbose", [hosts]) plugin_objects.add_object( - primaryId=freebox["mac"], - secondaryId=freebox["ip"], - watched1=freebox["name"], - watched2=freebox["operator"], + primaryId=freebox["mac"], # type: ignore + secondaryId=freebox["ip"], # type: ignore + watched1=freebox["name"], # type: ignore + watched2=freebox["operator"], # type: ignore watched3="Gateway", watched4=timeNowUTC(), extra="", - foreignKey=freebox["mac"], + foreignKey=freebox["mac"], # type: ignore ) for host in hosts: # Check if 'l3connectivities' exists and is a list @@ -175,7 +177,7 @@ def main(): # Optional: Log or handle hosts without 'l3connectivities' mylog("verbose", [f"[{pluginName}] Host missing 'l3connectivities': {host}"]) - # commit result + # Commit result plugin_objects.write_result_file() return 0 diff --git a/install/production-filesystem/opt/venv/lib/python3.12/site-packages/aiofreebox/freebox_certificate.pem b/install/production-filesystem/opt/venv/lib/python3.12/site-packages/aiofreebox/freebox_certificate.pem deleted file mode 100755 index 1a329f07..00000000 --- a/install/production-filesystem/opt/venv/lib/python3.12/site-packages/aiofreebox/freebox_certificate.pem +++ /dev/null @@ -1,15 +0,0 @@ - ------BEGIN CERTIFICATE----- -MIICOjCCAcCgAwIBAgIUI0Tu7zsrBJACQIZgLMJobtbdNn4wCgYIKoZIzj0EAwIw -TDELMAkGA1UEBhMCSVQxDjAMBgNVBAgMBUl0YWx5MQ4wDAYDVQQKDAVJbGlhZDEd -MBsGA1UEAwwUSWxpYWRib3ggRUNDIFJvb3QgQ0EwHhcNMjAxMTI3MDkzODEzWhcN -NDAxMTIyMDkzODEzWjBMMQswCQYDVQQGEwJJVDEOMAwGA1UECAwFSXRhbHkxDjAM -BgNVBAoMBUlsaWFkMR0wGwYDVQQDDBRJbGlhZGJveCBFQ0MgUm9vdCBDQTB2MBAG -ByqGSM49AgEGBSuBBAAiA2IABMryJyb2loHNAioY8IztN5MI3UgbVHVP/vZwcnre -ZvJOyDvE4HJgIti5qmfswlnMzpNbwf/MkT+7HAU8jJoTorRm1wtAnQ9cWD3Ebv79 -RPwtjjy3Bza3SgdVxmd6fWPUKaNjMGEwHQYDVR0OBBYEFDUij/4lpoJ+kOXRyrcM -jf2RPzOqMB8GA1UdIwQYMBaAFDUij/4lpoJ+kOXRyrcMjf2RPzOqMA8GA1UdEwEB -/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQC6eUV1 -pFh4UpJOTc1JToztN4ttnQR6rIzxMZ6mNCe+nhjkohWp24pr7BpUYSbEizYCMAQ6 -LCiBKV2j7QQGy7N1aBmdur17ZepYzR1YV0eI+Kd978aZggsmhjXENQYVTmm/XA== ------END CERTIFICATE----- diff --git a/requirements.txt b/requirements.txt index 9d03b87b..f713720d 100755 --- a/requirements.txt +++ b/requirements.txt @@ -30,7 +30,7 @@ six urllib3 httplib2 gunicorn -git+https://github.com/foreign-sub/aiofreepybox.git +freebox-api mcp psutil pydantic>=2.0,<3.0 From e30bdc526b450937a358ec2da55ce67a07f8d83f Mon Sep 17 00:00:00 2001 From: Alvise Bruniera Date: Thu, 26 Mar 2026 17:14:56 +0100 Subject: [PATCH 018/189] updated freebox requirements --- front/plugins/freebox/freebox.py | 25 +++++++++++++------------ install/proxmox/requirements.txt | 2 +- install/ubuntu24/requirements.txt | 2 +- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/front/plugins/freebox/freebox.py b/front/plugins/freebox/freebox.py index a31fe10c..02bdc7f6 100755 --- a/front/plugins/freebox/freebox.py +++ b/front/plugins/freebox/freebox.py @@ -107,10 +107,10 @@ async def get_device_data(api_version: int, api_address: str, api_port: int): await fbx.open(host=api_address, port=str(api_port)) except NotOpenError as e: mylog("verbose", [f"[{pluginName}] Error connecting to freebox: {e}"]) - return (),() + return None, [] except AuthorizationError as e: mylog("verbose", [f"[{pluginName}] Auth error: {str(e)}"]) - return (),() + return None, [] # get also info of the freebox itself config = await cast(System, fbx.system).get_config() @@ -147,16 +147,17 @@ def main(): mylog("verbose", [freebox]) mylog("verbose", [hosts]) - plugin_objects.add_object( - primaryId=freebox["mac"], # type: ignore - secondaryId=freebox["ip"], # type: ignore - watched1=freebox["name"], # type: ignore - watched2=freebox["operator"], # type: ignore - watched3="Gateway", - watched4=timeNowUTC(), - extra="", - foreignKey=freebox["mac"], # type: ignore - ) + if freebox: + plugin_objects.add_object( + primaryId=freebox["mac"], + secondaryId=freebox["ip"], + watched1=freebox["name"], + watched2=freebox["operator"], + watched3="Gateway", + watched4=timeNowUTC(), + extra="", + foreignKey=freebox["mac"], + ) for host in hosts: # Check if 'l3connectivities' exists and is a list if "l3connectivities" in host and isinstance(host["l3connectivities"], list): diff --git a/install/proxmox/requirements.txt b/install/proxmox/requirements.txt index 6948ea66..2c858b95 100755 --- a/install/proxmox/requirements.txt +++ b/install/proxmox/requirements.txt @@ -24,4 +24,4 @@ librouteros yattag zeroconf psutil -git+https://github.com/foreign-sub/aiofreepybox.git +freebox-api diff --git a/install/ubuntu24/requirements.txt b/install/ubuntu24/requirements.txt index 6948ea66..2c858b95 100755 --- a/install/ubuntu24/requirements.txt +++ b/install/ubuntu24/requirements.txt @@ -24,4 +24,4 @@ librouteros yattag zeroconf psutil -git+https://github.com/foreign-sub/aiofreepybox.git +freebox-api From ec3e4c8988b6c53f0043e4d35d428727857253d2 Mon Sep 17 00:00:00 2001 From: "Jokob @NetAlertX" <96159884+jokob-sk@users.noreply.github.com> Date: Thu, 26 Mar 2026 20:57:10 +0000 Subject: [PATCH 019/189] feat(api): Enhance session events API with pagination, sorting, and filtering - Added support for pagination (page and limit) in the session events endpoint. - Implemented sorting functionality based on specified columns and directions. - Introduced free-text search capability for session events. - Updated SQL queries to retrieve all events and added a new SQL constant for events. - Refactored GraphQL types and helpers to support new plugin and event queries. - Created new GraphQL resolvers for plugins and events with pagination and filtering. - Added comprehensive tests for new GraphQL endpoints and session events functionality. --- CONTRIBUTING.md | 27 +- docs/API_GRAPHQL.md | 157 +++++++++- docs/API_SESSIONS.md | 22 +- front/deviceDetailsEvents.php | 71 +++-- front/events.php | 111 ++++--- front/pluginsCore.php | 287 ++++++++++++------ server/api.py | 2 + server/api_server/api_server_start.py | 16 +- server/api_server/graphql_endpoint.py | 257 ++++++---------- server/api_server/graphql_helpers.py | 140 +++++++++ server/api_server/graphql_types.py | 261 ++++++++++++++++ server/api_server/sessions_endpoint.py | 36 ++- server/const.py | 1 + test/api_endpoints/test_graphq_endpoints.py | 239 +++++++++++++++ test/api_endpoints/test_sessions_endpoints.py | 37 +++ 15 files changed, 1312 insertions(+), 352 deletions(-) create mode 100644 server/api_server/graphql_helpers.py create mode 100644 server/api_server/graphql_types.py diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4f108749..67256a3d 100755 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,6 +15,21 @@ Before opening a new issue: - [Check Common Issues & Debug Tips](https://docs.netalertx.com/DEBUG_TIPS#common-issues) - [Search Closed Issues](https://github.com/netalertx/NetAlertX/issues?q=is%3Aissue+is%3Aclosed) +--- + +## Use of AI + +Use of AI-assisted tools is permitted, provided all generated code is reviewed, understood, and verified before submission. + +- All AI-generated code must meet the project's **quality, security, and performance standards**. +- Contributors are responsible for **fully understanding** any code they submit, regardless of how it was produced. +- Prefer **clarity and maintainability over cleverness or brevity**. Readable code is always favored over dense or obfuscated implementations. +- Follow the **DRY (Don't Repeat Yourself) principle** where appropriate, without sacrificing readability. +- Do not submit code that you cannot confidently explain or debug. + +All changes must pass the **full test suite** before opening a PR. + + --- ## Submitting Pull Requests (PRs) @@ -28,11 +43,19 @@ Please: - Provide a clear title and description for your PR - If relevant, add or update tests and documentation - For plugins, refer to the [Plugin Dev Guide](https://docs.netalertx.com/PLUGINS_DEV) +- Switch the PR to DRAFT mode if still being worked on +- Keep PRs **focused and minimal** — avoid unrelated changes in a single PR +- PRs that do not meet these guidelines may be closed without review +## Commit Messages -## Code quality +- Use clear, descriptive commit messages +- Explain *why* a change was made, not just *what* changed +- Reference related issues where applicable -- read and follow the [code-standards](/.github/skills/code-standards/SKILL.md) +## Code Quality + +- Read and follow the [code standards](/.github/skills/code-standards/SKILL.md) --- diff --git a/docs/API_GRAPHQL.md b/docs/API_GRAPHQL.md index e7ccfd10..cd176f57 100755 --- a/docs/API_GRAPHQL.md +++ b/docs/API_GRAPHQL.md @@ -4,6 +4,10 @@ GraphQL queries are **read-optimized for speed**. Data may be slightly out of da * Devices * Settings +* Events +* PluginsObjects +* PluginsHistory +* PluginsEvents * Language Strings (LangStrings) ## Endpoints @@ -254,11 +258,160 @@ curl 'http://host:GRAPHQL_PORT/graphql' \ --- +## Plugin Tables (Objects, Events, History) + +Three queries expose the plugin database tables with server-side pagination, filtering, and search: + +* `pluginsObjects` — current plugin object state +* `pluginsEvents` — unprocessed plugin events +* `pluginsHistory` — historical plugin event log + +All three share the same `PluginQueryOptionsInput` and return the same `PluginEntry` shape. + +### Sample Query + +```graphql +query GetPluginObjects($options: PluginQueryOptionsInput) { + pluginsObjects(options: $options) { + dbCount + count + entries { + index plugin objectPrimaryId objectSecondaryId + dateTimeCreated dateTimeChanged + watchedValue1 watchedValue2 watchedValue3 watchedValue4 + status extra userData foreignKey + syncHubNodeName helpVal1 helpVal2 helpVal3 helpVal4 objectGuid + } + } +} +``` + +### Query Parameters (`PluginQueryOptionsInput`) + +| Parameter | Type | Description | +| ------------ | ----------------- | ------------------------------------------------------ | +| `page` | Int | Page number (1-based). | +| `limit` | Int | Rows per page (max 1000). | +| `sort` | [SortOptionsInput] | Sorting options (`field`, `order`). | +| `search` | String | Free-text search across key columns. | +| `filters` | [FilterOptionsInput] | Column-value exact-match filters. | +| `plugin` | String | Plugin prefix to scope results (e.g. `"ARPSCAN"`). | +| `foreignKey` | String | Foreign key filter (e.g. device MAC). | +| `dateFrom` | String | Start of date range filter on `dateTimeCreated`. | +| `dateTo` | String | End of date range filter on `dateTimeCreated`. | + +### Response Fields + +| Field | Type | Description | +| --------- | ------------- | ------------------------------------------------------------- | +| `dbCount` | Int | Total rows for the requested plugin (before search/filters). | +| `count` | Int | Total rows after all filters (before pagination). | +| `entries` | [PluginEntry] | Paginated list of plugin entries. | + +### `curl` Example + +```sh +curl 'http://host:GRAPHQL_PORT/graphql' \ + -X POST \ + -H 'Authorization: Bearer API_TOKEN' \ + -H 'Content-Type: application/json' \ + --data '{ + "query": "query GetPluginObjects($options: PluginQueryOptionsInput) { pluginsObjects(options: $options) { dbCount count entries { index plugin objectPrimaryId status foreignKey } } }", + "variables": { + "options": { + "plugin": "ARPSCAN", + "page": 1, + "limit": 25 + } + } + }' +``` + +### Badge Prefetch (Batched Counts) + +Use GraphQL aliases to fetch counts for all plugins in a single request: + +```graphql +query BadgeCounts { + ARPSCAN: pluginsObjects(options: {plugin: "ARPSCAN", page: 1, limit: 1}) { dbCount } + INTRNT: pluginsObjects(options: {plugin: "INTRNT", page: 1, limit: 1}) { dbCount } +} +``` + +--- + +## Events Query + +Access the Events table with server-side pagination, filtering, and search. + +### Sample Query + +```graphql +query GetEvents($options: EventQueryOptionsInput) { + events(options: $options) { + dbCount + count + entries { + eveMac + eveIp + eveDateTime + eveEventType + eveAdditionalInfo + evePendingAlertEmail + } + } +} +``` + +### Query Parameters (`EventQueryOptionsInput`) + +| Parameter | Type | Description | +| ----------- | ------------------ | ------------------------------------------------ | +| `page` | Int | Page number (1-based). | +| `limit` | Int | Rows per page (max 1000). | +| `sort` | [SortOptionsInput] | Sorting options (`field`, `order`). | +| `search` | String | Free-text search across key columns. | +| `filters` | [FilterOptionsInput] | Column-value exact-match filters. | +| `eveMac` | String | Filter by device MAC address. | +| `eventType` | String | Filter by event type (e.g. `"New Device"`). | +| `dateFrom` | String | Start of date range filter on `eveDateTime`. | +| `dateTo` | String | End of date range filter on `eveDateTime`. | + +### Response Fields + +| Field | Type | Description | +| --------- | ------------ | ------------------------------------------------------------ | +| `dbCount` | Int | Total rows in the Events table (before any filters). | +| `count` | Int | Total rows after all filters (before pagination). | +| `entries` | [EventEntry] | Paginated list of event entries. | + +### `curl` Example + +```sh +curl 'http://host:GRAPHQL_PORT/graphql' \ + -X POST \ + -H 'Authorization: Bearer API_TOKEN' \ + -H 'Content-Type: application/json' \ + --data '{ + "query": "query GetEvents($options: EventQueryOptionsInput) { events(options: $options) { dbCount count entries { eveMac eveIp eveDateTime eveEventType } } }", + "variables": { + "options": { + "eveMac": "00:11:22:33:44:55", + "page": 1, + "limit": 50 + } + } + }' +``` + +--- + ## Notes -* Device, settings, and LangStrings queries can be combined in **one request** since GraphQL supports batching. +* Device, settings, LangStrings, plugin, and event queries can be combined in **one request** since GraphQL supports batching. * The `fallback_to_en` feature ensures UI always has a value even if a translation is missing. * Data is **cached in memory** per JSON file; changes to language or plugin files will only refresh after the cache detects a file modification. * The `setOverriddenByEnv` flag helps identify setting values that are locked at container runtime. -* The schema is **read-only** — updates must be performed through other APIs or configuration management. See the other [API](API.md) endpoints for details. +* Plugin queries scope `dbCount` to the requested `plugin`/`foreignKey` so badge counts reflect per-plugin totals. +* The schema is **read-only** — updates must be performed through other APIs or configuration management. See the other [API](API.md) endpoints for details. diff --git a/docs/API_SESSIONS.md b/docs/API_SESSIONS.md index d5d19eed..879cdea2 100755 --- a/docs/API_SESSIONS.md +++ b/docs/API_SESSIONS.md @@ -224,15 +224,33 @@ curl -X GET "http://:/sessions/AA:BB:CC:DD:EE:FF?period * `type` → Event type (`all`, `sessions`, `missing`, `voided`, `new`, `down`) Default: `all` * `period` → Period to retrieve events (`7 days`, `1 month`, etc.) + * `page` → Page number, 1-based (default: `1`) + * `limit` → Rows per page, max 1000 (default: `100`) + * `search` → Free-text search filter across all columns + * `sortCol` → Column index to sort by, 0-based (default: `0`) + * `sortDir` → Sort direction: `asc` or `desc` (default: `desc`) **Example:** ``` - /sessions/session-events?type=all&period=7 days + /sessions/session-events?type=all&period=7 days&page=1&limit=25&sortCol=3&sortDir=desc ``` **Response:** - Returns a list of events or sessions with formatted connection, disconnection, duration, and IP information. + + ```json + { + "data": [...], + "total": 150, + "recordsFiltered": 150 + } + ``` + + | Field | Type | Description | + | ----------------- | ---- | ------------------------------------------------- | + | `data` | list | Paginated rows (each row is a list of values). | + | `total` | int | Total rows before search filter. | + | `recordsFiltered` | int | Total rows after search filter (before paging). | #### `curl` Example diff --git a/front/deviceDetailsEvents.php b/front/deviceDetailsEvents.php index c7b7fdfe..87a29885 100755 --- a/front/deviceDetailsEvents.php +++ b/front/deviceDetailsEvents.php @@ -32,51 +32,64 @@ function loadEventsData() { const hideConnections = $('#chkHideConnectionEvents')[0].checked; - const hideConnectionsStr = hideConnections ? 'true' : 'false'; let period = $("#period").val(); let { start, end } = getPeriodStartEnd(period); - const rawSql = ` - SELECT eveDateTime, eveEventType, eveIp, eveAdditionalInfo - FROM Events - WHERE eveMac = "${mac}" - AND eveDateTime BETWEEN "${start}" AND "${end}" - AND ( - (eveEventType NOT IN ("Connected", "Disconnected", "VOIDED - Connected", "VOIDED - Disconnected")) - OR "${hideConnectionsStr}" = "false" - ) + const apiToken = getSetting("API_TOKEN"); + const apiBase = getApiBase(); + const graphqlUrl = `${apiBase}/graphql`; + + const query = ` + query Events($options: EventQueryOptionsInput) { + events(options: $options) { + count + entries { + eveDateTime + eveEventType + eveIp + eveAdditionalInfo + } + } + } `; - const apiToken = getSetting("API_TOKEN"); - - const apiBaseUrl = getApiBase(); - const url = `${apiBaseUrl}/dbquery/read`; - $.ajax({ - url: url, + url: graphqlUrl, method: "POST", contentType: "application/json", headers: { "Authorization": `Bearer ${apiToken}` }, data: JSON.stringify({ - rawSql: btoa(rawSql) + query, + variables: { + options: { + eveMac: mac, + dateFrom: start, + dateTo: end, + limit: 500, + sort: [{ field: "eveDateTime", order: "desc" }] + } + } }), success: function (data) { - // assuming read_query returns rows directly - const rows = data["results"].map(row => { - const rawDate = row.eveDateTime; - const formattedDate = rawDate ? localizeTimestamp(rawDate) : '-'; + const CONNECTION_TYPES = ["Connected", "Disconnected", "VOIDED - Connected", "VOIDED - Disconnected"]; - return [ - formattedDate, - row.eveDateTime, - row.eveEventType, - row.eveIp, - row.eveAdditionalInfo - ]; - }); + const rows = data.data.events.entries + .filter(row => !hideConnections || !CONNECTION_TYPES.includes(row.eveEventType)) + .map(row => { + const rawDate = row.eveDateTime; + const formattedDate = rawDate ? localizeTimestamp(rawDate) : '-'; + + return [ + formattedDate, + row.eveDateTime, + row.eveEventType, + row.eveIp, + row.eveAdditionalInfo + ]; + }); const table = $('#tableEvents').DataTable(); table.clear(); diff --git a/front/events.php b/front/events.php index a0c9b6a8..4aa943dd 100755 --- a/front/events.php +++ b/front/events.php @@ -105,21 +105,64 @@ function main() { $('#period').val(period); initializeDatatable(); getEventsTotals(); - getEvents(eventsType); + getEvents(eventsType); // triggers first serverSide draw } /* ---------------- Initialize DataTable ---------------- */ function initializeDatatable() { - const table = $('#tableEvents').DataTable({ - paging: true, + const apiBase = getApiBase(); + const apiToken = getSetting("API_TOKEN"); + + $('#tableEvents').DataTable({ + processing: true, + serverSide: true, + paging: true, lengthChange: true, - lengthMenu: getLengthMenu(getSetting("UI_DEFAULT_PAGE_SIZE")), - searching: true, - ordering: true, - info: true, - autoWidth: false, - order: [[0, "desc"], [3, "desc"], [5, "desc"]], - pageLength: tableRows, + lengthMenu: getLengthMenu(getSetting("UI_DEFAULT_PAGE_SIZE")), + searching: true, + ordering: true, + info: true, + autoWidth: false, + order: [[0, "desc"]], + pageLength: tableRows, + + ajax: function (dtRequest, callback) { + const page = Math.floor(dtRequest.start / dtRequest.length) + 1; + const limit = dtRequest.length; + const search = dtRequest.search?.value || ''; + const sortCol = dtRequest.order?.length ? dtRequest.order[0].column : 0; + const sortDir = dtRequest.order?.length ? dtRequest.order[0].dir : 'desc'; + + const url = `${apiBase}/sessions/session-events` + + `?type=${encodeURIComponent(eventsType)}` + + `&period=${encodeURIComponent(period)}` + + `&page=${page}` + + `&limit=${limit}` + + `&sortCol=${sortCol}` + + `&sortDir=${sortDir}` + + (search ? `&search=${encodeURIComponent(search)}` : ''); + + $.ajax({ + url, + method: "GET", + dataType: "json", + headers: { "Authorization": `Bearer ${apiToken}` }, + success: function (response) { + callback({ + data: response.data || [], + recordsTotal: response.total || 0, + recordsFiltered: response.recordsFiltered || 0 + }); + hideSpinner(); + }, + error: function (xhr, status, error) { + console.error("Error fetching session events:", status, error, xhr.responseText); + callback({ data: [], recordsTotal: 0, recordsFiltered: 0 }); + hideSpinner(); + } + }); + }, + columnDefs: [ { targets: [0,5,6,7,8,10,11,12,13], visible: false }, { targets: [7], orderData: [8] }, @@ -131,14 +174,14 @@ function initializeDatatable() { { targets: [3], createdCell: (td, cellData) => $(td).html(localizeTimestamp(cellData)) }, { targets: [4,5,6,7], createdCell: (td, cellData) => $(td).html(translateHTMLcodes(cellData)) } ], - processing: true, // Shows "processing" overlay + language: { processing: '
', emptyTable: 'No data', lengthMenu: "", - search: ": ", - paginate: { next: "", previous: "" }, - info: "" + search: ": ", + paginate: { next: "", previous: "" }, + info: "" } }); @@ -179,53 +222,33 @@ function getEventsTotals() { }); } -/* ---------------- Fetch events and reload DataTable ---------------- */ +/* ---------------- Switch event type and reload DataTable ---------------- */ function getEvents(type) { eventsType = type; const table = $('#tableEvents').DataTable(); // Event type config: title, color, session columns visibility const config = { - all: {title: 'Events_Shortcut_AllEvents', color: 'aqua', sesionCols: false}, - sessions: {title: 'Events_Shortcut_Sessions', color: 'green', sesionCols: true}, - missing: {title: 'Events_Shortcut_MissSessions', color: 'yellow', sesionCols: true}, - voided: {title: 'Events_Shortcut_VoidSessions', color: 'yellow', sesionCols: false}, - new: {title: 'Events_Shortcut_NewDevices', color: 'yellow', sesionCols: false}, - down: {title: 'Events_Shortcut_DownAlerts', color: 'red', sesionCols: false} + all: {title: 'Events_Shortcut_AllEvents', color: 'aqua', sesionCols: false}, + sessions: {title: 'Events_Shortcut_Sessions', color: 'green', sesionCols: true}, + missing: {title: 'Events_Shortcut_MissSessions', color: 'yellow', sesionCols: true}, + voided: {title: 'Events_Shortcut_VoidSessions', color: 'yellow', sesionCols: false}, + new: {title: 'Events_Shortcut_NewDevices', color: 'yellow', sesionCols: false}, + down: {title: 'Events_Shortcut_DownAlerts', color: 'red', sesionCols: false} }[type] || {title: 'Events_Shortcut_Events', color: '', sesionCols: false}; // Update title and color $('#tableEventsTitle').attr('class', 'box-title text-' + config.color).html(getString(config.title)); $('#tableEventsBox').attr('class', 'box box-' + config.color); - // Toggle columns visibility + // Toggle column visibility table.column(3).visible(!config.sesionCols); table.column(4).visible(!config.sesionCols); table.column(5).visible(config.sesionCols); table.column(6).visible(config.sesionCols); table.column(7).visible(config.sesionCols); - // Build API URL - const apiBase = getApiBase(); - const apiToken = getSetting("API_TOKEN"); - const url = `${apiBase}/sessions/session-events?type=${encodeURIComponent(type)}&period=${encodeURIComponent(period)}`; - - table.clear().draw(); // Clear old rows - - showSpinner() - - $.ajax({ - url, - method: "GET", - dataType: "json", - headers: { "Authorization": `Bearer ${apiToken}` }, - beforeSend: showSpinner, // Show spinner during fetch - complete: hideSpinner, // Hide spinner after fetch - success: response => { - const data = Array.isArray(response) ? response : response.data || []; - table.rows.add(data).draw(); - }, - error: (xhr, status, error) => console.error("Error fetching session events:", status, error, xhr.responseText) - }); + showSpinner(); + table.ajax.reload(null, true); // reset to page 1 } diff --git a/front/pluginsCore.php b/front/pluginsCore.php index 1e19ff07..c76535b0 100755 --- a/front/pluginsCore.php +++ b/front/pluginsCore.php @@ -273,26 +273,14 @@ function genericSaveData (id) { // ----------------------------------------------------------------------------- pluginDefinitions = [] -pluginUnprocessedEvents = [] -pluginObjects = [] -pluginHistory = [] async function getData() { try { showSpinner(); console.log("Plugins getData called"); - const [plugins, events, objects, history] = await Promise.all([ - fetchJson('plugins.json'), - fetchJson('table_plugins_events.json'), - fetchJson('table_plugins_objects.json'), - fetchJson('table_plugins_history.json') - ]); - + const plugins = await fetchJson('plugins.json'); pluginDefinitions = plugins.data; - pluginUnprocessedEvents = events.data; - pluginObjects = objects.data; - pluginHistory = history.data; generateTabs(); } catch (err) { @@ -306,6 +294,106 @@ async function fetchJson(filename) { return await response.json(); } +// GraphQL helper — fires a paginated plugin table query and calls back with +// the DataTables-compatible response plus the raw GraphQL result object. +function postPluginGraphQL(gqlField, prefix, foreignKey, dtRequest, callback) { + const apiToken = getSetting("API_TOKEN"); + const apiBase = getApiBase(); + const page = Math.floor(dtRequest.start / dtRequest.length) + 1; + const limit = dtRequest.length; + const search = dtRequest.search?.value || null; + + let sort = []; + if (dtRequest.order?.length > 0) { + const order = dtRequest.order[0]; + sort.push({ field: dtRequest.columns[order.column].data, order: order.dir }); + } + + const query = ` + query PluginData($options: PluginQueryOptionsInput) { + ${gqlField}(options: $options) { + count + dbCount + entries { + index plugin objectPrimaryId objectSecondaryId + dateTimeCreated dateTimeChanged + watchedValue1 watchedValue2 watchedValue3 watchedValue4 + status extra userData foreignKey + syncHubNodeName helpVal1 helpVal2 helpVal3 helpVal4 objectGuid + } + } + } + `; + + $.ajax({ + method: "POST", + url: `${apiBase}/graphql`, + headers: { "Authorization": `Bearer ${apiToken}`, "Content-Type": "application/json" }, + data: JSON.stringify({ + query, + variables: { options: { page, limit, search, sort, plugin: prefix, foreignKey } } + }), + success: function(response) { + if (response.errors) { + console.error("[plugins] GraphQL errors:", response.errors); + callback({ data: [], recordsTotal: 0, recordsFiltered: 0 }); + return; + } + const result = response.data[gqlField]; + callback({ data: result.entries, recordsTotal: result.dbCount, recordsFiltered: result.count }, result); + }, + error: function() { + callback({ data: [], recordsTotal: 0, recordsFiltered: 0 }); + } + }); +} + +// Fire a single batched GraphQL request to fetch the Objects dbCount for +// every plugin and populate the sidebar badges immediately on page load. +function prefetchPluginBadges() { + const apiToken = getSetting("API_TOKEN"); + const apiBase = getApiBase(); + const mac = $("#txtMacFilter").val(); + const foreignKey = (mac && mac !== "--") ? mac : null; + + // Build one aliased sub-query per visible plugin + const prefixes = pluginDefinitions + .filter(p => p.show_ui) + .map(p => p.unique_prefix); + + if (prefixes.length === 0) return; + + // GraphQL aliases must be valid identifiers — prefixes already are (A-Z0-9_) + const fkOpt = foreignKey ? `, foreignKey: "${foreignKey}"` : ''; + const fragments = prefixes.map(p => [ + `${p}_obj: pluginsObjects(options: {plugin: "${p}", page: 1, limit: 1${fkOpt}}) { dbCount }`, + `${p}_evt: pluginsEvents(options: {plugin: "${p}", page: 1, limit: 1${fkOpt}}) { dbCount }`, + `${p}_hist: pluginsHistory(options: {plugin: "${p}", page: 1, limit: 1${fkOpt}}) { dbCount }`, + ].join('\n ')).join('\n '); + + const query = `query BadgeCounts {\n ${fragments}\n }`; + + $.ajax({ + method: "POST", + url: `${apiBase}/graphql`, + headers: { "Authorization": `Bearer ${apiToken}`, "Content-Type": "application/json" }, + data: JSON.stringify({ query }), + success: function(response) { + if (response.errors) { + console.error("[plugins] badge prefetch errors:", response.errors); + return; + } + prefixes.forEach(p => { + const obj = response.data[`${p}_obj`]; + const evt = response.data[`${p}_evt`]; + const hist = response.data[`${p}_hist`]; + if (obj) { $(`#badge_${p}`).text(obj.dbCount); $(`#objCount_${p}`).text(obj.dbCount); } + if (evt) { $(`#evtCount_${p}`).text(evt.dbCount); } + if (hist) { $(`#histCount_${p}`).text(hist.dbCount); } + }); + } + }); +} function generateTabs() { @@ -315,17 +403,32 @@ function generateTabs() { // Sort pluginDefinitions by unique_prefix alphabetically pluginDefinitions.sort((a, b) => a.unique_prefix.localeCompare(b.unique_prefix)); - assignActive = true; + let assignActive = true; // Iterate over the sorted pluginDefinitions to create tab headers and content pluginDefinitions.forEach(pluginObj => { if (pluginObj.show_ui) { - stats = createTabContent(pluginObj, assignActive); // Create the content for each tab + createTabContent(pluginObj, assignActive); + createTabHeader(pluginObj, assignActive); + assignActive = false; + } + }); - if(stats.objectDataCount > 0) - { - createTabHeader(pluginObj, stats, assignActive); // Create the header for each tab - assignActive = false; // only mark first with content active + // Now that ALL DOM elements exist (both headers and tab panes), + // wire up DataTable initialization: immediate for the active tab, + // deferred via shown.bs.tab for the rest. + let firstVisible = true; + pluginDefinitions.forEach(pluginObj => { + if (pluginObj.show_ui) { + const prefix = pluginObj.unique_prefix; + const colDefinitions = getColumnDefinitions(pluginObj); + if (firstVisible) { + initializeDataTables(prefix, colDefinitions, pluginObj); + firstVisible = false; + } else { + $(`a[href="#${prefix}"]`).one('shown.bs.tab', function() { + initializeDataTables(prefix, colDefinitions, pluginObj); + }); } } }); @@ -338,6 +441,9 @@ function generateTabs() { tabContainer: '#tabs-location' }); + // Pre-fetch badge counts for every plugin in a single batched GraphQL call. + prefetchPluginBadges(); + hideSpinner() } @@ -349,11 +455,11 @@ function resetTabs() { // --------------------------------------------------------------- // left headers -function createTabHeader(pluginObj, stats, assignActive) { +function createTabHeader(pluginObj, assignActive) { const prefix = pluginObj.unique_prefix; // Get the unique prefix for the plugin // Determine the active class for the first tab - assignActive ? activeClass = "active" : activeClass = ""; + const activeClass = assignActive ? "active" : ""; // Append the tab header to the tabs location $('#tabs-location').append(` @@ -362,7 +468,7 @@ function createTabHeader(pluginObj, stats, assignActive) { ${getString(`${prefix}_icon`)} ${getString(`${prefix}_display_name`)} - ${stats.objectDataCount > 0 ? `
${stats.objectDataCount}
` : ""} +
`); @@ -374,19 +480,14 @@ function createTabContent(pluginObj, assignActive) { const prefix = pluginObj.unique_prefix; // Get the unique prefix for the plugin const colDefinitions = getColumnDefinitions(pluginObj); // Get column definitions for DataTables - // Get data for events, objects, and history related to the plugin - const objectData = getObjectData(prefix, colDefinitions, pluginObj); - const eventData = getEventData(prefix, colDefinitions, pluginObj); - const historyData = getHistoryData(prefix, colDefinitions, pluginObj); - // Append the content structure for the plugin's tab to the content location $('#tabs-content-location').append(` -
- ${generateTabNavigation(prefix, objectData.length, eventData.length, historyData.length)} +
+ ${generateTabNavigation(prefix)}
- ${generateDataTable(prefix, 'Objects', objectData, colDefinitions)} - ${generateDataTable(prefix, 'Events', eventData, colDefinitions)} - ${generateDataTable(prefix, 'History', historyData, colDefinitions)} + ${generateDataTable(prefix, 'Objects', colDefinitions)} + ${generateDataTable(prefix, 'Events', colDefinitions)} + ${generateDataTable(prefix, 'History', colDefinitions)}
${getString(`${prefix}_description`)} @@ -395,14 +496,7 @@ function createTabContent(pluginObj, assignActive) {
`); - // Initialize DataTables for the respective sections - initializeDataTables(prefix, objectData, eventData, historyData, colDefinitions); - - return { - "objectDataCount": objectData.length, - "eventDataCount": eventData.length, - "historyDataCount": historyData.length - } + // DataTable init is handled by generateTabs() after all DOM elements exist. } function getColumnDefinitions(pluginObj) { @@ -410,53 +504,26 @@ function getColumnDefinitions(pluginObj) { return pluginObj["database_column_definitions"].filter(colDef => colDef.show); } -function getEventData(prefix, colDefinitions, pluginObj) { - // Extract event data specific to the plugin and format it for DataTables - return pluginUnprocessedEvents - .filter(event => event.plugin === prefix && shouldBeShown(event, pluginObj)) // Filter events for the specific plugin - .map(event => colDefinitions.map(colDef => event[colDef.column] || '')); // Map to the defined columns -} - -function getObjectData(prefix, colDefinitions, pluginObj) { - // Extract object data specific to the plugin and format it for DataTables - return pluginObjects - .filter(object => object.plugin === prefix && shouldBeShown(object, pluginObj)) // Filter objects for the specific plugin - .map(object => colDefinitions.map(colDef => getFormControl(colDef, object[colDef.column], object["index"], colDefinitions, object))); // Map to the defined columns -} - -function getHistoryData(prefix, colDefinitions, pluginObj) { - - return pluginHistory - .filter(history => history.plugin === prefix && shouldBeShown(history, pluginObj)) // First, filter based on the plugin prefix - .sort((a, b) => b.index - a.index) // Then, sort by the Index field in descending order - .slice(0, 50) // Limit the result to the first 50 entries - .map(object => - colDefinitions.map(colDef => - getFormControl(colDef, object[colDef.column], object["index"], colDefinitions, object) - ) - ); -} - -function generateTabNavigation(prefix, objectCount, eventCount, historyCount) { +function generateTabNavigation(prefix) { // Create navigation tabs for Objects, Unprocessed Events, and History return ` `; } -function generateDataTable(prefix, tableType, data, colDefinitions) { +function generateDataTable(prefix, tableType, colDefinitions) { // Generate HTML for a DataTable and associated buttons for a given table type const headersHtml = colDefinitions.map(colDef => `${getString(`${prefix}_${colDef.column}_name`)}`).join(''); @@ -473,34 +540,62 @@ function generateDataTable(prefix, tableType, data, colDefinitions) { `; } -function initializeDataTables(prefix, objectData, eventData, historyData, colDefinitions) { - // Common settings for DataTables initialization - const commonDataTableSettings = { - orderable: false, // Disable ordering - createdRow: function(row, data) { - $(row).attr('data-my-index', data[0]); // Set data attribute for indexing +function initializeDataTables(prefix, colDefinitions, pluginObj) { + const mac = $("#txtMacFilter").val(); + const foreignKey = (mac && mac !== "--") ? mac : null; + + const tableConfigs = [ + { tableId: `objectsTable_${prefix}`, gqlField: 'pluginsObjects', countId: `objCount_${prefix}`, badgeId: `badge_${prefix}` }, + { tableId: `eventsTable_${prefix}`, gqlField: 'pluginsEvents', countId: `evtCount_${prefix}`, badgeId: null }, + { tableId: `historyTable_${prefix}`, gqlField: 'pluginsHistory', countId: `histCount_${prefix}`, badgeId: null }, + ]; + + function buildDT(tableId, gqlField, countId, badgeId) { + if ($.fn.DataTable.isDataTable(`#${tableId}`)) { + return; // already initialized } - }; + $(`#${tableId}`).DataTable({ + processing: true, + serverSide: true, + paging: true, + searching: true, + ordering: false, + pageLength: 25, + lengthMenu: [[10, 25, 50, 100], [10, 25, 50, 100]], + createdRow: function(row, data) { + $(row).attr('data-my-index', data.index); + }, + ajax: function(dtRequest, callback) { + postPluginGraphQL(gqlField, prefix, foreignKey, dtRequest, function(dtResponse, result) { + if (result) { + $(`#${countId}`).text(result.count); + if (badgeId) $(`#${badgeId}`).text(result.dbCount); + } + callback(dtResponse); + }); + }, + columns: colDefinitions.map(colDef => ({ + data: colDef.column, + title: getString(`${prefix}_${colDef.column}_name`), + className: colDef.css_classes || '', + createdCell: function(td, cellData, rowData) { + $(td).html(getFormControl(colDef, cellData, rowData.index)); + } + })) + }); + } - // Initialize DataTable for Objects - $(`#objectsTable_${prefix}`).DataTable({ - data: objectData, - columns: colDefinitions.map(colDef => ({ title: getString(`${prefix}_${colDef.column}_name`) })), // Column titles - ...commonDataTableSettings // Spread common settings + // Initialize the Objects table immediately (it is the active/visible sub-tab). + // Defer Events and History tables until their sub-tab is first shown. + const [objCfg, evtCfg, histCfg] = tableConfigs; + buildDT(objCfg.tableId, objCfg.gqlField, objCfg.countId, objCfg.badgeId); + + $(`a[href="#eventsTarget_${prefix}"]`).one('shown.bs.tab', function() { + buildDT(evtCfg.tableId, evtCfg.gqlField, evtCfg.countId, evtCfg.badgeId); }); - // Initialize DataTable for Unprocessed Events - $(`#eventsTable_${prefix}`).DataTable({ - data: eventData, - columns: colDefinitions.map(colDef => ({ title: getString(`${prefix}_${colDef.column}_name`) })), // Column titles - ...commonDataTableSettings // Spread common settings - }); - - // Initialize DataTable for History - $(`#historyTable_${prefix}`).DataTable({ - data: historyData, - columns: colDefinitions.map(colDef => ({ title: getString(`${prefix}_${colDef.column}_name`) })), // Column titles - ...commonDataTableSettings // Spread common settings + $(`a[href="#historyTarget_${prefix}"]`).one('shown.bs.tab', function() { + buildDT(histCfg.tableId, histCfg.gqlField, histCfg.countId, histCfg.badgeId); }); } diff --git a/server/api.py b/server/api.py index 2ed27e37..fd775f47 100755 --- a/server/api.py +++ b/server/api.py @@ -10,6 +10,7 @@ from const import ( apiPath, sql_appevents, sql_devices_all, + sql_events_all, sql_events_pending_alert, sql_settings, sql_plugins_events, @@ -59,6 +60,7 @@ def update_api( dataSourcesSQLs = [ ["appevents", sql_appevents], ["devices", sql_devices_all], + ["events", sql_events_all], ["events_pending_alert", sql_events_pending_alert], ["settings", sql_settings], ["plugins_events", sql_plugins_events], diff --git a/server/api_server/api_server_start.py b/server/api_server/api_server_start.py index c50d7a35..0f1de544 100755 --- a/server/api_server/api_server_start.py +++ b/server/api_server/api_server_start.py @@ -1750,8 +1750,13 @@ def api_device_sessions(mac, payload=None): summary="Get Session Events", description="Retrieve events associated with sessions.", query_params=[ - {"name": "type", "description": "Event type", "required": False, "schema": {"type": "string", "default": "all"}}, - {"name": "period", "description": "Time period", "required": False, "schema": {"type": "string", "default": "7 days"}} + {"name": "type", "description": "Event type", "required": False, "schema": {"type": "string", "default": "all"}}, + {"name": "period", "description": "Time period", "required": False, "schema": {"type": "string", "default": "7 days"}}, + {"name": "page", "description": "Page number (1-based)", "required": False, "schema": {"type": "integer", "default": 1}}, + {"name": "limit", "description": "Rows per page (max 1000)", "required": False, "schema": {"type": "integer", "default": 100}}, + {"name": "search", "description": "Free-text search filter", "required": False, "schema": {"type": "string"}}, + {"name": "sortCol", "description": "Column index to sort by (0-based)", "required": False, "schema": {"type": "integer", "default": 0}}, + {"name": "sortDir", "description": "Sort direction: asc or desc", "required": False, "schema": {"type": "string", "default": "desc"}} ], tags=["sessions"], auth_callable=is_authorized @@ -1759,7 +1764,12 @@ def api_device_sessions(mac, payload=None): def api_get_session_events(payload=None): session_event_type = request.args.get("type", "all") period = get_date_from_period(request.args.get("period", "7 days")) - return get_session_events(session_event_type, period) + page = request.args.get("page", 1, type=int) + limit = request.args.get("limit", 100, type=int) + search = request.args.get("search", None) + sort_col = request.args.get("sortCol", 0, type=int) + sort_dir = request.args.get("sortDir", "desc") + return get_session_events(session_event_type, period, page=page, limit=limit, search=search, sort_col=sort_col, sort_dir=sort_dir) # -------------------------- diff --git a/server/api_server/graphql_endpoint.py b/server/api_server/graphql_endpoint.py index 99850800..e950a204 100755 --- a/server/api_server/graphql_endpoint.py +++ b/server/api_server/graphql_endpoint.py @@ -1,7 +1,5 @@ import graphene -from graphene import ( - ObjectType, String, Int, Boolean, List, Field, InputObjectType, Argument -) +from graphene import ObjectType, List, Field, Argument, String import json import sys import os @@ -19,175 +17,30 @@ from helper import ( # noqa: E402 [flake8 lint suppression] get_setting_value, ) -# Define a base URL with the user's home directory +from .graphql_types import ( # noqa: E402 [flake8 lint suppression] + FilterOptionsInput, PageQueryOptionsInput, + Device, DeviceResult, + Setting, SettingResult, + LangString, LangStringResult, + AppEvent, AppEventResult, + PluginQueryOptionsInput, PluginEntry, + PluginsObjectsResult, PluginsEventsResult, PluginsHistoryResult, + EventQueryOptionsInput, EventEntry, EventsResult, +) +from .graphql_helpers import ( # noqa: E402 [flake8 lint suppression] + mixed_type_sort_key, + apply_common_pagination, + apply_plugin_filters, + apply_events_filters, +) + folder = apiPath - -# --- DEVICES --- -# Pagination and Sorting Input Types -class SortOptionsInput(InputObjectType): - field = String() - order = String() - - -class FilterOptionsInput(InputObjectType): - filterColumn = String() - filterValue = String() - - -class PageQueryOptionsInput(InputObjectType): - page = Int() - limit = Int() - sort = List(SortOptionsInput) - search = String() - status = String() - filters = List(FilterOptionsInput) - - -# Device ObjectType -class Device(ObjectType): - rowid = Int(description="Database row ID") - devMac = String(description="Device MAC address (e.g., 00:11:22:33:44:55)") - devName = String(description="Device display name/alias") - devOwner = String(description="Device owner") - devType = String(description="Device type classification") - devVendor = String(description="Hardware vendor from OUI lookup") - devFavorite = Int(description="Favorite flag (0 or 1)") - devGroup = String(description="Device group") - devComments = String(description="User comments") - devFirstConnection = String(description="Timestamp of first discovery") - devLastConnection = String(description="Timestamp of last connection") - devLastIP = String(description="Last known IP address") - devPrimaryIPv4 = String(description="Primary IPv4 address") - devPrimaryIPv6 = String(description="Primary IPv6 address") - devVlan = String(description="VLAN identifier") - devForceStatus = String(description="Force device status (online/offline/dont_force)") - devStaticIP = Int(description="Static IP flag (0 or 1)") - devScan = Int(description="Scan flag (0 or 1)") - devLogEvents = Int(description="Log events flag (0 or 1)") - devAlertEvents = Int(description="Alert events flag (0 or 1)") - devAlertDown = Int(description="Alert on down flag (0 or 1)") - devSkipRepeated = Int(description="Skip repeated alerts flag (0 or 1)") - devLastNotification = String(description="Timestamp of last notification") - devPresentLastScan = Int(description="Present in last scan flag (0 or 1)") - devIsNew = Int(description="Is new device flag (0 or 1)") - devLocation = String(description="Device location") - devIsArchived = Int(description="Is archived flag (0 or 1)") - devParentMAC = String(description="Parent device MAC address") - devParentPort = String(description="Parent device port") - devIcon = String(description="Base64-encoded HTML/SVG markup used to render the device icon") - devGUID = String(description="Unique device GUID") - devSite = String(description="Site name") - devSSID = String(description="SSID connected to") - devSyncHubNode = String(description="Sync hub node name") - devSourcePlugin = String(description="Plugin that discovered the device") - devCustomProps = String(description="Base64-encoded custom properties in JSON format") - devStatus = String(description="Online/Offline status") - devIsRandomMac = Int(description="Calculated: Is MAC address randomized?") - devParentChildrenCount = Int(description="Calculated: Number of children attached to this parent") - devIpLong = String(description="Calculated: IP address in long format (returned as string to support the full unsigned 32-bit range)") - devFilterStatus = String(description="Calculated: Device status for UI filtering") - devFQDN = String(description="Fully Qualified Domain Name") - devParentRelType = String(description="Relationship type to parent") - devReqNicsOnline = Int(description="Required NICs online flag") - devMacSource = String(description="Source tracking for devMac (USER, LOCKED, NEWDEV, or plugin prefix)") - devNameSource = String(description="Source tracking for devName (USER, LOCKED, NEWDEV, or plugin prefix)") - devFQDNSource = String(description="Source tracking for devFQDN (USER, LOCKED, NEWDEV, or plugin prefix)") - devLastIPSource = String(description="Source tracking for devLastIP (USER, LOCKED, NEWDEV, or plugin prefix)") - devVendorSource = String(description="Source tracking for devVendor (USER, LOCKED, NEWDEV, or plugin prefix)") - devSSIDSource = String(description="Source tracking for devSSID (USER, LOCKED, NEWDEV, or plugin prefix)") - devParentMACSource = String(description="Source tracking for devParentMAC (USER, LOCKED, NEWDEV, or plugin prefix)") - devParentPortSource = String(description="Source tracking for devParentPort (USER, LOCKED, NEWDEV, or plugin prefix)") - devParentRelTypeSource = String(description="Source tracking for devParentRelType (USER, LOCKED, NEWDEV, or plugin prefix)") - devVlanSource = String(description="Source tracking for devVlan") - devFlapping = Int(description="Indicates flapping device (device changing between online/offline states frequently)") - devCanSleep = Int(description="Can this device sleep? (0 or 1). When enabled, offline periods within NTFPRCS_sleep_time are reported as Sleeping instead of Down.") - devIsSleeping = Int(description="Computed: Is device currently in a sleep window? (0 or 1)") - - -class DeviceResult(ObjectType): - devices = List(Device) - count = Int() - db_count = Int(description="Total device count in the database, before any status/filter/search is applied") - - -# --- SETTINGS --- - - -# Setting ObjectType -class Setting(ObjectType): - setKey = String(description="Unique configuration key") - setName = String(description="Human-readable setting name") - setDescription = String(description="Detailed description of the setting") - setType = String(description="Config-driven type definition used to determine value type and UI rendering") - setOptions = String(description="JSON string of available options") - setGroup = String(description="UI group for categorization") - setValue = String(description="Current value") - setEvents = String(description="JSON string of events") - setOverriddenByEnv = Boolean(description="Whether the value is currently overridden by an environment variable") - - -class SettingResult(ObjectType): - settings = List(Setting, description="List of setting objects") - count = Int(description="Total count of settings") - -# --- LANGSTRINGS --- - - # In-memory cache for lang strings _langstrings_cache = {} # caches lists per file (core JSON or plugin) _langstrings_cache_mtime = {} # tracks last modified times -# LangString ObjectType -class LangString(ObjectType): - langCode = String(description="Language code (e.g., en_us, de_de)") - langStringKey = String(description="Unique translation key") - langStringText = String(description="Translated text content") - - -class LangStringResult(ObjectType): - langStrings = List(LangString, description="List of language string objects") - count = Int(description="Total count of strings") - - -# --- APP EVENTS --- - -class AppEvent(ObjectType): - index = Int(description="Internal index") - guid = String(description="Unique event GUID") - appEventProcessed = Int(description="Processing status (0 or 1)") - dateTimeCreated = String(description="Event creation timestamp") - - objectType = String(description="Type of the related object (Device, Setting, etc.)") - objectGuid = String(description="GUID of the related object") - objectPlugin = String(description="Plugin associated with the object") - objectPrimaryId = String(description="Primary identifier of the object") - objectSecondaryId = String(description="Secondary identifier of the object") - objectForeignKey = String(description="Foreign key reference") - objectIndex = Int(description="Object index") - - objectIsNew = Int(description="Is the object new? (0 or 1)") - objectIsArchived = Int(description="Is the object archived? (0 or 1)") - objectStatusColumn = String(description="Column used for status") - objectStatus = String(description="Object status value") - - appEventType = String(description="Type of application event") - - helper1 = String(description="Generic helper field 1") - helper2 = String(description="Generic helper field 2") - helper3 = String(description="Generic helper field 3") - extra = String(description="Additional JSON data") - - -class AppEventResult(ObjectType): - appEvents = List(AppEvent, description="List of application events") - count = Int(description="Total count of events") - - -# ---------------------------------------------------------------------------------------------- - -# Define Query Type with Pagination Support class Query(ObjectType): # --- DEVICES --- devices = Field(DeviceResult, options=PageQueryOptionsInput()) @@ -652,15 +505,75 @@ class Query(ObjectType): return LangStringResult(langStrings=langStrings, count=len(langStrings)) + # --- PLUGINS_OBJECTS --- + pluginsObjects = Field(PluginsObjectsResult, options=PluginQueryOptionsInput()) -# helps sorting inconsistent dataset mixed integers and strings -def mixed_type_sort_key(value): - if value is None or value == "": - return (2, "") # Place None or empty strings last + def resolve_pluginsObjects(self, info, options=None): + return _resolve_plugin_table("table_plugins_objects.json", options, PluginsObjectsResult) + + # --- PLUGINS_EVENTS --- + pluginsEvents = Field(PluginsEventsResult, options=PluginQueryOptionsInput()) + + def resolve_pluginsEvents(self, info, options=None): + return _resolve_plugin_table("table_plugins_events.json", options, PluginsEventsResult) + + # --- PLUGINS_HISTORY --- + pluginsHistory = Field(PluginsHistoryResult, options=PluginQueryOptionsInput()) + + def resolve_pluginsHistory(self, info, options=None): + return _resolve_plugin_table("table_plugins_history.json", options, PluginsHistoryResult) + + # --- EVENTS --- + events = Field(EventsResult, options=EventQueryOptionsInput()) + + def resolve_events(self, info, options=None): + try: + with open(folder + "table_events.json", "r") as f: + data = json.load(f).get("data", []) + except (FileNotFoundError, json.JSONDecodeError) as e: + mylog("none", f"[graphql_schema] Error loading events data: {e}") + return EventsResult(entries=[], count=0, db_count=0) + + db_count = len(data) + data = apply_events_filters(data, options) + data, total_count = apply_common_pagination(data, options) + return EventsResult( + entries=[EventEntry(**r) for r in data], + count=total_count, + db_count=db_count, + ) + + +# --------------------------------------------------------------------------- +# Private resolver helper — shared by all three plugin table resolvers +# --------------------------------------------------------------------------- + +def _resolve_plugin_table(json_file, options, ResultType): try: - return (0, int(value)) # Integers get priority - except (ValueError, TypeError): - return (1, str(value)) # Strings come next + with open(folder + json_file, "r") as f: + data = json.load(f).get("data", []) + except (FileNotFoundError, json.JSONDecodeError) as e: + mylog("none", f"[graphql_schema] Error loading {json_file}: {e}") + return ResultType(entries=[], count=0, db_count=0) + + # Scope to the requested plugin + foreignKey FIRST so db_count + # reflects the total for THIS plugin, not the entire table. + if options: + if options.plugin: + pl = options.plugin.lower() + data = [r for r in data if str(r.get("plugin", "")).lower() == pl] + if options.foreignKey: + fk = options.foreignKey.lower() + data = [r for r in data if str(r.get("foreignKey", "")).lower() == fk] + + db_count = len(data) + data = apply_plugin_filters(data, options) + data, total_count = apply_common_pagination(data, options) + return ResultType( + entries=[PluginEntry(**r) for r in data], + count=total_count, + db_count=db_count, + ) # Schema Definition diff --git a/server/api_server/graphql_helpers.py b/server/api_server/graphql_helpers.py new file mode 100644 index 00000000..eeb17641 --- /dev/null +++ b/server/api_server/graphql_helpers.py @@ -0,0 +1,140 @@ +""" +graphql_helpers.py — Shared utility functions for GraphQL resolvers. +""" + +_MAX_LIMIT = 1000 +_DEFAULT_LIMIT = 100 + + +def mixed_type_sort_key(value): + """Sort key that handles mixed int/string datasets without crashing. + + Ordering priority: + 0 — integers (sorted numerically) + 1 — strings (sorted lexicographically) + 2 — None / empty string (always last) + """ + if value is None or value == "": + return (2, "") + try: + return (0, int(value)) + except (ValueError, TypeError): + return (1, str(value)) + + +def apply_common_pagination(data, options): + """Apply sort + capture total_count + paginate. + + Returns (paged_data, total_count). + Enforces a hard limit cap of _MAX_LIMIT — never returns unbounded results. + """ + if not options: + return data, len(data) + + # --- SORT --- + if options.sort: + for sort_option in reversed(options.sort): + field = sort_option.field + reverse = (sort_option.order or "asc").lower() == "desc" + data = sorted( + data, + key=lambda x: mixed_type_sort_key(x.get(field)), + reverse=reverse, + ) + + total_count = len(data) + + # --- PAGINATE --- + if options.page is not None and options.limit is not None: + effective_limit = min(options.limit, _MAX_LIMIT) + start = (options.page - 1) * effective_limit + end = start + effective_limit + data = data[start:end] + + return data, total_count + + +def apply_plugin_filters(data, options): + """Filter a list of plugin table rows (Plugins_Objects/Events/History). + + Handles: date range, column filters, free-text search. + NOTE: plugin prefix and foreignKey scoping is done in the resolver + BEFORE db_count is captured — do NOT duplicate here. + """ + if not options: + return data + + # Date-range filter on dateTimeCreated + if options.dateFrom: + data = [r for r in data if str(r.get("dateTimeCreated", "")) >= options.dateFrom] + if options.dateTo: + data = [r for r in data if str(r.get("dateTimeCreated", "")) <= options.dateTo] + + # Column-value exact-match filters + if options.filters: + for f in options.filters: + if f.filterColumn and f.filterValue is not None: + data = [ + r for r in data + if str(r.get(f.filterColumn, "")).lower() == str(f.filterValue).lower() + ] + + # Free-text search + if options.search: + term = options.search.lower() + searchable = [ + "plugin", "objectPrimaryId", "objectSecondaryId", + "watchedValue1", "watchedValue2", "watchedValue3", "watchedValue4", + "status", "extra", "foreignKey", "objectGuid", "userData", + ] + data = [ + r for r in data + if any(term in str(r.get(field, "")).lower() for field in searchable) + ] + + return data + + +def apply_events_filters(data, options): + """Filter a list of Events table rows. + + Handles: eveMac, eventType, date range, column filters, free-text search. + """ + if not options: + return data + + # MAC filter + if options.eveMac: + mac = options.eveMac.lower() + data = [r for r in data if str(r.get("eveMac", "")).lower() == mac] + + # Event-type filter + if options.eventType: + et = options.eventType.lower() + data = [r for r in data if str(r.get("eveEventType", "")).lower() == et] + + # Date-range filter on eveDateTime + if options.dateFrom: + data = [r for r in data if str(r.get("eveDateTime", "")) >= options.dateFrom] + if options.dateTo: + data = [r for r in data if str(r.get("eveDateTime", "")) <= options.dateTo] + + # Column-value exact-match filters + if options.filters: + for f in options.filters: + if f.filterColumn and f.filterValue is not None: + data = [ + r for r in data + if str(r.get(f.filterColumn, "")).lower() == str(f.filterValue).lower() + ] + + # Free-text search + if options.search: + term = options.search.lower() + searchable = ["eveMac", "eveIp", "eveEventType", "eveAdditionalInfo"] + data = [ + r for r in data + if any(term in str(r.get(field, "")).lower() for field in searchable) + ] + + return data diff --git a/server/api_server/graphql_types.py b/server/api_server/graphql_types.py new file mode 100644 index 00000000..3465e2cd --- /dev/null +++ b/server/api_server/graphql_types.py @@ -0,0 +1,261 @@ +import graphene # noqa: F401 (re-exported for schema creation in graphql_endpoint.py) +from graphene import ( + ObjectType, String, Int, Boolean, List, InputObjectType, +) + +# --------------------------------------------------------------------------- +# Shared Input Types +# --------------------------------------------------------------------------- + + +class SortOptionsInput(InputObjectType): + field = String() + order = String() + + +class FilterOptionsInput(InputObjectType): + filterColumn = String() + filterValue = String() + + +class PageQueryOptionsInput(InputObjectType): + page = Int() + limit = Int() + sort = List(SortOptionsInput) + search = String() + status = String() + filters = List(FilterOptionsInput) + + +# --------------------------------------------------------------------------- +# Devices +# --------------------------------------------------------------------------- + +class Device(ObjectType): + rowid = Int(description="Database row ID") + devMac = String(description="Device MAC address (e.g., 00:11:22:33:44:55)") + devName = String(description="Device display name/alias") + devOwner = String(description="Device owner") + devType = String(description="Device type classification") + devVendor = String(description="Hardware vendor from OUI lookup") + devFavorite = Int(description="Favorite flag (0 or 1)") + devGroup = String(description="Device group") + devComments = String(description="User comments") + devFirstConnection = String(description="Timestamp of first discovery") + devLastConnection = String(description="Timestamp of last connection") + devLastIP = String(description="Last known IP address") + devPrimaryIPv4 = String(description="Primary IPv4 address") + devPrimaryIPv6 = String(description="Primary IPv6 address") + devVlan = String(description="VLAN identifier") + devForceStatus = String(description="Force device status (online/offline/dont_force)") + devStaticIP = Int(description="Static IP flag (0 or 1)") + devScan = Int(description="Scan flag (0 or 1)") + devLogEvents = Int(description="Log events flag (0 or 1)") + devAlertEvents = Int(description="Alert events flag (0 or 1)") + devAlertDown = Int(description="Alert on down flag (0 or 1)") + devSkipRepeated = Int(description="Skip repeated alerts flag (0 or 1)") + devLastNotification = String(description="Timestamp of last notification") + devPresentLastScan = Int(description="Present in last scan flag (0 or 1)") + devIsNew = Int(description="Is new device flag (0 or 1)") + devLocation = String(description="Device location") + devIsArchived = Int(description="Is archived flag (0 or 1)") + devParentMAC = String(description="Parent device MAC address") + devParentPort = String(description="Parent device port") + devIcon = String(description="Base64-encoded HTML/SVG markup used to render the device icon") + devGUID = String(description="Unique device GUID") + devSite = String(description="Site name") + devSSID = String(description="SSID connected to") + devSyncHubNode = String(description="Sync hub node name") + devSourcePlugin = String(description="Plugin that discovered the device") + devCustomProps = String(description="Base64-encoded custom properties in JSON format") + devStatus = String(description="Online/Offline status") + devIsRandomMac = Int(description="Calculated: Is MAC address randomized?") + devParentChildrenCount = Int(description="Calculated: Number of children attached to this parent") + devIpLong = String(description="Calculated: IP address in long format (returned as string to support the full unsigned 32-bit range)") + devFilterStatus = String(description="Calculated: Device status for UI filtering") + devFQDN = String(description="Fully Qualified Domain Name") + devParentRelType = String(description="Relationship type to parent") + devReqNicsOnline = Int(description="Required NICs online flag") + devMacSource = String(description="Source tracking for devMac (USER, LOCKED, NEWDEV, or plugin prefix)") + devNameSource = String(description="Source tracking for devName (USER, LOCKED, NEWDEV, or plugin prefix)") + devFQDNSource = String(description="Source tracking for devFQDN (USER, LOCKED, NEWDEV, or plugin prefix)") + devLastIPSource = String(description="Source tracking for devLastIP (USER, LOCKED, NEWDEV, or plugin prefix)") + devVendorSource = String(description="Source tracking for devVendor (USER, LOCKED, NEWDEV, or plugin prefix)") + devSSIDSource = String(description="Source tracking for devSSID (USER, LOCKED, NEWDEV, or plugin prefix)") + devParentMACSource = String(description="Source tracking for devParentMAC (USER, LOCKED, NEWDEV, or plugin prefix)") + devParentPortSource = String(description="Source tracking for devParentPort (USER, LOCKED, NEWDEV, or plugin prefix)") + devParentRelTypeSource = String(description="Source tracking for devParentRelType (USER, LOCKED, NEWDEV, or plugin prefix)") + devVlanSource = String(description="Source tracking for devVlan") + devFlapping = Int(description="Indicates flapping device (device changing between online/offline states frequently)") + devCanSleep = Int(description="Can this device sleep? (0 or 1). When enabled, offline periods within NTFPRCS_sleep_time are reported as Sleeping instead of Down.") + devIsSleeping = Int(description="Computed: Is device currently in a sleep window? (0 or 1)") + + +class DeviceResult(ObjectType): + devices = List(Device) + count = Int() + db_count = Int(description="Total device count in the database, before any status/filter/search is applied") + + +# --------------------------------------------------------------------------- +# Settings +# --------------------------------------------------------------------------- + +class Setting(ObjectType): + setKey = String(description="Unique configuration key") + setName = String(description="Human-readable setting name") + setDescription = String(description="Detailed description of the setting") + setType = String(description="Config-driven type definition used to determine value type and UI rendering") + setOptions = String(description="JSON string of available options") + setGroup = String(description="UI group for categorization") + setValue = String(description="Current value") + setEvents = String(description="JSON string of events") + setOverriddenByEnv = Boolean(description="Whether the value is currently overridden by an environment variable") + + +class SettingResult(ObjectType): + settings = List(Setting, description="List of setting objects") + count = Int(description="Total count of settings") + + +# --------------------------------------------------------------------------- +# Language Strings +# --------------------------------------------------------------------------- + +class LangString(ObjectType): + langCode = String(description="Language code (e.g., en_us, de_de)") + langStringKey = String(description="Unique translation key") + langStringText = String(description="Translated text content") + + +class LangStringResult(ObjectType): + langStrings = List(LangString, description="List of language string objects") + count = Int(description="Total count of strings") + + +# --------------------------------------------------------------------------- +# App Events +# --------------------------------------------------------------------------- + +class AppEvent(ObjectType): + index = Int(description="Internal index") + guid = String(description="Unique event GUID") + appEventProcessed = Int(description="Processing status (0 or 1)") + dateTimeCreated = String(description="Event creation timestamp") + + objectType = String(description="Type of the related object (Device, Setting, etc.)") + objectGuid = String(description="GUID of the related object") + objectPlugin = String(description="Plugin associated with the object") + objectPrimaryId = String(description="Primary identifier of the object") + objectSecondaryId = String(description="Secondary identifier of the object") + objectForeignKey = String(description="Foreign key reference") + objectIndex = Int(description="Object index") + + objectIsNew = Int(description="Is the object new? (0 or 1)") + objectIsArchived = Int(description="Is the object archived? (0 or 1)") + objectStatusColumn = String(description="Column used for status") + objectStatus = String(description="Object status value") + + appEventType = String(description="Type of application event") + + helper1 = String(description="Generic helper field 1") + helper2 = String(description="Generic helper field 2") + helper3 = String(description="Generic helper field 3") + extra = String(description="Additional JSON data") + + +class AppEventResult(ObjectType): + appEvents = List(AppEvent, description="List of application events") + count = Int(description="Total count of events") + + +# --------------------------------------------------------------------------- +# Plugin tables (Plugins_Objects, Plugins_Events, Plugins_History) +# All three tables share the same schema — one ObjectType, three result wrappers. +# GraphQL requires distinct named types even when fields are identical. +# --------------------------------------------------------------------------- + +class PluginQueryOptionsInput(InputObjectType): + page = Int() + limit = Int() + sort = List(SortOptionsInput) + search = String() + filters = List(FilterOptionsInput) + plugin = String(description="Filter by plugin prefix (e.g. 'ARPSCAN')") + foreignKey = String(description="Filter by foreignKey (e.g. device MAC)") + dateFrom = String(description="dateTimeCreated >= dateFrom (ISO datetime string)") + dateTo = String(description="dateTimeCreated <= dateTo (ISO datetime string)") + + +class PluginEntry(ObjectType): + index = Int(description="Auto-increment primary key") + plugin = String(description="Plugin prefix identifier") + objectPrimaryId = String(description="Primary identifier (e.g. MAC, IP)") + objectSecondaryId = String(description="Secondary identifier") + dateTimeCreated = String(description="Record creation timestamp") + dateTimeChanged = String(description="Record last-changed timestamp") + watchedValue1 = String(description="Monitored value 1") + watchedValue2 = String(description="Monitored value 2") + watchedValue3 = String(description="Monitored value 3") + watchedValue4 = String(description="Monitored value 4") + status = String(description="Record status") + extra = String(description="Extra JSON payload") + userData = String(description="User-supplied data") + foreignKey = String(description="Foreign key (e.g. device MAC)") + syncHubNodeName = String(description="Sync hub node name") + helpVal1 = String(description="Helper value 1") + helpVal2 = String(description="Helper value 2") + helpVal3 = String(description="Helper value 3") + helpVal4 = String(description="Helper value 4") + objectGuid = String(description="Object GUID") + + +class PluginsObjectsResult(ObjectType): + entries = List(PluginEntry, description="Plugins_Objects rows") + count = Int(description="Filtered count (before pagination)") + db_count = Int(description="Total rows in table before any filter") + + +class PluginsEventsResult(ObjectType): + entries = List(PluginEntry, description="Plugins_Events rows") + count = Int(description="Filtered count (before pagination)") + db_count = Int(description="Total rows in table before any filter") + + +class PluginsHistoryResult(ObjectType): + entries = List(PluginEntry, description="Plugins_History rows") + count = Int(description="Filtered count (before pagination)") + db_count = Int(description="Total rows in table before any filter") + + +# --------------------------------------------------------------------------- +# Events table (device presence events) +# --------------------------------------------------------------------------- + +class EventQueryOptionsInput(InputObjectType): + page = Int() + limit = Int() + sort = List(SortOptionsInput) + search = String() + filters = List(FilterOptionsInput) + eveMac = String(description="Filter by device MAC address") + eventType = String(description="Filter by eveEventType (exact match)") + dateFrom = String(description="eveDateTime >= dateFrom (ISO datetime string)") + dateTo = String(description="eveDateTime <= dateTo (ISO datetime string)") + + +class EventEntry(ObjectType): + rowid = Int(description="SQLite rowid") + eveMac = String(description="Device MAC address") + eveIp = String(description="Device IP at event time") + eveDateTime = String(description="Event timestamp") + eveEventType = String(description="Event type (Connected, New Device, etc.)") + eveAdditionalInfo = String(description="Additional event info") + evePendingAlertEmail = Int(description="Pending alert flag (0 or 1)") + evePairEventRowid = Int(description="Paired event rowid (for session pairing)") + + +class EventsResult(ObjectType): + entries = List(EventEntry, description="Events table rows") + count = Int(description="Filtered count (before pagination)") + db_count = Int(description="Total rows in table before any filter") diff --git a/server/api_server/sessions_endpoint.py b/server/api_server/sessions_endpoint.py index dd6037d2..bda77dae 100755 --- a/server/api_server/sessions_endpoint.py +++ b/server/api_server/sessions_endpoint.py @@ -295,10 +295,16 @@ def get_device_sessions(mac, period): return jsonify({"success": True, "sessions": sessions}) -def get_session_events(event_type, period_date): +def get_session_events(event_type, period_date, page=1, limit=100, search=None, sort_col=0, sort_dir="desc"): """ Fetch events or sessions based on type and period. + Supports server-side pagination (page/limit), free-text search, and sorting. + Returns { data, total, recordsFiltered } so callers can drive DataTables serverSide mode. """ + _MAX_LIMIT = 1000 + limit = min(max(1, int(limit)), _MAX_LIMIT) + page = max(1, int(page)) + conn = get_temp_db_connection() conn.row_factory = sqlite3.Row cur = conn.cursor() @@ -420,4 +426,30 @@ def get_session_events(event_type, period_date): table_data["data"].append(row) - return jsonify(table_data) + all_rows = table_data["data"] + + # --- Sorting --- + num_cols = len(all_rows[0]) if all_rows else 0 + if 0 <= sort_col < num_cols: + reverse = sort_dir.lower() == "desc" + all_rows.sort( + key=lambda r: (r[sort_col] is None, r[sort_col] if r[sort_col] is not None else ""), + reverse=reverse, + ) + + total = len(all_rows) + + # --- Free-text search (applied after formatting so display values are searchable) --- + if search: + search_lower = search.strip().lower() + + def _row_matches(r): + return any(search_lower in str(v).lower() for v in r if v is not None) + all_rows = [r for r in all_rows if _row_matches(r)] + records_filtered = len(all_rows) + + # --- Pagination --- + offset = (page - 1) * limit + paged_rows = all_rows[offset: offset + limit] + + return jsonify({"data": paged_rows, "total": total, "recordsFiltered": records_filtered}) diff --git a/server/const.py b/server/const.py index e07eda06..97faa00b 100755 --- a/server/const.py +++ b/server/const.py @@ -117,6 +117,7 @@ sql_devices_stats = f""" LIMIT 1 """ sql_events_pending_alert = "SELECT * FROM Events where evePendingAlertEmail is not 0" +sql_events_all = "SELECT rowid, * FROM Events ORDER BY eveDateTime DESC" sql_settings = "SELECT * FROM Settings" sql_plugins_objects = "SELECT * FROM Plugins_Objects" sql_language_strings = "SELECT * FROM Plugins_Language_Strings" diff --git a/test/api_endpoints/test_graphq_endpoints.py b/test/api_endpoints/test_graphq_endpoints.py index 15f799ac..86733e94 100644 --- a/test/api_endpoints/test_graphq_endpoints.py +++ b/test/api_endpoints/test_graphq_endpoints.py @@ -192,3 +192,242 @@ def test_graphql_langstrings_excludes_languages_json(client, api_token): f"languages.json leaked into langStrings as {len(polluted)} entries; " "graphql_endpoint.py must exclude it from the directory scan" ) + + +# --- PLUGINS_OBJECTS TESTS --- + +def test_graphql_plugins_objects_no_options(client, api_token): + """pluginsObjects without options returns valid schema (entries list + count fields)""" + query = { + "query": """ + { + pluginsObjects { + dbCount + count + entries { + index + plugin + objectPrimaryId + status + } + } + } + """ + } + resp = client.post("/graphql", json=query, headers=auth_headers(api_token)) + assert resp.status_code == 200 + body = resp.get_json() + assert "errors" not in body + result = body["data"]["pluginsObjects"] + assert isinstance(result["entries"], list) + assert isinstance(result["dbCount"], int) + assert isinstance(result["count"], int) + assert result["dbCount"] >= result["count"] + + +def test_graphql_plugins_objects_pagination(client, api_token): + """pluginsObjects with limit=5 returns at most 5 entries and count reflects filter total""" + query = { + "query": """ + query PluginsObjectsPaged($options: PluginQueryOptionsInput) { + pluginsObjects(options: $options) { + dbCount + count + entries { index plugin } + } + } + """, + "variables": {"options": {"page": 1, "limit": 5}} + } + resp = client.post("/graphql", json=query, headers=auth_headers(api_token)) + assert resp.status_code == 200 + body = resp.get_json() + assert "errors" not in body + result = body["data"]["pluginsObjects"] + assert len(result["entries"]) <= 5 + assert result["count"] >= len(result["entries"]) + + +def test_graphql_plugins_events_no_options(client, api_token): + """pluginsEvents without options returns valid schema""" + query = { + "query": """ + { + pluginsEvents { + dbCount + count + entries { index plugin objectPrimaryId dateTimeCreated } + } + } + """ + } + resp = client.post("/graphql", json=query, headers=auth_headers(api_token)) + assert resp.status_code == 200 + body = resp.get_json() + assert "errors" not in body + result = body["data"]["pluginsEvents"] + assert isinstance(result["entries"], list) + assert isinstance(result["count"], int) + + +def test_graphql_plugins_history_no_options(client, api_token): + """pluginsHistory without options returns valid schema""" + query = { + "query": """ + { + pluginsHistory { + dbCount + count + entries { index plugin watchedValue1 } + } + } + """ + } + resp = client.post("/graphql", json=query, headers=auth_headers(api_token)) + assert resp.status_code == 200 + body = resp.get_json() + assert "errors" not in body + result = body["data"]["pluginsHistory"] + assert isinstance(result["entries"], list) + assert isinstance(result["count"], int) + + +def test_graphql_plugins_hard_cap(client, api_token): + """limit=99999 is clamped server-side to at most 1000 entries""" + query = { + "query": """ + query PluginsHardCap($options: PluginQueryOptionsInput) { + pluginsObjects(options: $options) { + count + entries { index } + } + } + """, + "variables": {"options": {"page": 1, "limit": 99999}} + } + resp = client.post("/graphql", json=query, headers=auth_headers(api_token)) + assert resp.status_code == 200 + body = resp.get_json() + assert "errors" not in body + entries = body["data"]["pluginsObjects"]["entries"] + assert len(entries) <= 1000, f"Hard cap violated: got {len(entries)} entries" + + +# --- EVENTS TESTS --- + +def test_graphql_events_no_options(client, api_token): + """events without options returns valid schema (entries list + count fields)""" + query = { + "query": """ + { + events { + dbCount + count + entries { + eveMac + eveIp + eveDateTime + eveEventType + eveAdditionalInfo + } + } + } + """ + } + resp = client.post("/graphql", json=query, headers=auth_headers(api_token)) + assert resp.status_code == 200 + body = resp.get_json() + assert "errors" not in body + result = body["data"]["events"] + assert isinstance(result["entries"], list) + assert isinstance(result["count"], int) + assert isinstance(result["dbCount"], int) + + +def test_graphql_events_filter_by_mac(client, api_token): + """events filtered by eveMac='00:00:00:00:00:00' returns only that MAC (or empty)""" + query = { + "query": """ + query EventsByMac($options: EventQueryOptionsInput) { + events(options: $options) { + count + entries { eveMac eveEventType eveDateTime } + } + } + """, + "variables": {"options": {"eveMac": "00:00:00:00:00:00", "limit": 50}} + } + resp = client.post("/graphql", json=query, headers=auth_headers(api_token)) + assert resp.status_code == 200 + body = resp.get_json() + assert "errors" not in body + result = body["data"]["events"] + for entry in result["entries"]: + assert entry["eveMac"].upper() == "00:00:00:00:00:00", ( + f"MAC filter leaked a non-matching row: {entry['eveMac']}" + ) + + +# --- PLUGIN FILTER SCOPING TESTS --- + +def test_graphql_plugins_objects_dbcount_scoped_to_plugin(client, api_token): + """dbCount should reflect only the rows for the requested plugin, not the entire table.""" + # First, get the unscoped total + query_all = { + "query": "{ pluginsObjects { dbCount count } }" + } + resp_all = client.post("/graphql", json=query_all, headers=auth_headers(api_token)) + assert resp_all.status_code == 200 + total_all = resp_all.get_json()["data"]["pluginsObjects"]["dbCount"] + + # Now request a non-existent plugin — dbCount must be 0 + query_fake = { + "query": """ + query Scoped($options: PluginQueryOptionsInput) { + pluginsObjects(options: $options) { dbCount count entries { plugin } } + } + """, + "variables": {"options": {"plugin": "NONEXISTENT_PLUGIN_XYZ"}} + } + resp_fake = client.post("/graphql", json=query_fake, headers=auth_headers(api_token)) + assert resp_fake.status_code == 200 + body_fake = resp_fake.get_json() + assert "errors" not in body_fake + result_fake = body_fake["data"]["pluginsObjects"] + assert result_fake["dbCount"] == 0, ( + f"dbCount should be 0 for non-existent plugin, got {result_fake['dbCount']}" + ) + assert result_fake["count"] == 0 + assert result_fake["entries"] == [] + + +def test_graphql_plugins_objects_scoped_entries_match_plugin(client, api_token): + """When filtering by plugin, all returned entries must belong to that plugin.""" + # Get first available plugin prefix from the unscoped query + query_sample = { + "query": "{ pluginsObjects(options: {page: 1, limit: 1}) { entries { plugin } } }" + } + resp = client.post("/graphql", json=query_sample, headers=auth_headers(api_token)) + assert resp.status_code == 200 + entries = resp.get_json()["data"]["pluginsObjects"]["entries"] + if not entries: + pytest.skip("No plugin objects in database") + target = entries[0]["plugin"] + + # Query scoped to that plugin + query_scoped = { + "query": """ + query Scoped($options: PluginQueryOptionsInput) { + pluginsObjects(options: $options) { dbCount count entries { plugin } } + } + """, + "variables": {"options": {"plugin": target, "page": 1, "limit": 100}} + } + resp2 = client.post("/graphql", json=query_scoped, headers=auth_headers(api_token)) + assert resp2.status_code == 200 + result = resp2.get_json()["data"]["pluginsObjects"] + assert result["dbCount"] > 0 + for entry in result["entries"]: + assert entry["plugin"].upper() == target.upper(), ( + f"Plugin filter leaked: expected {target}, got {entry['plugin']}" + ) diff --git a/test/api_endpoints/test_sessions_endpoints.py b/test/api_endpoints/test_sessions_endpoints.py index 50ed1fd2..a59e7de9 100644 --- a/test/api_endpoints/test_sessions_endpoints.py +++ b/test/api_endpoints/test_sessions_endpoints.py @@ -160,6 +160,43 @@ def test_device_session_events(client, api_token, test_mac): assert isinstance(sessions, list) +def test_session_events_pagination(client, api_token): + """session-events supports page, limit, and returns total/recordsFiltered.""" + resp = client.get( + "/sessions/session-events?type=all&period=1 year&page=1&limit=5", + headers=auth_headers(api_token), + ) + assert resp.status_code == 200 + body = resp.json + assert "data" in body + assert "total" in body + assert "recordsFiltered" in body + assert isinstance(body["total"], int) + assert len(body["data"]) <= 5 + + +def test_session_events_sorting(client, api_token): + """session-events supports sortCol and sortDir without errors.""" + resp_desc = client.get( + "/sessions/session-events?type=all&period=1 year&page=1&limit=10&sortCol=0&sortDir=desc", + headers=auth_headers(api_token), + ) + assert resp_desc.status_code == 200 + desc_data = resp_desc.json["data"] + + resp_asc = client.get( + "/sessions/session-events?type=all&period=1 year&page=1&limit=10&sortCol=0&sortDir=asc", + headers=auth_headers(api_token), + ) + assert resp_asc.status_code == 200 + asc_data = resp_asc.json["data"] + + # If there are at least 2 rows, order should differ (or be identical if all same) + if len(desc_data) >= 2 and len(asc_data) >= 2: + # First row of desc should >= first row of asc (column 0 is the order column) + assert desc_data[0][0] >= asc_data[0][0] or desc_data == asc_data + + # ----------------------------- def test_delete_session(client, api_token, test_mac): # First create session From 7305fd78e30eed8dedb6ea98dab88200f75b256c Mon Sep 17 00:00:00 2001 From: "Jokob @NetAlertX" <96159884+jokob-sk@users.noreply.github.com> Date: Fri, 27 Mar 2026 06:51:17 +0000 Subject: [PATCH 020/189] fix(pagination): Ensure page number is always at least 1 in apply_common_pagination --- server/api_server/graphql_helpers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/api_server/graphql_helpers.py b/server/api_server/graphql_helpers.py index eeb17641..807c7cc9 100644 --- a/server/api_server/graphql_helpers.py +++ b/server/api_server/graphql_helpers.py @@ -47,7 +47,8 @@ def apply_common_pagination(data, options): # --- PAGINATE --- if options.page is not None and options.limit is not None: effective_limit = min(options.limit, _MAX_LIMIT) - start = (options.page - 1) * effective_limit + page = max(1, options.page) + start = (page - 1) * effective_limit end = start + effective_limit data = data[start:end] From 48454f6f2f10213af9cff89002847e38d27b4137 Mon Sep 17 00:00:00 2001 From: "Jokob @NetAlertX" <96159884+jokob-sk@users.noreply.github.com> Date: Fri, 27 Mar 2026 07:30:13 +0000 Subject: [PATCH 021/189] feat(plugins): Optimize badge fetching by using lightweight JSON instead of GraphQL --- front/pluginsCore.php | 75 ++++++++++++++++++------------------------- server/api.py | 2 ++ server/const.py | 5 +++ 3 files changed, 38 insertions(+), 44 deletions(-) diff --git a/front/pluginsCore.php b/front/pluginsCore.php index c76535b0..1065df12 100755 --- a/front/pluginsCore.php +++ b/front/pluginsCore.php @@ -348,51 +348,38 @@ function postPluginGraphQL(gqlField, prefix, foreignKey, dtRequest, callback) { }); } -// Fire a single batched GraphQL request to fetch the Objects dbCount for -// every plugin and populate the sidebar badges immediately on page load. -function prefetchPluginBadges() { - const apiToken = getSetting("API_TOKEN"); - const apiBase = getApiBase(); - const mac = $("#txtMacFilter").val(); - const foreignKey = (mac && mac !== "--") ? mac : null; - - // Build one aliased sub-query per visible plugin - const prefixes = pluginDefinitions - .filter(p => p.show_ui) - .map(p => p.unique_prefix); - - if (prefixes.length === 0) return; - - // GraphQL aliases must be valid identifiers — prefixes already are (A-Z0-9_) - const fkOpt = foreignKey ? `, foreignKey: "${foreignKey}"` : ''; - const fragments = prefixes.map(p => [ - `${p}_obj: pluginsObjects(options: {plugin: "${p}", page: 1, limit: 1${fkOpt}}) { dbCount }`, - `${p}_evt: pluginsEvents(options: {plugin: "${p}", page: 1, limit: 1${fkOpt}}) { dbCount }`, - `${p}_hist: pluginsHistory(options: {plugin: "${p}", page: 1, limit: 1${fkOpt}}) { dbCount }`, - ].join('\n ')).join('\n '); - - const query = `query BadgeCounts {\n ${fragments}\n }`; - - $.ajax({ - method: "POST", - url: `${apiBase}/graphql`, - headers: { "Authorization": `Bearer ${apiToken}`, "Content-Type": "application/json" }, - data: JSON.stringify({ query }), - success: function(response) { - if (response.errors) { - console.error("[plugins] badge prefetch errors:", response.errors); - return; - } - prefixes.forEach(p => { - const obj = response.data[`${p}_obj`]; - const evt = response.data[`${p}_evt`]; - const hist = response.data[`${p}_hist`]; - if (obj) { $(`#badge_${p}`).text(obj.dbCount); $(`#objCount_${p}`).text(obj.dbCount); } - if (evt) { $(`#evtCount_${p}`).text(evt.dbCount); } - if (hist) { $(`#histCount_${p}`).text(hist.dbCount); } - }); +// Fetch the lightweight plugins_stats.json (~1KB) and populate all badge +// and sub-tab counts instantly — no GraphQL, no 250MB file loads. +async function prefetchPluginBadges() { + try { + const stats = await fetchJson('table_plugins_stats.json'); + // Build lookup: { ARPSCAN: { objects: 42, events: 3, history: 100 }, ... } + const counts = {}; + for (const row of stats.data) { + const p = row.tableName; // 'objects' | 'events' | 'history' + const plugin = row.plugin; + if (!counts[plugin]) counts[plugin] = { objects: 0, events: 0, history: 0 }; + counts[plugin][p] = row.cnt; } - }); + for (const [prefix, c] of Object.entries(counts)) { + $(`#badge_${prefix}`).text(c.objects); + $(`#objCount_${prefix}`).text(c.objects); + $(`#evtCount_${prefix}`).text(c.events); + $(`#histCount_${prefix}`).text(c.history); + } + // Set 0 for plugins with no rows in any table + pluginDefinitions.filter(p => p.show_ui).forEach(p => { + const prefix = p.unique_prefix; + if (!counts[prefix]) { + $(`#badge_${prefix}`).text(0); + $(`#objCount_${prefix}`).text(0); + $(`#evtCount_${prefix}`).text(0); + $(`#histCount_${prefix}`).text(0); + } + }); + } catch (err) { + console.error('[plugins] badge prefetch failed:', err); + } } function generateTabs() { diff --git a/server/api.py b/server/api.py index fd775f47..cf4d2ad4 100755 --- a/server/api.py +++ b/server/api.py @@ -16,6 +16,7 @@ from const import ( sql_plugins_events, sql_plugins_history, sql_plugins_objects, + sql_plugins_stats, sql_language_strings, sql_notifications_all, sql_online_history, @@ -66,6 +67,7 @@ def update_api( ["plugins_events", sql_plugins_events], ["plugins_history", sql_plugins_history], ["plugins_objects", sql_plugins_objects], + ["plugins_stats", sql_plugins_stats], ["plugins_language_strings", sql_language_strings], ["notifications", sql_notifications_all], ["online_history", sql_online_history], diff --git a/server/const.py b/server/const.py index 97faa00b..1dc1e83b 100755 --- a/server/const.py +++ b/server/const.py @@ -120,6 +120,11 @@ sql_events_pending_alert = "SELECT * FROM Events where evePendingAlertEmail is sql_events_all = "SELECT rowid, * FROM Events ORDER BY eveDateTime DESC" sql_settings = "SELECT * FROM Settings" sql_plugins_objects = "SELECT * FROM Plugins_Objects" +sql_plugins_stats = """SELECT 'objects' AS tableName, plugin, COUNT(*) AS cnt FROM Plugins_Objects GROUP BY plugin + UNION ALL + SELECT 'events', plugin, COUNT(*) FROM Plugins_Events GROUP BY plugin + UNION ALL + SELECT 'history', plugin, COUNT(*) FROM Plugins_History GROUP BY plugin""" sql_language_strings = "SELECT * FROM Plugins_Language_Strings" sql_notifications_all = "SELECT * FROM Notifications" sql_online_history = "SELECT * FROM Online_History" From 4daead1f8fddc81b2a357a6696bb1f889aba783e Mon Sep 17 00:00:00 2001 From: "Jokob @NetAlertX" <96159884+jokob-sk@users.noreply.github.com> Date: Fri, 27 Mar 2026 08:08:44 +0000 Subject: [PATCH 022/189] feat(plugins): Enhance badge fetching with conditional JSON and GraphQL support --- front/pluginsCore.php | 66 ++++++++++++++++++++++++++++++++++--------- server/api.py | 7 +++++ 2 files changed, 60 insertions(+), 13 deletions(-) diff --git a/front/pluginsCore.php b/front/pluginsCore.php index 1065df12..1fa8ab13 100755 --- a/front/pluginsCore.php +++ b/front/pluginsCore.php @@ -348,28 +348,68 @@ function postPluginGraphQL(gqlField, prefix, foreignKey, dtRequest, callback) { }); } -// Fetch the lightweight plugins_stats.json (~1KB) and populate all badge -// and sub-tab counts instantly — no GraphQL, no 250MB file loads. +// Fetch badge counts for every plugin and populate sidebar + sub-tab counters. +// Fast path: static JSON (~1KB) when no MAC filter is active. +// Filtered path: batched GraphQL aliases when a foreignKey (MAC) is set. async function prefetchPluginBadges() { + const mac = $("#txtMacFilter").val(); + const foreignKey = (mac && mac !== "--") ? mac : null; + + const prefixes = pluginDefinitions + .filter(p => p.show_ui) + .map(p => p.unique_prefix); + + if (prefixes.length === 0) return; + try { - const stats = await fetchJson('table_plugins_stats.json'); - // Build lookup: { ARPSCAN: { objects: 42, events: 3, history: 100 }, ... } - const counts = {}; - for (const row of stats.data) { - const p = row.tableName; // 'objects' | 'events' | 'history' - const plugin = row.plugin; - if (!counts[plugin]) counts[plugin] = { objects: 0, events: 0, history: 0 }; - counts[plugin][p] = row.cnt; + let counts = {}; // { PREFIX: { objects: N, events: N, history: N } } + + if (!foreignKey) { + // ---- FAST PATH: lightweight pre-computed JSON ---- + const stats = await fetchJson('table_plugins_stats.json'); + for (const row of stats.data) { + const p = row.tableName; // 'objects' | 'events' | 'history' + const plugin = row.plugin; + if (!counts[plugin]) counts[plugin] = { objects: 0, events: 0, history: 0 }; + counts[plugin][p] = row.cnt; + } + } else { + // ---- FILTERED PATH: GraphQL with foreignKey ---- + const apiToken = getSetting("API_TOKEN"); + const apiBase = getApiBase(); + const fkOpt = `, foreignKey: "${foreignKey}"`; + const fragments = prefixes.map(p => [ + `${p}_obj: pluginsObjects(options: {plugin: "${p}", page: 1, limit: 1${fkOpt}}) { dbCount }`, + `${p}_evt: pluginsEvents(options: {plugin: "${p}", page: 1, limit: 1${fkOpt}}) { dbCount }`, + `${p}_hist: pluginsHistory(options: {plugin: "${p}", page: 1, limit: 1${fkOpt}}) { dbCount }`, + ].join('\n ')).join('\n '); + + const query = `query BadgeCounts {\n ${fragments}\n }`; + const response = await $.ajax({ + method: "POST", + url: `${apiBase}/graphql`, + headers: { "Authorization": `Bearer ${apiToken}`, "Content-Type": "application/json" }, + data: JSON.stringify({ query }), + }); + if (response.errors) { console.error("[plugins] badge GQL errors:", response.errors); return; } + for (const p of prefixes) { + counts[p] = { + objects: response.data[`${p}_obj`]?.dbCount ?? 0, + events: response.data[`${p}_evt`]?.dbCount ?? 0, + history: response.data[`${p}_hist`]?.dbCount ?? 0, + }; + } } + + // Update DOM for (const [prefix, c] of Object.entries(counts)) { $(`#badge_${prefix}`).text(c.objects); $(`#objCount_${prefix}`).text(c.objects); $(`#evtCount_${prefix}`).text(c.events); $(`#histCount_${prefix}`).text(c.history); } - // Set 0 for plugins with no rows in any table - pluginDefinitions.filter(p => p.show_ui).forEach(p => { - const prefix = p.unique_prefix; + // Zero out plugins with no rows in any table + prefixes.forEach(prefix => { if (!counts[prefix]) { $(`#badge_${prefix}`).text(0); $(`#objCount_${prefix}`).text(0); diff --git a/server/api.py b/server/api.py index cf4d2ad4..11834bc4 100755 --- a/server/api.py +++ b/server/api.py @@ -76,6 +76,13 @@ def update_api( ["custom_endpoint", conf.API_CUSTOM_SQL], ] + # plugins_stats is derived from plugins_objects/events/history — + # ensure it is refreshed when any of its sources are partially updated. + _STATS_SOURCES = {"plugins_objects", "plugins_events", "plugins_history"} + if updateOnlyDataSources and _STATS_SOURCES & set(updateOnlyDataSources): + if "plugins_stats" not in updateOnlyDataSources: + updateOnlyDataSources = list(updateOnlyDataSources) + ["plugins_stats"] + # Save selected database tables for dsSQL in dataSourcesSQLs: if not updateOnlyDataSources or dsSQL[0] in updateOnlyDataSources: From 7ef19b1c12cc9060528036faf7a3abc45cea0416 Mon Sep 17 00:00:00 2001 From: "Jokob @NetAlertX" <96159884+jokob-sk@users.noreply.github.com> Date: Fri, 27 Mar 2026 09:26:25 +0000 Subject: [PATCH 023/189] feat(plugins): Implement auto-hide functionality for empty plugin tabs --- front/pluginsCore.php | 73 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/front/pluginsCore.php b/front/pluginsCore.php index 1fa8ab13..4e0592ad 100755 --- a/front/pluginsCore.php +++ b/front/pluginsCore.php @@ -417,11 +417,84 @@ async function prefetchPluginBadges() { $(`#histCount_${prefix}`).text(0); } }); + + // Auto-hide tabs with zero results + autoHideEmptyTabs(counts, prefixes); + } catch (err) { console.error('[plugins] badge prefetch failed:', err); } } +// --------------------------------------------------------------- +// Hide plugin tabs (left-nav + pane) where all three counts are 0. +// Within visible plugins, hide inner sub-tabs whose count is 0. +// If the active tab was hidden, activate the first visible one. +function autoHideEmptyTabs(counts, prefixes) { + prefixes.forEach(prefix => { + const c = counts[prefix] || { objects: 0, events: 0, history: 0 }; + const total = c.objects + c.events + c.history; + const $li = $(`#tabs-location li:has(a[href="#${prefix}"])`); + const $pane = $(`#tabs-content-location > #${prefix}`); + + if (total === 0) { + // Hide the entire plugin tab + $li.hide(); + $pane.removeClass('active').hide(); + } else { + // Ensure visible (in case a previous filter hid it) + $li.show(); + $pane.show(); + + // Hide inner sub-tabs with zero count + const subTabs = [ + { href: `#objectsTarget_${prefix}`, count: c.objects }, + { href: `#eventsTarget_${prefix}`, count: c.events }, + { href: `#historyTarget_${prefix}`, count: c.history }, + ]; + + let activeSubHidden = false; + subTabs.forEach(st => { + const $subLi = $pane.find(`ul.nav-tabs li:has(a[href="${st.href}"])`); + const $subPane = $pane.find(st.href); + if (st.count === 0) { + if ($subLi.hasClass('active')) activeSubHidden = true; + $subLi.hide(); + $subPane.removeClass('active').hide(); + } else { + $subLi.show(); + $subPane.show(); + } + }); + + // If the active inner sub-tab was hidden, activate the first visible one + if (activeSubHidden) { + const $firstVisibleSubLi = $pane.find('ul.nav-tabs li:visible').first(); + if ($firstVisibleSubLi.length) { + $firstVisibleSubLi.addClass('active'); + const target = $firstVisibleSubLi.find('a').attr('href'); + $pane.find(target).addClass('active'); + } + } + } + }); + + // If the active left-nav tab was hidden, activate the first visible one + const $activeLi = $(`#tabs-location li.active:visible`); + if ($activeLi.length === 0) { + const $firstVisibleLi = $(`#tabs-location li:visible`).first(); + if ($firstVisibleLi.length) { + $firstVisibleLi.addClass('active'); + const targetPrefix = $firstVisibleLi.find('a').attr('href')?.replace('#', ''); + if (targetPrefix) { + $(`#tabs-content-location > #${targetPrefix}`).addClass('active'); + // Trigger shown.bs.tab so deferred DataTables initialize + $firstVisibleLi.find('a').tab('show'); + } + } + } +} + function generateTabs() { // Reset the tabs by clearing previous headers and content From 13e91731be80e3a0387369f188e0c372cfc9d6d4 Mon Sep 17 00:00:00 2001 From: "Jokob @NetAlertX" <96159884+jokob-sk@users.noreply.github.com> Date: Fri, 27 Mar 2026 09:49:21 +0000 Subject: [PATCH 024/189] feat(plugins): Improve auto-hide functionality for empty plugin tabs by ensuring proper visibility handling and Bootstrap integration --- front/pluginsCore.php | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/front/pluginsCore.php b/front/pluginsCore.php index 4e0592ad..36685e51 100755 --- a/front/pluginsCore.php +++ b/front/pluginsCore.php @@ -438,13 +438,14 @@ function autoHideEmptyTabs(counts, prefixes) { const $pane = $(`#tabs-content-location > #${prefix}`); if (total === 0) { - // Hide the entire plugin tab - $li.hide(); - $pane.removeClass('active').hide(); + // Hide the entire plugin tab and strip active from both nav item and pane + $li.removeClass('active').hide(); + $pane.removeClass('active').css('display', ''); } else { - // Ensure visible (in case a previous filter hid it) + // Ensure nav item visible (in case a previous filter hid it) $li.show(); - $pane.show(); + // Clear any inline display override so Bootstrap CSS controls pane visibility via .active + $pane.css('display', ''); // Hide inner sub-tabs with zero count const subTabs = [ @@ -460,20 +461,19 @@ function autoHideEmptyTabs(counts, prefixes) { if (st.count === 0) { if ($subLi.hasClass('active')) activeSubHidden = true; $subLi.hide(); - $subPane.removeClass('active').hide(); + $subPane.removeClass('active').css('display', ''); } else { $subLi.show(); - $subPane.show(); + $subPane.css('display', ''); } }); // If the active inner sub-tab was hidden, activate the first visible one + // via Bootstrap's tab lifecycle so shown.bs.tab fires for deferred DataTable init if (activeSubHidden) { - const $firstVisibleSubLi = $pane.find('ul.nav-tabs li:visible').first(); - if ($firstVisibleSubLi.length) { - $firstVisibleSubLi.addClass('active'); - const target = $firstVisibleSubLi.find('a').attr('href'); - $pane.find(target).addClass('active'); + const $firstVisibleSubA = $pane.find('ul.nav-tabs li:visible:first a'); + if ($firstVisibleSubA.length) { + $firstVisibleSubA.tab('show'); } } } From cd0a3f6de09095eecf46ed2b41db6470343370ce Mon Sep 17 00:00:00 2001 From: "Jokob @NetAlertX" <96159884+jokob-sk@users.noreply.github.com> Date: Fri, 27 Mar 2026 10:12:12 +0000 Subject: [PATCH 025/189] feat(plugins): Refactor auto-hide functionality to leverage Bootstrap's tab management for improved visibility handling --- front/pluginsCore.php | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/front/pluginsCore.php b/front/pluginsCore.php index 36685e51..2f7f9689 100755 --- a/front/pluginsCore.php +++ b/front/pluginsCore.php @@ -484,13 +484,10 @@ function autoHideEmptyTabs(counts, prefixes) { if ($activeLi.length === 0) { const $firstVisibleLi = $(`#tabs-location li:visible`).first(); if ($firstVisibleLi.length) { - $firstVisibleLi.addClass('active'); - const targetPrefix = $firstVisibleLi.find('a').attr('href')?.replace('#', ''); - if (targetPrefix) { - $(`#tabs-content-location > #${targetPrefix}`).addClass('active'); - // Trigger shown.bs.tab so deferred DataTables initialize - $firstVisibleLi.find('a').tab('show'); - } + // Let Bootstrap's .tab('show') manage the active class on both + // the
  • and the pane — adding it manually beforehand causes + // Bootstrap to bail out early without firing shown.bs.tab. + $firstVisibleLi.find('a').tab('show'); } } } From 77369c3ce8e23a90c7fb567d5178e890b75b56d3 Mon Sep 17 00:00:00 2001 From: "Jokob @NetAlertX" <96159884+jokob-sk@users.noreply.github.com> Date: Fri, 27 Mar 2026 10:41:18 +0000 Subject: [PATCH 026/189] feat(plugins): Optimize plugin badge fetching and rendering to prevent flicker and enhance visibility --- front/pluginsCore.php | 306 ++++++++++++++++++++---------------------- 1 file changed, 144 insertions(+), 162 deletions(-) diff --git a/front/pluginsCore.php b/front/pluginsCore.php index 2f7f9689..d08ee9ca 100755 --- a/front/pluginsCore.php +++ b/front/pluginsCore.php @@ -274,6 +274,9 @@ function genericSaveData (id) { // ----------------------------------------------------------------------------- pluginDefinitions = [] +// Global counts map, populated before tabs are rendered +let pluginCounts = {}; + async function getData() { try { showSpinner(); @@ -282,6 +285,10 @@ async function getData() { const plugins = await fetchJson('plugins.json'); pluginDefinitions = plugins.data; + // Fetch counts BEFORE rendering tabs so we can skip empty plugins (no flicker) + const prefixes = pluginDefinitions.filter(p => p.show_ui).map(p => p.unique_prefix); + pluginCounts = await fetchPluginCounts(prefixes); + generateTabs(); } catch (err) { console.error("Failed to load data", err); @@ -348,148 +355,117 @@ function postPluginGraphQL(gqlField, prefix, foreignKey, dtRequest, callback) { }); } -// Fetch badge counts for every plugin and populate sidebar + sub-tab counters. +// Fetch counts for all plugins. Returns { PREFIX: { objects, events, history } }. // Fast path: static JSON (~1KB) when no MAC filter is active. // Filtered path: batched GraphQL aliases when a foreignKey (MAC) is set. -async function prefetchPluginBadges() { +async function fetchPluginCounts(prefixes) { + if (prefixes.length === 0) return {}; + const mac = $("#txtMacFilter").val(); const foreignKey = (mac && mac !== "--") ? mac : null; + let counts = {}; - const prefixes = pluginDefinitions - .filter(p => p.show_ui) - .map(p => p.unique_prefix); - - if (prefixes.length === 0) return; - - try { - let counts = {}; // { PREFIX: { objects: N, events: N, history: N } } - - if (!foreignKey) { - // ---- FAST PATH: lightweight pre-computed JSON ---- - const stats = await fetchJson('table_plugins_stats.json'); - for (const row of stats.data) { - const p = row.tableName; // 'objects' | 'events' | 'history' - const plugin = row.plugin; - if (!counts[plugin]) counts[plugin] = { objects: 0, events: 0, history: 0 }; - counts[plugin][p] = row.cnt; - } - } else { - // ---- FILTERED PATH: GraphQL with foreignKey ---- - const apiToken = getSetting("API_TOKEN"); - const apiBase = getApiBase(); - const fkOpt = `, foreignKey: "${foreignKey}"`; - const fragments = prefixes.map(p => [ - `${p}_obj: pluginsObjects(options: {plugin: "${p}", page: 1, limit: 1${fkOpt}}) { dbCount }`, - `${p}_evt: pluginsEvents(options: {plugin: "${p}", page: 1, limit: 1${fkOpt}}) { dbCount }`, - `${p}_hist: pluginsHistory(options: {plugin: "${p}", page: 1, limit: 1${fkOpt}}) { dbCount }`, - ].join('\n ')).join('\n '); - - const query = `query BadgeCounts {\n ${fragments}\n }`; - const response = await $.ajax({ - method: "POST", - url: `${apiBase}/graphql`, - headers: { "Authorization": `Bearer ${apiToken}`, "Content-Type": "application/json" }, - data: JSON.stringify({ query }), - }); - if (response.errors) { console.error("[plugins] badge GQL errors:", response.errors); return; } - for (const p of prefixes) { - counts[p] = { - objects: response.data[`${p}_obj`]?.dbCount ?? 0, - events: response.data[`${p}_evt`]?.dbCount ?? 0, - history: response.data[`${p}_hist`]?.dbCount ?? 0, - }; - } + if (!foreignKey) { + // ---- FAST PATH: lightweight pre-computed JSON ---- + const stats = await fetchJson('table_plugins_stats.json'); + for (const row of stats.data) { + const p = row.tableName; // 'objects' | 'events' | 'history' + const plugin = row.plugin; + if (!counts[plugin]) counts[plugin] = { objects: 0, events: 0, history: 0 }; + counts[plugin][p] = row.cnt; } + } else { + // ---- FILTERED PATH: GraphQL with foreignKey ---- + const apiToken = getSetting("API_TOKEN"); + const apiBase = getApiBase(); + const fkOpt = `, foreignKey: "${foreignKey}"`; + const fragments = prefixes.map(p => [ + `${p}_obj: pluginsObjects(options: {plugin: "${p}", page: 1, limit: 1${fkOpt}}) { dbCount }`, + `${p}_evt: pluginsEvents(options: {plugin: "${p}", page: 1, limit: 1${fkOpt}}) { dbCount }`, + `${p}_hist: pluginsHistory(options: {plugin: "${p}", page: 1, limit: 1${fkOpt}}) { dbCount }`, + ].join('\n ')).join('\n '); - // Update DOM - for (const [prefix, c] of Object.entries(counts)) { - $(`#badge_${prefix}`).text(c.objects); - $(`#objCount_${prefix}`).text(c.objects); - $(`#evtCount_${prefix}`).text(c.events); - $(`#histCount_${prefix}`).text(c.history); - } - // Zero out plugins with no rows in any table - prefixes.forEach(prefix => { - if (!counts[prefix]) { - $(`#badge_${prefix}`).text(0); - $(`#objCount_${prefix}`).text(0); - $(`#evtCount_${prefix}`).text(0); - $(`#histCount_${prefix}`).text(0); - } + const query = `query BadgeCounts {\n ${fragments}\n }`; + const response = await $.ajax({ + method: "POST", + url: `${apiBase}/graphql`, + headers: { "Authorization": `Bearer ${apiToken}`, "Content-Type": "application/json" }, + data: JSON.stringify({ query }), }); - - // Auto-hide tabs with zero results - autoHideEmptyTabs(counts, prefixes); - - } catch (err) { - console.error('[plugins] badge prefetch failed:', err); + if (response.errors) { console.error("[plugins] badge GQL errors:", response.errors); return counts; } + for (const p of prefixes) { + counts[p] = { + objects: response.data[`${p}_obj`]?.dbCount ?? 0, + events: response.data[`${p}_evt`]?.dbCount ?? 0, + history: response.data[`${p}_hist`]?.dbCount ?? 0, + }; + } } + + return counts; } -// --------------------------------------------------------------- -// Hide plugin tabs (left-nav + pane) where all three counts are 0. -// Within visible plugins, hide inner sub-tabs whose count is 0. -// If the active tab was hidden, activate the first visible one. -function autoHideEmptyTabs(counts, prefixes) { +// Apply pre-fetched counts to the DOM badges and hide empty tabs/sub-tabs. +function applyPluginBadges(counts, prefixes) { + // Update DOM badges + for (const [prefix, c] of Object.entries(counts)) { + $(`#badge_${prefix}`).text(c.objects); + $(`#objCount_${prefix}`).text(c.objects); + $(`#evtCount_${prefix}`).text(c.events); + $(`#histCount_${prefix}`).text(c.history); + } + // Zero out plugins with no rows in any table prefixes.forEach(prefix => { - const c = counts[prefix] || { objects: 0, events: 0, history: 0 }; - const total = c.objects + c.events + c.history; - const $li = $(`#tabs-location li:has(a[href="#${prefix}"])`); - const $pane = $(`#tabs-content-location > #${prefix}`); - - if (total === 0) { - // Hide the entire plugin tab and strip active from both nav item and pane - $li.removeClass('active').hide(); - $pane.removeClass('active').css('display', ''); - } else { - // Ensure nav item visible (in case a previous filter hid it) - $li.show(); - // Clear any inline display override so Bootstrap CSS controls pane visibility via .active - $pane.css('display', ''); - - // Hide inner sub-tabs with zero count - const subTabs = [ - { href: `#objectsTarget_${prefix}`, count: c.objects }, - { href: `#eventsTarget_${prefix}`, count: c.events }, - { href: `#historyTarget_${prefix}`, count: c.history }, - ]; - - let activeSubHidden = false; - subTabs.forEach(st => { - const $subLi = $pane.find(`ul.nav-tabs li:has(a[href="${st.href}"])`); - const $subPane = $pane.find(st.href); - if (st.count === 0) { - if ($subLi.hasClass('active')) activeSubHidden = true; - $subLi.hide(); - $subPane.removeClass('active').css('display', ''); - } else { - $subLi.show(); - $subPane.css('display', ''); - } - }); - - // If the active inner sub-tab was hidden, activate the first visible one - // via Bootstrap's tab lifecycle so shown.bs.tab fires for deferred DataTable init - if (activeSubHidden) { - const $firstVisibleSubA = $pane.find('ul.nav-tabs li:visible:first a'); - if ($firstVisibleSubA.length) { - $firstVisibleSubA.tab('show'); - } - } + if (!counts[prefix]) { + $(`#badge_${prefix}`).text(0); + $(`#objCount_${prefix}`).text(0); + $(`#evtCount_${prefix}`).text(0); + $(`#histCount_${prefix}`).text(0); } }); - // If the active left-nav tab was hidden, activate the first visible one - const $activeLi = $(`#tabs-location li.active:visible`); - if ($activeLi.length === 0) { - const $firstVisibleLi = $(`#tabs-location li:visible`).first(); - if ($firstVisibleLi.length) { - // Let Bootstrap's .tab('show') manage the active class on both - // the
  • and the pane — adding it manually beforehand causes - // Bootstrap to bail out early without firing shown.bs.tab. - $firstVisibleLi.find('a').tab('show'); + // Auto-hide sub-tabs with zero results (outer tabs already excluded during creation) + autoHideEmptyTabs(counts, prefixes); +} + +// --------------------------------------------------------------- +// Within visible plugins, hide inner sub-tabs (Objects/Events/History) whose count is 0. +// Outer plugin tabs with zero total are already excluded during tab creation. +function autoHideEmptyTabs(counts, prefixes) { + prefixes.forEach(prefix => { + const c = counts[prefix] || { objects: 0, events: 0, history: 0 }; + const $pane = $(`#tabs-content-location > #${prefix}`); + + // Hide inner sub-tabs with zero count + const subTabs = [ + { href: `#objectsTarget_${prefix}`, count: c.objects }, + { href: `#eventsTarget_${prefix}`, count: c.events }, + { href: `#historyTarget_${prefix}`, count: c.history }, + ]; + + let activeSubHidden = false; + subTabs.forEach(st => { + const $subLi = $pane.find(`ul.nav-tabs li:has(a[href="${st.href}"])`); + const $subPane = $pane.find(st.href); + if (st.count === 0) { + if ($subLi.hasClass('active')) activeSubHidden = true; + $subLi.hide(); + $subPane.removeClass('active').css('display', ''); + } else { + $subLi.show(); + $subPane.css('display', ''); + } + }); + + // If the active inner sub-tab was hidden, activate the first visible one + // via Bootstrap's tab lifecycle so shown.bs.tab fires for deferred DataTable init + if (activeSubHidden) { + const $firstVisibleSubA = $pane.find('ul.nav-tabs li:visible:first a'); + if ($firstVisibleSubA.length) { + $firstVisibleSubA.tab('show'); + } } - } + }); } function generateTabs() { @@ -502,31 +478,36 @@ function generateTabs() { let assignActive = true; - // Iterate over the sorted pluginDefinitions to create tab headers and content - pluginDefinitions.forEach(pluginObj => { - if (pluginObj.show_ui) { - createTabContent(pluginObj, assignActive); - createTabHeader(pluginObj, assignActive); - assignActive = false; - } + // Build list of visible plugins (skip plugins with 0 total count) + const visiblePlugins = pluginDefinitions.filter(pluginObj => { + if (!pluginObj.show_ui) return false; + const c = pluginCounts[pluginObj.unique_prefix] || { objects: 0, events: 0, history: 0 }; + return (c.objects + c.events + c.history) > 0; + }); + + // Create tab DOM for visible plugins only — no flicker + visiblePlugins.forEach(pluginObj => { + const prefix = pluginObj.unique_prefix; + const c = pluginCounts[prefix] || { objects: 0, events: 0, history: 0 }; + createTabContent(pluginObj, assignActive, c); + createTabHeader(pluginObj, assignActive, c); + assignActive = false; }); // Now that ALL DOM elements exist (both headers and tab panes), // wire up DataTable initialization: immediate for the active tab, // deferred via shown.bs.tab for the rest. let firstVisible = true; - pluginDefinitions.forEach(pluginObj => { - if (pluginObj.show_ui) { - const prefix = pluginObj.unique_prefix; - const colDefinitions = getColumnDefinitions(pluginObj); - if (firstVisible) { + visiblePlugins.forEach(pluginObj => { + const prefix = pluginObj.unique_prefix; + const colDefinitions = getColumnDefinitions(pluginObj); + if (firstVisible) { + initializeDataTables(prefix, colDefinitions, pluginObj); + firstVisible = false; + } else { + $(`a[href="#${prefix}"]`).one('shown.bs.tab', function() { initializeDataTables(prefix, colDefinitions, pluginObj); - firstVisible = false; - } else { - $(`a[href="#${prefix}"]`).one('shown.bs.tab', function() { - initializeDataTables(prefix, colDefinitions, pluginObj); - }); - } + }); } }); @@ -538,8 +519,9 @@ function generateTabs() { tabContainer: '#tabs-location' }); - // Pre-fetch badge counts for every plugin in a single batched GraphQL call. - prefetchPluginBadges(); + // Apply badge counts to the DOM and hide empty inner sub-tabs + const prefixes = visiblePlugins.map(p => p.unique_prefix); + applyPluginBadges(pluginCounts, prefixes); hideSpinner() } @@ -552,20 +534,18 @@ function resetTabs() { // --------------------------------------------------------------- // left headers -function createTabHeader(pluginObj, assignActive) { - const prefix = pluginObj.unique_prefix; // Get the unique prefix for the plugin - - // Determine the active class for the first tab +function createTabHeader(pluginObj, assignActive, counts) { + const prefix = pluginObj.unique_prefix; const activeClass = assignActive ? "active" : ""; + const badgeText = counts ? counts.objects : '…'; - // Append the tab header to the tabs location $('#tabs-location').append(`
  • ${getString(`${prefix}_icon`)} ${getString(`${prefix}_display_name`)} -
    +
    ${badgeText}
  • `); @@ -573,14 +553,13 @@ function createTabHeader(pluginObj, assignActive) { // --------------------------------------------------------------- // Content of selected plugin (header) -function createTabContent(pluginObj, assignActive) { - const prefix = pluginObj.unique_prefix; // Get the unique prefix for the plugin - const colDefinitions = getColumnDefinitions(pluginObj); // Get column definitions for DataTables +function createTabContent(pluginObj, assignActive, counts) { + const prefix = pluginObj.unique_prefix; + const colDefinitions = getColumnDefinitions(pluginObj); - // Append the content structure for the plugin's tab to the content location $('#tabs-content-location').append(`
    - ${generateTabNavigation(prefix)} + ${generateTabNavigation(prefix, counts)}
    ${generateDataTable(prefix, 'Objects', colDefinitions)} ${generateDataTable(prefix, 'Events', colDefinitions)} @@ -601,19 +580,22 @@ function getColumnDefinitions(pluginObj) { return pluginObj["database_column_definitions"].filter(colDef => colDef.show); } -function generateTabNavigation(prefix) { - // Create navigation tabs for Objects, Unprocessed Events, and History +function generateTabNavigation(prefix, counts) { + const objCount = counts ? counts.objects : '…'; + const evtCount = counts ? counts.events : '…'; + const histCount = counts ? counts.history : '…'; + return ` From b18cf98266a75a55254be02d9d2cdf92ca357e15 Mon Sep 17 00:00:00 2001 From: "Jokob @NetAlertX" <96159884+jokob-sk@users.noreply.github.com> Date: Fri, 27 Mar 2026 10:59:22 +0000 Subject: [PATCH 027/189] feat(plugins): Enhance plugin counts handling with fail-open support and improved comments --- front/pluginsCore.php | 140 +++++++++++++++++++++++++----------------- 1 file changed, 83 insertions(+), 57 deletions(-) diff --git a/front/pluginsCore.php b/front/pluginsCore.php index d08ee9ca..94835edd 100755 --- a/front/pluginsCore.php +++ b/front/pluginsCore.php @@ -274,8 +274,9 @@ function genericSaveData (id) { // ----------------------------------------------------------------------------- pluginDefinitions = [] -// Global counts map, populated before tabs are rendered -let pluginCounts = {}; +// Global counts map, populated before tabs are rendered. +// null = counts unavailable (fail-open: show all plugins) +let pluginCounts = null; async function getData() { try { @@ -285,7 +286,8 @@ async function getData() { const plugins = await fetchJson('plugins.json'); pluginDefinitions = plugins.data; - // Fetch counts BEFORE rendering tabs so we can skip empty plugins (no flicker) + // Fetch counts BEFORE rendering tabs so we can skip empty plugins (no flicker). + // fetchPluginCounts never throws — returns null on failure (fail-open). const prefixes = pluginDefinitions.filter(p => p.show_ui).map(p => p.unique_prefix); pluginCounts = await fetchPluginCounts(prefixes); @@ -355,54 +357,63 @@ function postPluginGraphQL(gqlField, prefix, foreignKey, dtRequest, callback) { }); } -// Fetch counts for all plugins. Returns { PREFIX: { objects, events, history } }. +// Fetch counts for all plugins. Returns { PREFIX: { objects, events, history } } +// or null on failure (fail-open so tabs still render). // Fast path: static JSON (~1KB) when no MAC filter is active. // Filtered path: batched GraphQL aliases when a foreignKey (MAC) is set. async function fetchPluginCounts(prefixes) { if (prefixes.length === 0) return {}; - const mac = $("#txtMacFilter").val(); - const foreignKey = (mac && mac !== "--") ? mac : null; - let counts = {}; + try { + const mac = $("#txtMacFilter").val(); + const foreignKey = (mac && mac !== "--") ? mac : null; + let counts = {}; - if (!foreignKey) { - // ---- FAST PATH: lightweight pre-computed JSON ---- - const stats = await fetchJson('table_plugins_stats.json'); - for (const row of stats.data) { - const p = row.tableName; // 'objects' | 'events' | 'history' - const plugin = row.plugin; - if (!counts[plugin]) counts[plugin] = { objects: 0, events: 0, history: 0 }; - counts[plugin][p] = row.cnt; - } - } else { - // ---- FILTERED PATH: GraphQL with foreignKey ---- - const apiToken = getSetting("API_TOKEN"); - const apiBase = getApiBase(); - const fkOpt = `, foreignKey: "${foreignKey}"`; - const fragments = prefixes.map(p => [ - `${p}_obj: pluginsObjects(options: {plugin: "${p}", page: 1, limit: 1${fkOpt}}) { dbCount }`, - `${p}_evt: pluginsEvents(options: {plugin: "${p}", page: 1, limit: 1${fkOpt}}) { dbCount }`, - `${p}_hist: pluginsHistory(options: {plugin: "${p}", page: 1, limit: 1${fkOpt}}) { dbCount }`, - ].join('\n ')).join('\n '); + if (!foreignKey) { + // ---- FAST PATH: lightweight pre-computed JSON ---- + const stats = await fetchJson('table_plugins_stats.json'); + for (const row of stats.data) { + const p = row.tableName; // 'objects' | 'events' | 'history' + const plugin = row.plugin; + if (!counts[plugin]) counts[plugin] = { objects: 0, events: 0, history: 0 }; + counts[plugin][p] = row.cnt; + } + } else { + // ---- FILTERED PATH: GraphQL with foreignKey ---- + const apiToken = getSetting("API_TOKEN"); + const apiBase = getApiBase(); + const fkOpt = `, foreignKey: "${foreignKey}"`; + const fragments = prefixes.map(p => [ + `${p}_obj: pluginsObjects(options: {plugin: "${p}", page: 1, limit: 1${fkOpt}}) { dbCount }`, + `${p}_evt: pluginsEvents(options: {plugin: "${p}", page: 1, limit: 1${fkOpt}}) { dbCount }`, + `${p}_hist: pluginsHistory(options: {plugin: "${p}", page: 1, limit: 1${fkOpt}}) { dbCount }`, + ].join('\n ')).join('\n '); - const query = `query BadgeCounts {\n ${fragments}\n }`; - const response = await $.ajax({ - method: "POST", - url: `${apiBase}/graphql`, - headers: { "Authorization": `Bearer ${apiToken}`, "Content-Type": "application/json" }, - data: JSON.stringify({ query }), - }); - if (response.errors) { console.error("[plugins] badge GQL errors:", response.errors); return counts; } - for (const p of prefixes) { - counts[p] = { - objects: response.data[`${p}_obj`]?.dbCount ?? 0, - events: response.data[`${p}_evt`]?.dbCount ?? 0, - history: response.data[`${p}_hist`]?.dbCount ?? 0, - }; + const query = `query BadgeCounts {\n ${fragments}\n }`; + const response = await $.ajax({ + method: "POST", + url: `${apiBase}/graphql`, + headers: { "Authorization": `Bearer ${apiToken}`, "Content-Type": "application/json" }, + data: JSON.stringify({ query }), + }); + if (response.errors) { + console.error("[plugins] badge GQL errors:", response.errors); + return null; // fail-open + } + for (const p of prefixes) { + counts[p] = { + objects: response.data[`${p}_obj`]?.dbCount ?? 0, + events: response.data[`${p}_evt`]?.dbCount ?? 0, + history: response.data[`${p}_hist`]?.dbCount ?? 0, + }; + } } + + return counts; + } catch (err) { + console.error('[plugins] fetchPluginCounts failed (fail-open):', err); + return null; } - - return counts; } // Apply pre-fetched counts to the DOM badges and hide empty tabs/sub-tabs. @@ -478,17 +489,20 @@ function generateTabs() { let assignActive = true; - // Build list of visible plugins (skip plugins with 0 total count) + // When counts are available, skip plugins with 0 total count (no flicker). + // When counts are null (fetch failed), show all show_ui plugins (fail-open). + const countsAvailable = pluginCounts !== null; const visiblePlugins = pluginDefinitions.filter(pluginObj => { if (!pluginObj.show_ui) return false; + if (!countsAvailable) return true; // fail-open: show all const c = pluginCounts[pluginObj.unique_prefix] || { objects: 0, events: 0, history: 0 }; return (c.objects + c.events + c.history) > 0; }); - // Create tab DOM for visible plugins only — no flicker + // Create tab DOM for visible plugins only visiblePlugins.forEach(pluginObj => { const prefix = pluginObj.unique_prefix; - const c = pluginCounts[prefix] || { objects: 0, events: 0, history: 0 }; + const c = countsAvailable ? (pluginCounts[prefix] || { objects: 0, events: 0, history: 0 }) : null; createTabContent(pluginObj, assignActive, c); createTabHeader(pluginObj, assignActive, c); assignActive = false; @@ -519,9 +533,11 @@ function generateTabs() { tabContainer: '#tabs-location' }); - // Apply badge counts to the DOM and hide empty inner sub-tabs - const prefixes = visiblePlugins.map(p => p.unique_prefix); - applyPluginBadges(pluginCounts, prefixes); + // Apply badge counts to the DOM and hide empty inner sub-tabs (only if counts loaded) + if (countsAvailable) { + const prefixes = visiblePlugins.map(p => p.unique_prefix); + applyPluginBadges(pluginCounts, prefixes); + } hideSpinner() } @@ -664,17 +680,27 @@ function initializeDataTables(prefix, colDefinitions, pluginObj) { }); } - // Initialize the Objects table immediately (it is the active/visible sub-tab). - // Defer Events and History tables until their sub-tab is first shown. + // Initialize the DataTable for whichever inner sub-tab is currently active + // (may not be Objects if autoHideEmptyTabs switched it). + // Defer the remaining sub-tabs until their shown.bs.tab fires. const [objCfg, evtCfg, histCfg] = tableConfigs; - buildDT(objCfg.tableId, objCfg.gqlField, objCfg.countId, objCfg.badgeId); + const allCfgs = [ + { cfg: objCfg, href: `#objectsTarget_${prefix}` }, + { cfg: evtCfg, href: `#eventsTarget_${prefix}` }, + { cfg: histCfg, href: `#historyTarget_${prefix}` }, + ]; - $(`a[href="#eventsTarget_${prefix}"]`).one('shown.bs.tab', function() { - buildDT(evtCfg.tableId, evtCfg.gqlField, evtCfg.countId, evtCfg.badgeId); - }); - - $(`a[href="#historyTarget_${prefix}"]`).one('shown.bs.tab', function() { - buildDT(histCfg.tableId, histCfg.gqlField, histCfg.countId, histCfg.badgeId); + allCfgs.forEach(({ cfg, href }) => { + const $subPane = $(href); + if ($subPane.hasClass('active') && $subPane.is(':visible')) { + // This sub-tab is the currently active one — initialize immediately + buildDT(cfg.tableId, cfg.gqlField, cfg.countId, cfg.badgeId); + } else if ($subPane.closest('.tab-pane').length) { + // Defer until shown + $(`a[href="${href}"]`).one('shown.bs.tab', function() { + buildDT(cfg.tableId, cfg.gqlField, cfg.countId, cfg.badgeId); + }); + } }); } From 3f80d2e57f22f812d04ee960b2c5d1dbc93f9640 Mon Sep 17 00:00:00 2001 From: "Jokob @NetAlertX" <96159884+jokob-sk@users.noreply.github.com> Date: Fri, 27 Mar 2026 21:35:41 +0000 Subject: [PATCH 028/189] feat(plugins): Implement /plugins/stats endpoint for per-plugin row counts with optional foreignKey filtering --- front/pluginsCore.php | 56 ++++++--------- server/api_server/api_server_start.py | 29 ++++++++ server/api_server/openapi/schemas.py | 29 ++++++++ server/models/plugin_object_instance.py | 28 ++++++++ .../test_plugin_stats_endpoints.py | 72 +++++++++++++++++++ 5 files changed, 180 insertions(+), 34 deletions(-) create mode 100644 test/api_endpoints/test_plugin_stats_endpoints.py diff --git a/front/pluginsCore.php b/front/pluginsCore.php index 94835edd..868a36d7 100755 --- a/front/pluginsCore.php +++ b/front/pluginsCore.php @@ -359,56 +359,44 @@ function postPluginGraphQL(gqlField, prefix, foreignKey, dtRequest, callback) { // Fetch counts for all plugins. Returns { PREFIX: { objects, events, history } } // or null on failure (fail-open so tabs still render). -// Fast path: static JSON (~1KB) when no MAC filter is active. -// Filtered path: batched GraphQL aliases when a foreignKey (MAC) is set. +// Unfiltered: static JSON (~1KB pre-computed). +// MAC-filtered: lightweight REST endpoint (single SQL query). async function fetchPluginCounts(prefixes) { if (prefixes.length === 0) return {}; + const mac = $("#txtMacFilter").val(); + const foreignKey = (mac && mac !== "--") ? mac : null; + try { - const mac = $("#txtMacFilter").val(); - const foreignKey = (mac && mac !== "--") ? mac : null; let counts = {}; + let rows; if (!foreignKey) { - // ---- FAST PATH: lightweight pre-computed JSON ---- + // ---- FAST PATH: pre-computed static JSON ---- const stats = await fetchJson('table_plugins_stats.json'); - for (const row of stats.data) { - const p = row.tableName; // 'objects' | 'events' | 'history' - const plugin = row.plugin; - if (!counts[plugin]) counts[plugin] = { objects: 0, events: 0, history: 0 }; - counts[plugin][p] = row.cnt; - } + rows = stats.data; } else { - // ---- FILTERED PATH: GraphQL with foreignKey ---- + // ---- MAC-FILTERED PATH: single SQL via REST endpoint ---- const apiToken = getSetting("API_TOKEN"); const apiBase = getApiBase(); - const fkOpt = `, foreignKey: "${foreignKey}"`; - const fragments = prefixes.map(p => [ - `${p}_obj: pluginsObjects(options: {plugin: "${p}", page: 1, limit: 1${fkOpt}}) { dbCount }`, - `${p}_evt: pluginsEvents(options: {plugin: "${p}", page: 1, limit: 1${fkOpt}}) { dbCount }`, - `${p}_hist: pluginsHistory(options: {plugin: "${p}", page: 1, limit: 1${fkOpt}}) { dbCount }`, - ].join('\n ')).join('\n '); - - const query = `query BadgeCounts {\n ${fragments}\n }`; const response = await $.ajax({ - method: "POST", - url: `${apiBase}/graphql`, - headers: { "Authorization": `Bearer ${apiToken}`, "Content-Type": "application/json" }, - data: JSON.stringify({ query }), + method: "GET", + url: `${apiBase}/plugins/stats?foreignKey=${encodeURIComponent(foreignKey)}`, + headers: { "Authorization": `Bearer ${apiToken}` }, }); - if (response.errors) { - console.error("[plugins] badge GQL errors:", response.errors); - return null; // fail-open - } - for (const p of prefixes) { - counts[p] = { - objects: response.data[`${p}_obj`]?.dbCount ?? 0, - events: response.data[`${p}_evt`]?.dbCount ?? 0, - history: response.data[`${p}_hist`]?.dbCount ?? 0, - }; + if (!response.success) { + console.error("[plugins] /plugins/stats error:", response.error); + return null; } + rows = response.data; } + for (const row of rows) { + const p = row.tableName; // 'objects' | 'events' | 'history' + const plugin = row.plugin; + if (!counts[plugin]) counts[plugin] = { objects: 0, events: 0, history: 0 }; + counts[plugin][p] = row.cnt; + } return counts; } catch (err) { console.error('[plugins] fetchPluginCounts failed (fail-open):', err); diff --git a/server/api_server/api_server_start.py b/server/api_server/api_server_start.py index 0f1de544..f85f9a57 100755 --- a/server/api_server/api_server_start.py +++ b/server/api_server/api_server_start.py @@ -43,6 +43,7 @@ from .sync_endpoint import handle_sync_post, handle_sync_get # noqa: E402 [flak from .logs_endpoint import clean_log # noqa: E402 [flake8 lint suppression] from .health_endpoint import get_health_status # noqa: E402 [flake8 lint suppression] from .languages_endpoint import get_languages # noqa: E402 [flake8 lint suppression] +from models.plugin_object_instance import PluginObjectInstance # noqa: E402 [flake8 lint suppression] from models.user_events_queue_instance import UserEventsQueueInstance # noqa: E402 [flake8 lint suppression] from models.event_instance import EventInstance # noqa: E402 [flake8 lint suppression] @@ -97,6 +98,7 @@ from .openapi.schemas import ( # noqa: E402 [flake8 lint suppression] AddToQueueRequest, GetSettingResponse, RecentEventsRequest, SetDeviceAliasRequest, LanguagesResponse, + PluginStatsResponse, ) from .sse_endpoint import ( # noqa: E402 [flake8 lint suppression] @@ -2002,6 +2004,33 @@ def list_languages(payload=None): }), 500 +# -------------------------- +# Plugin Stats endpoint +# -------------------------- +@app.route("/plugins/stats", methods=["GET"]) +@validate_request( + operation_id="get_plugin_stats", + summary="Get Plugin Row Counts", + description="Return per-plugin row counts across Objects, Events, and History tables. Optionally filter by foreignKey (MAC).", + response_model=PluginStatsResponse, + tags=["plugins"], + auth_callable=is_authorized, + query_params=[{ + "name": "foreignKey", + "in": "query", + "required": False, + "description": "Filter counts to rows matching this foreignKey (typically a MAC address)", + "schema": {"type": "string"} + }] +) +def api_plugin_stats(payload=None): + """Get per-plugin row counts, optionally filtered by foreignKey.""" + foreign_key = request.args.get("foreignKey", None) + handler = PluginObjectInstance() + data = handler.getStats(foreign_key) + return jsonify({"success": True, "data": data}) + + # -------------------------- # Background Server Start # -------------------------- diff --git a/server/api_server/openapi/schemas.py b/server/api_server/openapi/schemas.py index 4d7d3127..7310f637 100644 --- a/server/api_server/openapi/schemas.py +++ b/server/api_server/openapi/schemas.py @@ -1084,3 +1084,32 @@ class GraphQLRequest(BaseModel): """Request payload for GraphQL queries.""" query: str = Field(..., description="GraphQL query string", json_schema_extra={"examples": ["{ devices { devMac devName } }"]}) variables: Optional[Dict[str, Any]] = Field(None, description="Variables for the GraphQL query") + + +# ============================================================================= +# PLUGIN SCHEMAS +# ============================================================================= +class PluginStatsEntry(BaseModel): + """Per-plugin row count for one table.""" + tableName: str = Field(..., description="Table category: objects, events, or history") + plugin: str = Field(..., description="Plugin unique prefix") + cnt: int = Field(..., ge=0, description="Row count") + + +class PluginStatsResponse(BaseResponse): + """Response for GET /plugins/stats — per-plugin row counts.""" + model_config = ConfigDict( + extra="allow", + json_schema_extra={ + "examples": [{ + "success": True, + "data": [ + {"tableName": "objects", "plugin": "ARPSCAN", "cnt": 42}, + {"tableName": "events", "plugin": "ARPSCAN", "cnt": 5}, + {"tableName": "history", "plugin": "ARPSCAN", "cnt": 100} + ] + }] + } + ) + + data: List[PluginStatsEntry] = Field(default_factory=list, description="Per-plugin row counts") diff --git a/server/models/plugin_object_instance.py b/server/models/plugin_object_instance.py index 5018b6d5..89eb69e7 100755 --- a/server/models/plugin_object_instance.py +++ b/server/models/plugin_object_instance.py @@ -101,3 +101,31 @@ class PluginObjectInstance: raise ValueError(msg) self._execute("DELETE FROM Plugins_Objects WHERE objectGuid=?", (ObjectGUID,)) + + def getStats(self, foreign_key=None): + """Per-plugin row counts across Objects, Events, and History tables. + Optionally scoped to a specific foreignKey (e.g. MAC address).""" + if foreign_key: + sql = """ + SELECT 'objects' AS tableName, plugin, COUNT(*) AS cnt + FROM Plugins_Objects WHERE foreignKey = ? GROUP BY plugin + UNION ALL + SELECT 'events', plugin, COUNT(*) + FROM Plugins_Events WHERE foreignKey = ? GROUP BY plugin + UNION ALL + SELECT 'history', plugin, COUNT(*) + FROM Plugins_History WHERE foreignKey = ? GROUP BY plugin + """ + return self._fetchall(sql, (foreign_key, foreign_key, foreign_key)) + else: + sql = """ + SELECT 'objects' AS tableName, plugin, COUNT(*) AS cnt + FROM Plugins_Objects GROUP BY plugin + UNION ALL + SELECT 'events', plugin, COUNT(*) + FROM Plugins_Events GROUP BY plugin + UNION ALL + SELECT 'history', plugin, COUNT(*) + FROM Plugins_History GROUP BY plugin + """ + return self._fetchall(sql) diff --git a/test/api_endpoints/test_plugin_stats_endpoints.py b/test/api_endpoints/test_plugin_stats_endpoints.py new file mode 100644 index 00000000..3633b79f --- /dev/null +++ b/test/api_endpoints/test_plugin_stats_endpoints.py @@ -0,0 +1,72 @@ +"""Tests for /plugins/stats endpoint.""" + +import sys +import os +import pytest + +INSTALL_PATH = os.getenv("NETALERTX_APP", "/app") +sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"]) + +from helper import get_setting_value # noqa: E402 +from api_server.api_server_start import app # noqa: E402 + + +@pytest.fixture(scope="session") +def api_token(): + return get_setting_value("API_TOKEN") + + +@pytest.fixture +def client(): + with app.test_client() as client: + yield client + + +def auth_headers(token): + return {"Authorization": f"Bearer {token}"} + + +def test_plugin_stats_unauthorized(client): + """Missing token should be forbidden.""" + resp = client.get("/plugins/stats") + assert resp.status_code == 403 + assert resp.get_json().get("success") is False + + +def test_plugin_stats_success(client, api_token): + """Valid token returns success with data array.""" + resp = client.get("/plugins/stats", headers=auth_headers(api_token)) + assert resp.status_code == 200 + + data = resp.get_json() + assert data.get("success") is True + assert isinstance(data.get("data"), list) + + +def test_plugin_stats_entry_structure(client, api_token): + """Each entry has tableName, plugin, cnt fields.""" + resp = client.get("/plugins/stats", headers=auth_headers(api_token)) + data = resp.get_json() + + for entry in data["data"]: + assert "tableName" in entry + assert "plugin" in entry + assert "cnt" in entry + assert entry["tableName"] in ("objects", "events", "history") + assert isinstance(entry["cnt"], int) + assert entry["cnt"] >= 0 + + +def test_plugin_stats_with_foreignkey(client, api_token): + """foreignKey param filters results and returns valid structure.""" + resp = client.get( + "/plugins/stats?foreignKey=00:00:00:00:00:00", + headers=auth_headers(api_token), + ) + assert resp.status_code == 200 + + data = resp.get_json() + assert data.get("success") is True + assert isinstance(data.get("data"), list) + # With a non-existent MAC, data should be empty + assert len(data["data"]) == 0 From 8b80a6d59c9b874c31e7b0820422e826ef8427a1 Mon Sep 17 00:00:00 2001 From: navnitan-7 Date: Tue, 31 Mar 2026 02:08:15 +0530 Subject: [PATCH 029/189] Security: jQuery ajaxConvert cross-domain script mitigation (CVE-2015-9251) Backport upstream jQuery gh-2432 logic in bundled DataTables/jQuery: skip inferred script conversion for cross-domain ajax responses. Refs: https://github.com/jquery/jquery/commit/2546bb35b89413da5198d54a4539e4ed0aaf6e49 Made-with: Cursor --- front/lib/datatables/datatables.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/front/lib/datatables/datatables.js b/front/lib/datatables/datatables.js index 6336121b..2027407d 100755 --- a/front/lib/datatables/datatables.js +++ b/front/lib/datatables/datatables.js @@ -9059,6 +9059,11 @@ function ajaxConvert( s, response, jqXHR, isSuccess ) { // Convert response if prev dataType is non-auto and differs from current } else if ( prev !== "*" && prev !== current ) { + // Mitigate possible XSS vulnerability (gh-2432) + if ( s.crossDomain && current === "script" ) { + continue; + } + // Seek a direct converter conv = converters[ prev + " " + current ] || converters[ "* " + current ]; From cfa75178a46ee6935e9a6e5e23af51896c0da775 Mon Sep 17 00:00:00 2001 From: jokob-sk Date: Fri, 3 Apr 2026 12:38:38 +1100 Subject: [PATCH 030/189] en_us has to be first --- .../php/templates/language/language_definitions/languages.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/php/templates/language/language_definitions/languages.json b/front/php/templates/language/language_definitions/languages.json index b106a0e4..eeec2772 100644 --- a/front/php/templates/language/language_definitions/languages.json +++ b/front/php/templates/language/language_definitions/languages.json @@ -1,11 +1,11 @@ { "default": "en_us", "languages": [ + { "code": "en_us", "display": "English (en_us)" }, { "code": "ar_ar", "display": "Arabic (ar_ar)" }, { "code": "ca_ca", "display": "Catalan (ca_ca)" }, { "code": "cs_cz", "display": "Czech (cs_cz)" }, { "code": "de_de", "display": "German (de_de)" }, - { "code": "en_us", "display": "English (en_us)" }, { "code": "es_es", "display": "Spanish (es_es)" }, { "code": "fa_fa", "display": "Farsi (fa_fa)" }, { "code": "id_id", "display": "Indonesian (id_id)" }, From 4f2fa86a493d8e94070d05a33a087eee9b7fdc70 Mon Sep 17 00:00:00 2001 From: "Jokob @NetAlertX" <96159884+jokob-sk@users.noreply.github.com> Date: Fri, 3 Apr 2026 01:56:42 +0000 Subject: [PATCH 031/189] feat(docs): Update coding standards to clarify database storage guidelines --- .github/skills/code-standards/SKILL.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/skills/code-standards/SKILL.md b/.github/skills/code-standards/SKILL.md index 00128188..37e6932b 100644 --- a/.github/skills/code-standards/SKILL.md +++ b/.github/skills/code-standards/SKILL.md @@ -13,6 +13,7 @@ description: NetAlertX coding standards and conventions. Use this when writing c - follow DRY principle - maintainability of code is more important than speed of implementation - code files should be less than 500 LOC for better maintainability - DB columns must not contain underscores, use camelCase instead (e.g., deviceInstanceId, not device_instance_id) +- treat DB as temporary storage for stats, long term configuration should be stored in the /config folder, the /config folder should allow you to restore most of your functionality (excluding historical data) ## File Length From 4c117db4635fcac91ecf634db4464617a9a22b19 Mon Sep 17 00:00:00 2001 From: sebingel Date: Fri, 3 Apr 2026 18:28:48 +0000 Subject: [PATCH 032/189] Fix elementOptions: rename typo 'ordeable' to 'orderable' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The key 'ordeable' in elementOptions was a long-standing typo for the correct English word 'orderable'. Since the JS check in settings_utils.js used the same misspelled key, the feature appeared to work — but it was relying on the consistent propagation of a typo across the entire codebase. Two pre-existing entries in front/plugins/ui_settings/config.json already used the correct spelling 'orderable', but these had no effect because the JavaScript check (option.ordeable === 'true') never matched them. As a result, orderable behavior was silently disabled for those two settings. Changes: - front/js/settings_utils.js: renamed option.ordeable → option.orderable and isOrdeable → isOrderable (6 occurrences, lines 792/823/824/880/1079/ 1192/1228). The JS key check is the authoritative definition of the elementOptions property name, so this must change atomically with all config files. - server/initialise.py:245: renamed "ordeable" → "orderable" in the hardcoded JSON string for LOADED_PLUGINS setting. This string is the source-of-truth for that setting's elementOptions and is not auto- generated from the plugin config files. - front/plugins/*/config.json (33 files, 90 occurrences): renamed all "ordeable": "true" entries to "orderable": "true" via sed. All plugins used the typo consistently; they must be updated in the same commit to avoid a broken intermediate state. The two formerly broken 'orderable' entries in ui_settings/config.json are now matched by the corrected JS check and work as intended. Fixes netalertx/NetAlertX#1584 Co-Authored-By: Claude Opus 4.6 --- front/js/settings_utils.js | 14 +++++++------- front/plugins/__template/config.json | 4 ++-- front/plugins/adguard_import/config.json | 4 ++-- front/plugins/arp_scan/config.json | 8 ++++---- front/plugins/asuswrt_import/config.json | 4 ++-- front/plugins/avahi_scan/config.json | 4 ++-- front/plugins/ddns_update/config.json | 4 ++-- front/plugins/dhcp_leases/config.json | 8 ++++---- front/plugins/dhcp_servers/config.json | 4 ++-- front/plugins/dig_scan/config.json | 4 ++-- front/plugins/freebox/config.json | 4 ++-- front/plugins/icmp_scan/config.json | 4 ++-- front/plugins/internet_ip/config.json | 8 ++++---- front/plugins/internet_speedtest/config.json | 4 ++-- front/plugins/ipneigh/config.json | 4 ++-- front/plugins/luci_import/config.json | 4 ++-- front/plugins/mikrotik_scan/config.json | 4 ++-- front/plugins/nbtscan_scan/config.json | 4 ++-- front/plugins/newdev_template/config.json | 6 +++--- front/plugins/nmap_dev_scan/config.json | 4 ++-- front/plugins/nmap_scan/config.json | 4 ++-- front/plugins/notification_processing/config.json | 2 +- front/plugins/nslookup_scan/config.json | 4 ++-- front/plugins/omada_sdn_imp/config.json | 8 ++++---- front/plugins/omada_sdn_openapi/config.json | 8 ++++---- front/plugins/pihole_api_scan/config.json | 4 ++-- front/plugins/pihole_scan/config.json | 8 ++++---- front/plugins/snmp_discovery/config.json | 8 ++++---- front/plugins/sync/config.json | 6 +++--- front/plugins/ui_settings/config.json | 14 +++++++------- front/plugins/unifi_api_import/config.json | 4 ++-- front/plugins/unifi_import/config.json | 8 ++++---- front/plugins/vendor_update/config.json | 8 ++++---- front/plugins/website_monitor/config.json | 4 ++-- server/initialise.py | 2 +- 35 files changed, 98 insertions(+), 98 deletions(-) diff --git a/front/js/settings_utils.js b/front/js/settings_utils.js index dfafde44..2c64e92a 100755 --- a/front/js/settings_utils.js +++ b/front/js/settings_utils.js @@ -789,7 +789,7 @@ const handleElementOptions = (setKey, elementOptions, transformers, val) => { let inputType = "text"; let readOnly = ""; let isMultiSelect = false; - let isOrdeable = false; + let isOrderable = false; let cssClasses = ""; let placeholder = ""; let suffix = ""; @@ -820,8 +820,8 @@ const handleElementOptions = (setKey, elementOptions, transformers, val) => { if (option.multiple === "true") { isMultiSelect = true; } - if (option.ordeable === "true") { - isOrdeable = true; + if (option.orderable === "true") { + isOrderable = true; } if (option.editable === "true") { editable = true; @@ -877,7 +877,7 @@ const handleElementOptions = (setKey, elementOptions, transformers, val) => { inputType, readOnly, isMultiSelect, - isOrdeable, + isOrderable, cssClasses, placeholder, suffix, @@ -1076,7 +1076,7 @@ function collectSetting(prefix, setCodeName, setType, settingsArray) { }, array: () => { let temps = []; - if (opts.isOrdeable) { + if (opts.isOrderable) { temps = $(`#${setCodeName}`).val(); } else { const sel = $(`#${setCodeName}`).attr("my-editable") === "true" ? "" : ":selected"; @@ -1189,7 +1189,7 @@ function generateFormHtml(settingsData, set, overrideValue, overrideOptions, ori inputType, readOnly, isMultiSelect, - isOrdeable, + isOrderable, cssClasses, placeholder, suffix, @@ -1225,7 +1225,7 @@ function generateFormHtml(settingsData, set, overrideValue, overrideOptions, ori switch (elementType) { case 'select': const multi = isMultiSelect ? "multiple" : ""; - const addCss = isOrdeable ? "select2 select2-hidden-accessible" : ""; + const addCss = isOrderable ? "select2 select2-hidden-accessible" : ""; inputHtml += `