diff --git a/docs/WORKFLOWS.md b/docs/WORKFLOWS.md index 60d9b4d0..254f9c45 100755 --- a/docs/WORKFLOWS.md +++ b/docs/WORKFLOWS.md @@ -23,6 +23,7 @@ Triggers define the event that activates a workflow. They monitor changes to obj > Workflows not running? Check the [Workflows debugging](./WORKFLOWS_DEBUGGING.md) guide how to troubleshoot triggers and conditions. #### Example Trigger: + - **Object Type**: `Devices` - **Event Type**: `update` @@ -38,7 +39,9 @@ Conditions determine whether a workflow should proceed based on certain criteria > To better understand how to use specific Device fields, please read through the [Database overview](./DATABASE.md) guide. #### Example Condition: + - **Logic**: `AND` + - **Field**: `devVendor` - **Operator**: `contains` (case in-sensitive) - **Value**: `Google` @@ -53,8 +56,16 @@ 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. +#### Target device for the Actions + +You can change the target of the actions of the workflows. You can either target the device that triggered by selecting the **Apply action to** to `triggering_device`, or target multiple devices by selecting `query` in the **Apply action to** dropdown. + +![Action target](./img/WORKFLOWS/actions_target.png) + #### Example Action: + - **Action Type**: `update_field` + - **Field**: `devIsNew` - **Value**: `0` diff --git a/docs/WORKFLOW_EXAMPLES.md b/docs/WORKFLOW_EXAMPLES.md index f94647f8..9ec6c93d 100755 --- a/docs/WORKFLOW_EXAMPLES.md +++ b/docs/WORKFLOW_EXAMPLES.md @@ -54,6 +54,7 @@ Sometimes devices are manually archived (e.g., no longer expected on the network * `devIsArchived` is `1` (archived). * `devPresentLastScan` is `1` (device was detected in the latest scan). + * **Action**: * Updates the device to set `devIsArchived` to `0` (unarchived). @@ -110,6 +111,7 @@ When new devices join your network, assigning them to the correct network node i * **Conditions**: * `devLastIP` contains `192.168.1.` (matches subnet). + * **Action**: * Sets `devNetworkNode` to the specified MAC address. @@ -175,6 +177,7 @@ You may want to automatically clear out newly detected Google devices (such as C * `devVendor` contains `Google`. * `devIsNew` is `1` (device marked as new). + * **Actions**: 1. Sets `devIsNew` to `0` (mark as not new). @@ -182,4 +185,70 @@ You may want to automatically clear out newly detected Google devices (such as C ### ✅ Result -Any newly detected Google devices are cleaned up instantly — first marked as not new, then deleted — helping you avoid clutter in your device records. \ No newline at end of file +Any newly detected Google devices are cleaned up instantly — first marked as not new, then deleted — helping you avoid clutter in your device records. + +--- + +## Example 4: On new device discovery archive the old device with the same ip + +This workflow automatically archives devices if a new device is discovered with an already assigned IP. + +### 📋 Use Case + +This workflow is useful if you are assigning static IPs to your devices. This workflow can also help with archiving device entries with [random MAC addresses](./RANDOM_MAC.md). + +### ⚙️ Workflow Configuration + +```json +{ + "name": "Archive device with same ip", + "trigger": { + "object_type": "Devices", + "event_type": "insert" + }, + "conditions": [ + { + "logic": "AND", + "conditions": [] + } + ], + "actions": [ + { + "type": "update_field", + "field": "devIsArchived", + "value": "1", + "target": { + "strategy": "query", + "conditions": [ + { + "field": "devLastIP", + "operator": "equals", + "value": "{{trigger.devLastIP}}" + } + ] + } + } + ] +} +``` + +### 🔍 Explanation + +* **Trigger**: Runs on a new device being inserted. + +* **Conditions**: + + * `N/A` + +* **Target Conditions**: + + * `devLastIP` of the target is the same as the newly discovered device's `devLastIP` value. + +* **Actions**: + + 1. Sets `devIsArchived` to `1` (mark target device as archived). + + +### ✅ Result + +Any newly detected device that has the same IP as an existing device will automatically trigger the archival of the old device. diff --git a/docs/img/WORKFLOWS/actions_target.png b/docs/img/WORKFLOWS/actions_target.png new file mode 100644 index 00000000..fb15f1f7 Binary files /dev/null and b/docs/img/WORKFLOWS/actions_target.png differ diff --git a/front/css/app.css b/front/css/app.css index 333da974..ef8f2ddf 100755 --- a/front/css/app.css +++ b/front/css/app.css @@ -2345,6 +2345,17 @@ textarea[readonly], padding: 5px; } +.workflows .add-target-condition +{ + margin: 10px; + text-align: center; +} + +.workflows .inline-hint +{ + margin: 5px; +} + .workflows { max-width: 800px; @@ -2437,6 +2448,12 @@ textarea[readonly], color: var(--color-green) !important; } +.workflows .add-condition, .workflows .add-condition-group, .workflows .add-target-condition +{ + padding-top: 0.5em; + padding-bottom: 0.5em; +} + .workflows .action-target-conditions { opacity: 0.8; diff --git a/front/plugins/kea_api/script.py b/front/plugins/kea_api/script.py index 9128eed5..2f418d9c 100644 --- a/front/plugins/kea_api/script.py +++ b/front/plugins/kea_api/script.py @@ -40,14 +40,14 @@ def main(): if entry['result'] == 0: leases = entry['arguments']['leases'] for lease in leases: - mac = lease['hw-address'] + mac = lease['hw-address'] state = lease['state'] if is_mac(mac): plugin_objects.add_object( primaryId = mac, secondaryId = lease['ip-address'], # Active or not, similar to watched1 of DHCPLSS plugin - watched1 = state == 0, + watched1 = state == 0, watched2 = lease['hostname'], watched3 = None, # Default (or assigned) (0), declined (1), expired-reclaimed (2), released (3), and registered (4)). diff --git a/front/workflowsCore.php b/front/workflowsCore.php index b66ef284..4601e628 100755 --- a/front/workflowsCore.php +++ b/front/workflowsCore.php @@ -325,8 +325,8 @@ function generateWorkflowUI(wf, wfIndex) { .append($("", { class: "fa-solid fa-crosshairs" })) .append(` ${getString("WF_Action_target_conditions")}:`); - let $tokenHint = $("
", { class: "text-muted small col-sm-12 col-xs-12" }) - .text(getString("WF_Action_token_hint")); + let $tokenHint = $("
", { class: "text-muted inline-hint small col-sm-12 col-xs-12" }) + .html(getString("WF_Action_token_hint")); $targetConditionsWrap.append($targetConditionsTitle); $targetConditionsWrap.append($tokenHint); @@ -1230,12 +1230,18 @@ function saveWorkflows() appConfBase64 = btoa(JSON.stringify(getWorkflowsJson())) // import - $.post('php/server/query_replace_config.php', { base64data: appConfBase64, fileName: "workflows.json" }, function(msg) { - console.log(msg); - // showMessage(msg); - hideSpinner(); - write_notification(`[WF]: ${msg}`, 'interrupt'); - }); + $.post('php/server/query_replace_config.php', { base64data: appConfBase64, fileName: "workflows.json" }) + .done(function(msg) { + console.log(msg); + write_notification(`[WF]: ${msg}`, 'interrupt'); + }) + .fail(function(jqXHR, textStatus, errorThrown) { + console.warn("Failed to save workflows.json:", textStatus, errorThrown); + write_notification(`[WF]: Save failed (${textStatus})`, 'interrupt'); + }) + .always(function() { + hideSpinner(); + }); } // ---------------------------------------------------