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 00000000..f04a196f Binary files /dev/null and b/docs/img/WORKFLOWS/trigger.jpg differ 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