From 2889be28e426a14f30dccd6223c3343b2ba1c5ae Mon Sep 17 00:00:00 2001 From: jokob-sk Date: Thu, 3 Apr 2025 07:51:59 +1100 Subject: [PATCH] wf work --- README.md | 4 +++ docs/DOCKER_COMPOSE.md | 7 +---- docs/HOME_ASSISTANT.md | 2 +- docs/INSTALLATION.md | 2 +- docs/SYNOLOGY_GUIDE.md | 2 +- docs/WORKFLOWS.md | 6 +++-- docs/WORKFLOWS_DEBUGGING.md | 34 ++++++++++++++++++++++++ docs/img/WORKFLOWS/trigger.jpg | Bin 0 -> 6854 bytes front/css/app.css | 5 ++++ front/php/templates/language/uk_ua.json | 0 front/workflowsCore.php | 24 +++++++++-------- mkdocs.yml | 1 + server/database.py | 16 ++++++++++- server/workflows/conditions.py | 8 +++--- server/workflows/manager.py | 2 ++ server/workflows/triggers.py | 5 +++- 16 files changed, 89 insertions(+), 29 deletions(-) create mode 100755 docs/WORKFLOWS_DEBUGGING.md create mode 100755 docs/img/WORKFLOWS/trigger.jpg mode change 100644 => 100755 front/php/templates/language/uk_ua.json diff --git a/README.md b/README.md index 2724ec15..cb75bf65 100755 --- a/README.md +++ b/README.md @@ -41,6 +41,10 @@ Send notifications to more than 80+ services, including Telegram via [Apprise](h Feed your data and device changes into [Home Assistant](https://github.com/jokob-sk/NetAlertX/blob/main/docs/HOME_ASSISTANT.md), read [API endpoints](https://github.com/jokob-sk/NetAlertX/blob/main/docs/API.md), or use [Webhooks](https://github.com/jokob-sk/NetAlertX/blob/main/docs/WEBHOOK_N8N.md) to setup custom automation flows. You can also build your own scanners with the [Plugin system](https://github.com/jokob-sk/NetAlertX/tree/main/docs/PLUGINS.md#readme) in as little as [15 minutes](https://www.youtube.com/watch?v=cdbxlwiWhv8). +### Workflows + +The [workflows module](https://github.com/jokob-sk/NetAlertX/blob/main/docs/WORKFLOWS.md) in NetAlertX allows to automate repetitive tasks, making network management more efficient. Whether you need to assign newly discovered devices to a specific Network Node, auto-group devices from a given vendor, unarchive a device if detected online, or automatically delete devices, this module provides the flexibility to tailor the automations to your needs. + ## 📚 Documentation diff --git a/docs/DOCKER_COMPOSE.md b/docs/DOCKER_COMPOSE.md index a2eb10fc..ab45863d 100755 --- a/docs/DOCKER_COMPOSE.md +++ b/docs/DOCKER_COMPOSE.md @@ -1,7 +1,7 @@ # `docker-compose.yaml` Examples > [!NOTE] -> The container needs to run in `network_mode:"host"`. +> The container needs to run in `network_mode:"host"`. This also means that not all functionality is supported on a Widndows host as Docker for Windows doesn't support this networking option. ### Example 1 @@ -122,7 +122,6 @@ services: environment: - TZ=Europe/London - PORT=20211 - # network_mode: host networks: - outside deploy: @@ -130,10 +129,6 @@ services: replicas: 1 restart_policy: condition: on-failure - # placement: # ✅ Placement is now correctly inside deploy - # constraints: - # - node.role == manager - # - node.labels.device == NUC2 networks: outside: diff --git a/docs/HOME_ASSISTANT.md b/docs/HOME_ASSISTANT.md index 2fbf29ae..15316e52 100755 --- a/docs/HOME_ASSISTANT.md +++ b/docs/HOME_ASSISTANT.md @@ -3,7 +3,7 @@ NetAlertX comes with MQTT support, allowing you to show all detected devices as devices in Home Assistant. It also supplies a collection of stats, such as number of online devices. > [!TIP] -> You can install NetAlertX also as a Home Assistant addon [![Home Assistant](https://img.shields.io/badge/Repo-blue?logo=home-assistant&style=for-the-badge&color=0aa8d2&logoColor=fff&label=Add)](https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https%3A%2F%2Fgithub.com%2Falexbelgium%2Fhassio-addons) via the [alexbelgium/hassio-addons](https://github.com/alexbelgium/hassio-addons/). This is only possible if you run a supervised instance of Home Assistant. If not, you can still run NetAlertX in a separate Docker container and follow this guide to configure MQTT. +> You can install NetAlertX also as a Home Assistant addon [![Home Assistant](https://img.shields.io/badge/Repo-blue?logo=home-assistant&style=for-the-badge&color=0aa8d2&logoColor=fff&label=Add)](https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https%3A%2F%2Fgithub.com%2Falexbelgium%2Fhassio-addons) via the [alexbelgium/hassio-addons](https://github.com/alexbelgium/hassio-addons/) repository. This is only possible if you run a supervised instance of Home Assistant. If not, you can still run NetAlertX in a separate Docker container and follow this guide to configure MQTT. ## ⚠ Note diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md index 92bd6a44..8cb13f86 100755 --- a/docs/INSTALLATION.md +++ b/docs/INSTALLATION.md @@ -2,7 +2,7 @@ ## Installation options -NetAlertX can be installed several ways. The best supported option is Docker, followed by a supervised the Home Assistant instance, as an Unraid app and lastly on bare metal. +NetAlertX can be installed several ways. The best supported option is Docker, followed by a supervised Home Assistant instance, as an Unraid app, and lastly, on bare metal. - [[Installation] Docker (recommended)](https://github.com/jokob-sk/NetAlertX/blob/main/dockerfiles/README.md) - [[Installation] Home Assistant](https://github.com/alexbelgium/hassio-addons/tree/master/netalertx) diff --git a/docs/SYNOLOGY_GUIDE.md b/docs/SYNOLOGY_GUIDE.md index 15b38a38..4b6418b5 100755 --- a/docs/SYNOLOGY_GUIDE.md +++ b/docs/SYNOLOGY_GUIDE.md @@ -51,7 +51,7 @@ services: ![Project settings](./img/SYNOLOGY/07_Create_project.png) -7. Replace the paths to your volume and/or comment out unnecessary line(s): +7. Replace the paths to your volume and comment out unnecessary line(s): - This is only an example, your paths will differ. diff --git a/docs/WORKFLOWS.md b/docs/WORKFLOWS.md index 059b6a79..3205e44d 100755 --- a/docs/WORKFLOWS.md +++ b/docs/WORKFLOWS.md @@ -15,6 +15,8 @@ Below are a few examples that demonstrate how this module can be used to simplif ### Triggers +![Trigger example](./img/WORKFLOWS/trigger.jpg) + Triggers define the event that activates a workflow. They monitor changes to objects within the system, such as updates to devices or the insertion of new entries. When the specified event occurs, the workflow is executed. #### Example Trigger: @@ -32,7 +34,7 @@ Conditions determine whether a workflow should proceed based on certain criteria > [!TIP] > To better understand how to use specific Device fields, please read through the [Database overview](./DATABASE.md) guide. -### Example Condition: +#### Example Condition: - **Logic**: `AND` - **Field**: `devVendor` - **Operator**: `contains` (case in-sensitive) @@ -48,7 +50,7 @@ Actions define the tasks that the workflow will perform once the conditions are You can include multiple actions that should execute once the conditions are met. -### Example Action: +#### Example Action: - **Action Type**: `update_field` - **Field**: `devIsNew` - **Value**: `0` diff --git a/docs/WORKFLOWS_DEBUGGING.md b/docs/WORKFLOWS_DEBUGGING.md new file mode 100755 index 00000000..dbcdf85d --- /dev/null +++ b/docs/WORKFLOWS_DEBUGGING.md @@ -0,0 +1,34 @@ +# Workflows debugging and troubleshooting + +> [!TIP] +> Before troubleshooting, please ensure you have [Debugging enabled](./DEBUG_TIPS.md). + +Workflows are triggered by various events. These events are captured and listed in the _Integrations -> App Events_ section of the application. + +## Troubleshooting triggers + +> [!NOTE] +> Workflow events are processed once every 5 seconds. However, if a scan or other background tasks are running, this can cause a delay up to a few minutes. + +If an event doesn't trigger a workflow as expected, check the _App Events_ section for the event. You can filter these by the ID of the device (`devMAC` or `devGUID`). + +Once you find the _Event Guid_ and _Object GUID_, use them to find relevant debug entries. + +Navigate to _Mainetenace -> Logs_ where you can filter the logs based on the _Event or Object GUID_. + +Below you can find some example `app.log` entries that will help you understand why a Workflow was or was not triggered. + +```bash +16:27:03 [WF] Checking if '13f0ce26-1835-4c48-ae03-cdaf38f328fe' triggers the workflow 'Sample Device Update Workflow' +16:27:03 [WF] self.triggered 'False' for event '[[155], ['13f0ce26-1835-4c48-ae03-cdaf38f328fe'], [0], ['2025-04-02 05:26:56'], ['Devices'], ['050b6980-7af6-4409-950d-08e9786b7b33'], ['DEVICES'], ['00:11:32:ef:a5:6c'], ['192.168.1.82'], ['050b6980-7af6-4409-950d-08e9786b7b33'], [None], [0], [0], ['devPresentLastScan'], ['online'], ['update'], [None], [None], [None], [None]] and trigger {"object_type": "Devices", "event_type": "insert"}' +16:27:03 [WF] Checking if '13f0ce26-1835-4c48-ae03-cdaf38f328fe' triggers the workflow 'Location Change' +16:27:03 [WF] self.triggered 'True' for event '[[155], ['13f0ce26-1835-4c48-ae03-cdaf38f328fe'], [0], ['2025-04-02 05:26:56'], ['Devices'], ['050b6980-7af6-4409-950d-08e9786b7b33'], ['DEVICES'], ['00:11:32:ef:a5:6c'], ['192.168.1.82'], ['050b6980-7af6-4409-950d-08e9786b7b33'], [None], [0], [0], ['devPresentLastScan'], ['online'], ['update'], [None], [None], [None], [None]] and trigger {"object_type": "Devices", "event_type": "update"}' +16:27:03 [WF] Event with GUID '13f0ce26-1835-4c48-ae03-cdaf38f328fe' triggered the workflow 'Location Change' +``` + +Note how one trigger executed, but the other didn't based on different `"event_type"` values. One is `"event_type": "insert"`, the other `"event_type": "update"`. + +Given the Event is a update event (note `...['online'], ['update'], [None]...` in the event structure), the `"event_type": "insert"` trigger didn't execute. + + + diff --git a/docs/img/WORKFLOWS/trigger.jpg b/docs/img/WORKFLOWS/trigger.jpg new file mode 100755 index 0000000000000000000000000000000000000000..f04a196ff8e9254a92d1759f011ebbe6d6c44664 GIT binary patch literal 6854 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 H{qBDNK~X@F literal 0 HcmV?d00001 diff --git a/front/css/app.css b/front/css/app.css index 9558c7a1..39cfb9fe 100755 --- a/front/css/app.css +++ b/front/css/app.css @@ -1857,6 +1857,11 @@ input[readonly] { padding: 5px; } +.workflows +{ + max-width: 800px; +} + .workflows .col-sm-12, .workflows .col-sx-12 { padding-right: 5px; diff --git a/front/php/templates/language/uk_ua.json b/front/php/templates/language/uk_ua.json old mode 100644 new mode 100755 diff --git a/front/workflowsCore.php b/front/workflowsCore.php index 482621cf..b7aeed26 100755 --- a/front/workflowsCore.php +++ b/front/workflowsCore.php @@ -66,6 +66,18 @@ let actionTypes = [ "update_field", "delete_device" ]; +let emptyWorkflow = { + "name": "New Workflow", + "trigger": { + "object_type": "Devices", + "event_type": "insert" + }, + "conditions": [ + ], + "actions": [ + ] + }; + // -------------------------------------- // Retrieve and process the data function getData() { @@ -1073,17 +1085,7 @@ function updateWorkflowsJson(workflows) // Get empty workflow JSON function getEmptyWorkflowJson() { - return { - "name": "New Workflow", - "trigger": { - "object_type": "Devices", - "event_type": "create" - }, - "conditions": [ - ], - "actions": [ - ] - } + return emptyWorkflow; } // --------------------------------------------------- diff --git a/mkdocs.yml b/mkdocs.yml index d877579d..f198938d 100755 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -61,6 +61,7 @@ nav: - Debugging Invalid JSON: DEBUG_INVALID_JSON.md - Debugging Plugins: DEBUG_PLUGINS.md - Debugging Web UI Port: WEB_UI_PORT_DEBUG.md + - Debugging Workflows: WORKFLOWS_DEBUGGING.md - Development: - Plugin and app development: - Environment Setup: DEV_ENV_SETUP.md diff --git a/server/database.py b/server/database.py index 8f09acd2..0db5a05b 100755 --- a/server/database.py +++ b/server/database.py @@ -75,7 +75,6 @@ class DB(): return arr - #------------------------------------------------------------------------------- def upgradeDB(self): """ @@ -929,4 +928,19 @@ def get_all_devices(db): return db.read(sql_devices_all) #------------------------------------------------------------------------------- + +def get_array_from_sql_rows(rows): + # Convert result into list of lists + arr = [] + for row in rows: + if isinstance(row, sqlite3.Row): + arr.append(list(row)) # Convert row to list + elif isinstance(row, (tuple, list)): + arr.append(list(row)) # Already iterable, just convert to list + else: + arr.append([row]) # Handle single values safely + + return arr + +#------------------------------------------------------------------------------- diff --git a/server/workflows/conditions.py b/server/workflows/conditions.py index c8ea4229..29522652 100755 --- a/server/workflows/conditions.py +++ b/server/workflows/conditions.py @@ -57,9 +57,7 @@ class ConditionGroup: def __init__(self, group_json): - mylog('none', ["[WF] json.dumps(group_json)"]) - mylog('none', [json.dumps(group_json)]) - mylog('none', [group_json]) + mylog('verbose', [f"[WF] ConditionGroup json.dumps(group_json): {json.dumps(group_json)}"]) self.logic = group_json.get("logic", "AND").upper() self.conditions = [] @@ -78,6 +76,6 @@ class ConditionGroup: elif self.logic == "OR": return any(results) else: - m = f"[WF] Unsupported logic: {self.logic}" - mylog('none', [m]) + m = f"[WF] ConditionGroup unsupported logic: {self.logic}" + mylog('verbose', [m]) raise ValueError(m) diff --git a/server/workflows/manager.py b/server/workflows/manager.py index 40e8f503..6fc061e7 100755 --- a/server/workflows/manager.py +++ b/server/workflows/manager.py @@ -57,6 +57,8 @@ class WorkflowManager: # Ensure workflow is enabled before proceeding if workflow.get("enabled", "No").lower() == "yes": + + mylog('debug', [f"[WF] Checking if '{event["GUID"]}' triggers the workflow '{workflow["name"]}'"]) # construct trigger object which also evaluates if the current event triggers it trigger = Trigger(workflow["trigger"], event, self.db) diff --git a/server/workflows/triggers.py b/server/workflows/triggers.py index f5f4be60..04024fe5 100755 --- a/server/workflows/triggers.py +++ b/server/workflows/triggers.py @@ -1,4 +1,5 @@ import sys +import json # Register NetAlertX directories INSTALL_PATH="/app" @@ -7,6 +8,7 @@ sys.path.extend([f"{INSTALL_PATH}/server"]) import conf from logger import mylog, Logger from helper import get_setting_value, timeNowTZ +from database import get_array_from_sql_rows # Make sure log level is initialized correctly Logger(get_setting_value('LOG_LEVEL')) @@ -27,7 +29,8 @@ class Trigger: self.event = event # Store the triggered event context, if provided self.triggered = self.object_type == event["ObjectType"] and self.event_type == event["AppEventType"] - mylog('verbose', [f"[WF] self.triggered '{self.triggered}'"]) + mylog('debug', [f"""[WF] self.triggered '{self.triggered}' for event '{get_array_from_sql_rows(event)} and trigger {json.dumps(triggerJson)}' """]) + if self.triggered: # object type corresponds with the DB table name