mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2026-06-21 14:49:59 -04:00
1380 lines
41 KiB
PHP
Executable File
1380 lines
41 KiB
PHP
Executable File
<?php
|
||
//------------------------------------------------------------------------------
|
||
// check if authenticated
|
||
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
|
||
?>
|
||
|
||
<script>
|
||
showSpinner();
|
||
</script>
|
||
|
||
|
||
<section class="content workflows col-sm-12 col-xs-12">
|
||
<div id="workflowContainerWrap" class="bg-grey-dark color-palette col-sm-12 col-xs-12 box-default box-info ">
|
||
<div id="workflowContainer"></div>
|
||
|
||
</div>
|
||
<div id="buttons" class="bottom-buttons col-sm-12 col-xs-12">
|
||
<div class="add-workflow col-sm-4 col-xs-12">
|
||
<button type="button" class="btn btn-primary add-workflow-btn col-sm-12 col-xs-12" id="add">
|
||
<i class="fa fa-fw fa-plus"></i> <?= lang('WF_Add');?>
|
||
</button>
|
||
</div>
|
||
<div class="import-wf col-sm-4 col-xs-12">
|
||
<button type="button" class="btn btn-primary col-sm-12 col-xs-12" id="import">
|
||
<i class="fa fa-fw fa-file-import"></i> <?= lang('WF_Import');?>
|
||
</button>
|
||
</div>
|
||
<div class="restart-app col-sm-4 col-xs-12">
|
||
<button type="button" class="btn btn-primary col-sm-12 col-xs-12" id="save" onclick="askRestartBackend()">
|
||
<i class="fa fa-fw fa-arrow-rotate-right"></i> <?= lang('Maint_RestartServer');?>
|
||
</button>
|
||
</div>
|
||
<div class="save-workflows col-xs-12">
|
||
<button type="button" class="btn btn-primary bg-green col-sm-12 col-xs-12" id="save" onclick="saveWorkflows()">
|
||
<i class="fa fa-fw fa-floppy-disk"></i> <?= lang('WF_Save');?>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<script>
|
||
|
||
let workflows = [];
|
||
|
||
let fieldOptions = [
|
||
"devName", "devMac", "devOwner", "devType", "devVendor", "devVlan", "devFavorite",
|
||
"devGroup", "devComments", "devFirstConnection", "devLastConnection",
|
||
"devLastIP", "devStaticIP", "devScan", "devLogEvents", "devAlertEvents",
|
||
"devAlertDown", "devCanSleep", "devSkipRepeated", "devLastNotification", "devPresentLastScan",
|
||
"devIsNew", "devLocation", "devIsArchived", "devParentMAC", "devParentPort",
|
||
"devIcon", "devSite", "devSSID", "devSyncHubNode", "devSourcePlugin", "devFQDN",
|
||
"devParentRelType", "devReqNicsOnline", "devNameSource", "devVendorSource" ,
|
||
"devSSIDSource", "devParentMACSource" , "devParentPortSource" , "devParentRelTypeSource",
|
||
"devVlanSource"
|
||
];
|
||
|
||
let triggerTypes = [
|
||
"Devices"
|
||
];
|
||
let triggerEvents = [
|
||
"update", "insert", "delete"
|
||
];
|
||
|
||
let wfEnabledOptions = [
|
||
"Yes", "No"
|
||
];
|
||
|
||
let operatorTypes = [
|
||
"equals", "contains" , "regex"
|
||
];
|
||
|
||
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() {
|
||
|
||
getSetting()
|
||
|
||
$.get('php/server/query_json.php?file=workflows.json')
|
||
.done(function (res) {
|
||
workflows = res;
|
||
console.log(workflows);
|
||
|
||
updateWorkflowsJson(workflows);
|
||
renderWorkflows();
|
||
})
|
||
.fail(function (jqXHR, textStatus, errorThrown) {
|
||
console.warn("Failed to load workflows.json:", textStatus, errorThrown);
|
||
workflows = []; // Set a default empty array to prevent crashes
|
||
updateWorkflowsJson(workflows);
|
||
renderWorkflows();
|
||
})
|
||
.always(function () {
|
||
hideSpinner(); // Ensure the spinner is hidden in all cases
|
||
});
|
||
}
|
||
|
||
|
||
// --------------------------------------
|
||
// Render all workflows
|
||
function renderWorkflows() {
|
||
let $container = $("#workflowContainer");
|
||
$container.empty(); // Clear previous UI
|
||
|
||
workflows = getWorkflowsJson();
|
||
|
||
console.log("renderWorkflows");
|
||
console.log(workflows);
|
||
|
||
$.each(workflows, function (index, wf) {
|
||
let $wfElement = generateWorkflowUI(wf, index);
|
||
$container.append($wfElement);
|
||
});
|
||
}
|
||
|
||
|
||
// --------------------------------------
|
||
// Generate UI for a single workflow
|
||
function generateWorkflowUI(wf, wfIndex) {
|
||
|
||
let wfEnabled = (wf?.enabled ?? "No") == "Yes";
|
||
|
||
let $wfContainer = $("<div>", {
|
||
class: "workflow-card panel col-sm-12 col-sx-12",
|
||
id: `wf-${wfIndex}-container`
|
||
});
|
||
|
||
// Workflow Name
|
||
let $wfLinkWrap = $("<div>",
|
||
{
|
||
class: " ",
|
||
id: `wf-${wfIndex}-header`
|
||
}
|
||
)
|
||
|
||
let $wfEnabledIcon = $("<i>", {
|
||
class: `alignRight fa ${wfEnabled ? "fa-dot-circle" : "fa-circle" }`
|
||
});
|
||
|
||
|
||
let $wfHeaderLink = $("<a>",
|
||
{
|
||
"class": "pointer ",
|
||
"data-toggle": "collapse",
|
||
"data-parent": "#workflowContainer",
|
||
"aria-expanded": false,
|
||
"href" : `#wf-${wfIndex}-collapsible-panel`
|
||
}
|
||
)
|
||
|
||
let $wfHeaderHeading = $("<h4>",
|
||
{
|
||
class: "panel-title"
|
||
}
|
||
).text(wf.name)
|
||
|
||
$wfContainer.append($wfHeaderLink.append($wfLinkWrap.append($wfHeaderHeading.append($wfEnabledIcon))));
|
||
|
||
// Collapsible panel start
|
||
|
||
// Get saved state from localStorage
|
||
let panelState = localStorage.getItem(`wf-${wfIndex}-collapsible-panel`);
|
||
let isOpen = panelState === "true"; // Convert stored string to boolean
|
||
|
||
console.log(`panel isOpen: ${isOpen}` );
|
||
|
||
|
||
let $wfCollapsiblePanel = $("<div>", {
|
||
class: ` panel-collapse collapse ${isOpen ? 'in' : ''}`,
|
||
id: `wf-${wfIndex}-collapsible-panel`
|
||
});
|
||
|
||
let $wfEnabled = createEditableDropdown(
|
||
`[${wfIndex}].enabled`,
|
||
getString("WF_Enabled"),
|
||
wfEnabledOptions,
|
||
wfEnabled ? "Yes" :"No",
|
||
`wf-${wfIndex}-enabled`
|
||
);
|
||
|
||
$wfCollapsiblePanel.append($wfEnabled)
|
||
|
||
let $wfNameInput = createEditableInput(
|
||
`[${wfIndex}].name`,
|
||
getString("WF_Name"),
|
||
wf.name,
|
||
`wf-${wfIndex}-name`,
|
||
"workflow-name-input"
|
||
);
|
||
|
||
$wfCollapsiblePanel.append($wfNameInput)
|
||
|
||
let $triggersIcon = $("<i>", {
|
||
class: "fa-solid fa-bolt"
|
||
});
|
||
|
||
let $triggerTitle = $("<div>",
|
||
{
|
||
class:"section-title"
|
||
}
|
||
).append($triggersIcon).append(` ${getString("WF_Trigger")}:`)
|
||
|
||
// Trigger Section with dropdowns
|
||
let $triggerSection = $("<div>",
|
||
{
|
||
class: " col-sm-12 col-sx-12"
|
||
}
|
||
).append($triggerTitle);
|
||
|
||
let $triggerTypeDropdown = createEditableDropdown(
|
||
`[${wfIndex}].trigger.object_type`,
|
||
getString("WF_Trigger_type"),
|
||
triggerTypes,
|
||
wf.trigger.object_type,
|
||
`wf-${wfIndex}-trigger-object-type`
|
||
);
|
||
|
||
let $eventTypeDropdown = createEditableDropdown(
|
||
`[${wfIndex}].trigger.event_type`,
|
||
getString("WF_Trigger_event_type"),
|
||
triggerEvents,
|
||
wf.trigger.event_type,
|
||
`wf-${wfIndex}-trigger-event-type`
|
||
);
|
||
|
||
let $triggerIcon = $("<i>", {
|
||
class: "fa-solid fa-bolt bckg-icon-2-line"
|
||
});
|
||
|
||
$triggerSection.append($triggerIcon);
|
||
$triggerSection.append($triggerTypeDropdown);
|
||
$triggerSection.append($eventTypeDropdown);
|
||
|
||
$wfCollapsiblePanel.append($triggerSection);
|
||
|
||
// Conditions
|
||
|
||
let $conditionsIcon = $("<i>", {
|
||
class: "fa-solid fa-arrows-split-up-and-left fa-rotate-270"
|
||
});
|
||
|
||
let $conditionsTitle = $("<div>", {
|
||
class: "section-title"
|
||
}).append($conditionsIcon).append(` ${getString("WF_Conditions")}:`);
|
||
|
||
let $conditionsContainer = $("<div>", {
|
||
class: "col-sm-12 col-sx-12"
|
||
}).append($conditionsTitle);
|
||
|
||
|
||
$conditionsContainer.append(renderConditions(wfIndex, `[${wfIndex}]`, 0, wf.conditions));
|
||
|
||
$wfCollapsiblePanel.append($conditionsContainer);
|
||
|
||
let $actionsIcon = $("<i>", {
|
||
class: "fa-solid fa-person-running fa-flip-horizontal"
|
||
});
|
||
|
||
let $actionsTitle = $("<div>",
|
||
{
|
||
class:"section-title"
|
||
}
|
||
).append($actionsIcon).append(` ${getString("WF_Actions")}:`)
|
||
|
||
// Actions with action.field as dropdown
|
||
let $actionsContainer = $("<div>",
|
||
{
|
||
class: "actions-list col-sm-12 col-sx-12 box "
|
||
}
|
||
).append($actionsTitle);
|
||
|
||
lastActionIndex = 0
|
||
$.each(wf.actions, function (actionIndex, action) {
|
||
let $actionEl = $("<div>", {
|
||
class: "col-sm-11 col-sx-12"
|
||
});
|
||
|
||
let $actionElWrap = $("<div>", {
|
||
class: "panel col-sm-12 col-sx-12"
|
||
});
|
||
|
||
|
||
// how big should the background icon be — computed after all content decisions
|
||
let numberOfLines = 1
|
||
|
||
// ------------------------------------------------------------------
|
||
// Target selector — shown first so user picks the target before the action
|
||
// Applies to update_field and delete_device actions
|
||
// ------------------------------------------------------------------
|
||
if (action.type == "update_field" || action.type == "delete_device") {
|
||
let currentStrategy = (action.target && action.target.strategy) ? action.target.strategy : "triggering_device";
|
||
|
||
let $targetDropdown = createEditableDropdown(
|
||
`[${wfIndex}].actions[${actionIndex}].target.strategy`,
|
||
getString("WF_Action_target"),
|
||
["triggering_device", "query"],
|
||
currentStrategy,
|
||
`wf-${wfIndex}-actionIndex-${actionIndex}-target-strategy`
|
||
);
|
||
|
||
$actionEl.append($targetDropdown);
|
||
|
||
// Conditional query conditions sub-form
|
||
let $targetConditionsWrap = $("<div>", {
|
||
class: `action-target-conditions panel col-sm-12 col-sx-12 ${currentStrategy === "query" ? "" : "hidden"}`,
|
||
id: `wf-${wfIndex}-actionIndex-${actionIndex}-target-conditions-wrap`
|
||
});
|
||
|
||
let $targetConditionsTitle = $("<div>", { class: "section-title" })
|
||
.append($("<i>", { class: "fa-solid fa-crosshairs" }))
|
||
.append(` ${getString("WF_Action_target_conditions")}:`);
|
||
|
||
let $tokenHint = $("<div>", { class: "text-muted inline-hint small col-sm-12 col-xs-12" })
|
||
.html(getString("WF_Action_token_hint"));
|
||
|
||
$targetConditionsWrap.append($targetConditionsTitle);
|
||
$targetConditionsWrap.append($tokenHint);
|
||
|
||
let targetConditions = (action.target && action.target.conditions) ? action.target.conditions : [];
|
||
let targetBasePath = `[${wfIndex}].actions[${actionIndex}].target`;
|
||
|
||
$.each(targetConditions, function(tcIdx, tc) {
|
||
let $tcRow = createTargetConditionRow(wfIndex, actionIndex, tcIdx, tc, targetBasePath);
|
||
$targetConditionsWrap.append($tcRow);
|
||
});
|
||
|
||
let $addTargetCondBtn = $("<div>", {
|
||
class: "pointer add-target-condition green-hover-text col-sm-12",
|
||
wfIndex: wfIndex,
|
||
actionIndex: actionIndex
|
||
}).append($("<i>", { class: "fa-solid fa-plus" })).append(` ${getString("WF_Add_Condition")}`);
|
||
|
||
$targetConditionsWrap.append($addTargetCondBtn);
|
||
$actionEl.append($targetConditionsWrap);
|
||
|
||
// Show/hide conditions sub-form when strategy dropdown changes
|
||
$targetDropdown.find("select").on("change", function() {
|
||
let val = $(this).val();
|
||
let $wrap = $(`#wf-${wfIndex}-actionIndex-${actionIndex}-target-conditions-wrap`);
|
||
if (val === "query") {
|
||
$wrap.removeClass("hidden");
|
||
} else {
|
||
$wrap.addClass("hidden");
|
||
// Strip target.conditions from the in-memory object when switching away from query
|
||
let wfs = getWorkflowsJson();
|
||
if (wfs[wfIndex] && wfs[wfIndex].actions[actionIndex] && wfs[wfIndex].actions[actionIndex].target) {
|
||
delete wfs[wfIndex].actions[actionIndex].target.conditions;
|
||
}
|
||
updateWorkflowsJson(wfs);
|
||
}
|
||
});
|
||
|
||
// numberOfLines: 1 (target dropdown) = 1 base for both action types
|
||
// query mode adds: 1 (section title+hint) + N×3 (each condition: field/op/value) + 1 (add btn)
|
||
let conditionLines = currentStrategy === "query"
|
||
? 2 + (targetConditions.length * 3)
|
||
: 0;
|
||
numberOfLines = 1 + conditionLines;
|
||
}
|
||
|
||
// Dropdown for action.type — rendered after target so user reads: who → what
|
||
let $actionDropdown = createEditableDropdown(
|
||
`[${wfIndex}].actions[${actionIndex}].type`,
|
||
getString("WF_Action_type"),
|
||
actionTypes,
|
||
action.type,
|
||
`wf-${wfIndex}-actionIndex-${actionIndex}-type`
|
||
);
|
||
|
||
$actionEl.append($actionDropdown);
|
||
numberOfLines += 1;
|
||
|
||
if(action.type == "update_field")
|
||
{
|
||
// +2 for field dropdown and value input rows
|
||
numberOfLines += 2;
|
||
|
||
// Dropdown for action.field
|
||
let $fieldDropdown = createEditableDropdown(
|
||
`[${wfIndex}].actions[${actionIndex}].field`,
|
||
getString("WF_Action_field"),
|
||
fieldOptions,
|
||
action.field,
|
||
`wf-${wfIndex}-actionIndex-${actionIndex}-field`
|
||
);
|
||
|
||
// Textbox for action.value
|
||
let $actionValueInput = createEditableInput(
|
||
`[${wfIndex}].actions[${actionIndex}].value`,
|
||
getString("WF_Action_value"),
|
||
action.value,
|
||
`wf-${wfIndex}-actionIndex-${actionIndex}-value`,
|
||
"action-value-input"
|
||
);
|
||
|
||
|
||
$actionEl.append($fieldDropdown);
|
||
$actionEl.append($actionValueInput);
|
||
|
||
}
|
||
|
||
// Actions
|
||
|
||
let $actionRemoveButtonWrap = $("<div>", { class: "button-container col-sm-1 col-sx-12" });
|
||
|
||
let $actionRemoveIcon = $("<i>", {
|
||
class: "fa-solid fa-trash"
|
||
});
|
||
|
||
let $actionRemoveButton = $("<div>", {
|
||
class: "pointer remove-action red-hover-text",
|
||
actionIndex: actionIndex,
|
||
wfIndex: wfIndex
|
||
})
|
||
.append($actionRemoveIcon)
|
||
|
||
$actionRemoveButtonWrap.append($actionRemoveButton);
|
||
|
||
let $actionIcon = $("<i>", {
|
||
class: `fa-solid fa-person-running fa-flip-horizontal bckg-icon-base`,
|
||
style: `font-size: ${numberOfLines * 3}em`
|
||
});
|
||
|
||
$actionEl.prepend($actionIcon)
|
||
|
||
$actionElWrap.append($actionEl)
|
||
|
||
$actionElWrap.append($actionRemoveButtonWrap)
|
||
|
||
$actionsContainer.append($actionElWrap);
|
||
|
||
lastActionIndex = actionIndex
|
||
});
|
||
|
||
// add action button
|
||
let $actionAddButtonWrap = $("<div>", { class: "button-container col-sm-12 col-sx-12" });
|
||
let $actionAddIcon = $("<i>", {
|
||
class: "fa-solid fa-plus"
|
||
});
|
||
let $actionAddButton = $("<div>", {
|
||
class : "pointer add-action green-hover-text",
|
||
lastActionIndex : lastActionIndex,
|
||
wfIndex: wfIndex
|
||
}).append($actionAddIcon).append(` ${getString("WF_Action_Add")}`)
|
||
|
||
$actionAddButtonWrap.append($actionAddButton)
|
||
$actionsContainer.append($actionAddButtonWrap)
|
||
|
||
|
||
let $wfRemoveButtonWrap = $("<div>", { class: "button-container col-sm-4 col-sx-12" });
|
||
|
||
let $wfRemoveIcon = $("<i>", {
|
||
class: "fa-solid fa-trash"
|
||
});
|
||
|
||
let $wfRemoveButton = $("<div>", {
|
||
class: "pointer remove-wf red-hover-text",
|
||
wfIndex: wfIndex
|
||
})
|
||
.append($wfRemoveIcon) // Add icon
|
||
.append(` ${getString("WF_Remove")}`); // Add text
|
||
|
||
|
||
let $wfDuplicateButtonWrap = $("<div>", { class: "button-container col-sm-4 col-sx-12" });
|
||
|
||
let $wfDuplicateIcon = $("<i>", {
|
||
class: "fa-solid fa-copy"
|
||
});
|
||
|
||
let $wfDuplicateButton = $("<div>", {
|
||
class: "pointer duplicate-wf green-hover-text",
|
||
wfIndex: wfIndex
|
||
})
|
||
.append($wfDuplicateIcon) // Add icon
|
||
.append(` ${getString("WF_Duplicate")}`); // Add text
|
||
|
||
let $wfExportButtonWrap = $("<div>", { class: "button-container col-sm-4 col-sx-12" });
|
||
|
||
let $wfExportIcon = $("<i>", {
|
||
class: "fa-solid fa-file-export"
|
||
});
|
||
|
||
let $wfExportButton = $("<div>", {
|
||
class: "pointer export-wf green-hover-text",
|
||
wfIndex: wfIndex
|
||
})
|
||
.append($wfExportIcon) // Add icon
|
||
.append(` ${getString("WF_Export")}`); // Add text
|
||
|
||
$wfCollapsiblePanel.append($actionsContainer);
|
||
|
||
$wfCollapsiblePanel.append($wfDuplicateButtonWrap.append($wfDuplicateButton))
|
||
$wfCollapsiblePanel.append($wfExportButtonWrap.append($wfExportButton))
|
||
$wfCollapsiblePanel.append($wfRemoveButtonWrap.append($wfRemoveButton))
|
||
|
||
|
||
$wfContainer.append($wfCollapsiblePanel)
|
||
|
||
return $wfContainer;
|
||
}
|
||
|
||
|
||
// --------------------------------------
|
||
// Render conditions recursively
|
||
function renderConditions(wfIndex, parentIndexPath, conditionGroupsIndex, conditions) {
|
||
let $conditionList = $("<div>", {
|
||
class: "condition-list panel col-sm-12 col-sx-12",
|
||
parentIndexPath: parentIndexPath
|
||
});
|
||
|
||
lastConditionIndex = 0
|
||
|
||
let $conditionListWrap = $("<div>", {
|
||
class: `condition-list-wrap ${conditionGroupsIndex==0?"col-sm-12":"col-sm-11"} col-sx-12`,
|
||
conditionGroupsIndex: conditionGroupsIndex
|
||
});
|
||
|
||
let $deleteConditionGroupWrap = $("<div>", {
|
||
class: "condition-group-wrap-del col-sm-1 col-sx-12"
|
||
});
|
||
|
||
$.each(conditions, function (conditionIndex, condition) {
|
||
|
||
let currentPath = `${parentIndexPath}.conditions[${conditionIndex}]`;
|
||
|
||
if (condition.logic) {
|
||
let $nestedCondition = $("<div>",
|
||
{
|
||
class : "condition box box-secondary col-sm-12 col-sx-12"
|
||
}
|
||
);
|
||
|
||
let $logicDropdown = createEditableDropdown(
|
||
`${currentPath}.logic`,
|
||
getString("WF_Conditions_logic_rules"),
|
||
["AND", "OR"],
|
||
condition.logic,
|
||
`wf-${wfIndex}-${currentPath.replace(/\./g, "-")}-logic` // id
|
||
);
|
||
|
||
$nestedCondition.append($logicDropdown);
|
||
|
||
$conditionListNested = renderConditions(wfIndex, currentPath, conditionGroupsIndex+1, condition.conditions)
|
||
|
||
$nestedCondition.append($conditionListNested); // Recursive call for nested conditions
|
||
|
||
$conditionList.append($nestedCondition);
|
||
|
||
} else {
|
||
// INDIVIDUAL CONDITIONS
|
||
let $conditionIcon = $("<i>", {
|
||
class: "fa-solid fa-arrows-split-up-and-left fa-rotate-270 bckg-icon-3-line "
|
||
});
|
||
|
||
let $conditionItem = $("<div>",
|
||
{
|
||
class: "panel col-sm-12 col-sx-12",
|
||
conditionIndex: conditionIndex,
|
||
wfIndex: wfIndex
|
||
});
|
||
|
||
$conditionItem.append($conditionIcon); // Append background icon
|
||
|
||
let $conditionItemsWrap = $("<div>",
|
||
{
|
||
class: "conditionItemsWrap col-sm-11 col-sx-12"
|
||
});
|
||
|
||
// Create dropdown for condition field
|
||
let $fieldDropdown = createEditableDropdown(
|
||
`${currentPath}.field`,
|
||
getString("WF_Condition_field"),
|
||
fieldOptions,
|
||
condition.field,
|
||
`wf-${wfIndex}-${currentPath.replace(/\./g, "-")}-field`
|
||
);
|
||
|
||
// Create dropdown for operator
|
||
let $operatorDropdown = createEditableDropdown(
|
||
`${currentPath}.operator`,
|
||
getString("WF_Condition_operator"),
|
||
operatorTypes,
|
||
condition.operator,
|
||
`wf-${wfIndex}-${currentPath.replace(/\./g, "-")}-operator`
|
||
);
|
||
|
||
// Editable input for condition value
|
||
let $editableInput = createEditableInput(
|
||
`${currentPath}.value`,
|
||
getString("WF_Condition_value"),
|
||
condition.value,
|
||
`wf-${wfIndex}-${currentPath.replace(/\./g, "-")}-value`,
|
||
"condition-value-input"
|
||
);
|
||
|
||
|
||
$conditionItemsWrap.append($fieldDropdown); // Append field dropdown
|
||
$conditionItemsWrap.append($operatorDropdown); // Append operator dropdown
|
||
$conditionItemsWrap.append($editableInput); // Append editable input for condition value
|
||
|
||
let $conditionRemoveButtonWrap = $("<div>", { class: "button-container col-sm-1 col-sx-12" });
|
||
let $conditionRemoveButtonIcon = $("<i>", {
|
||
class: "fa-solid fa-trash"
|
||
});
|
||
let $conditionRemoveButton = $("<div>", {
|
||
class : "pointer remove-condition red-hover-text",
|
||
conditionIndex : conditionIndex,
|
||
wfIndex: wfIndex,
|
||
parentIndexPath: parentIndexPath
|
||
}).append($conditionRemoveButtonIcon)
|
||
|
||
$conditionRemoveButtonWrap .append($conditionRemoveButton);
|
||
$conditionItem.append($conditionItemsWrap);
|
||
$conditionItem.append($conditionRemoveButtonWrap);
|
||
|
||
$conditionList.append($conditionItem);
|
||
}
|
||
|
||
lastConditionIndex = conditionIndex
|
||
});
|
||
|
||
let $addButtonWrap = $("<div>", {
|
||
class: "add-button-wrap col-sm-12 col-sx-12"
|
||
});
|
||
|
||
if (conditionGroupsIndex != 0) {
|
||
// Add Condition button
|
||
let $conditionAddWrap = $("<div>", { class: "button-container col-sm-6 col-sx-12" });
|
||
let $conditionAddIcon = $("<i>", {
|
||
class: "fa-solid fa-plus"
|
||
});
|
||
let $conditionAddButton = $("<div>", {
|
||
class: "pointer add-condition green-hover-text col-sx-12",
|
||
wfIndex: wfIndex,
|
||
parentIndexPath: parentIndexPath
|
||
}).append($conditionAddIcon).append(` ${getString("WF_Add_Condition")}`);
|
||
$conditionAddWrap.append($conditionAddButton);
|
||
|
||
// Remove Condition Group button
|
||
let $conditionGroupRemoveWrap = $("<div>", { class: "button-container col-sx-12" });
|
||
let $conditionGroupRemoveIcon = $("<i>", {
|
||
class: "fa-solid fa-trash"
|
||
});
|
||
let $conditionGroupRemoveButton = $("<div>", {
|
||
class: "pointer remove-condition-group red-hover-text col-sx-12",
|
||
lastConditionIndex: lastConditionIndex,
|
||
wfIndex: wfIndex,
|
||
parentIndexPath: parentIndexPath
|
||
}).append($conditionGroupRemoveIcon);
|
||
$conditionGroupRemoveWrap.append($conditionGroupRemoveButton);
|
||
|
||
$addButtonWrap.append($conditionAddWrap);
|
||
$deleteConditionGroupWrap.append($conditionGroupRemoveWrap);
|
||
|
||
}
|
||
|
||
// Add Condition Group button
|
||
let $conditionsGroupAddWrap = $("<div>", { class: "button-container col-sm-6 col-sx-12" });
|
||
let $conditionsGroupAddIcon = $("<i>", {
|
||
class: "fa-solid fa-plus"
|
||
});
|
||
let $conditionsGroupAddButton = $("<div>", {
|
||
class: "pointer add-condition-group green-hover-text col-sx-12",
|
||
wfIndex: wfIndex,
|
||
parentIndexPath: parentIndexPath
|
||
}).append($conditionsGroupAddIcon).append(` ${getString("WF_Add_Group")}`);
|
||
$conditionsGroupAddWrap.append($conditionsGroupAddButton);
|
||
|
||
$addButtonWrap.append($conditionsGroupAddWrap);
|
||
$conditionList.append($addButtonWrap);
|
||
|
||
$conditionListWrap.append($conditionList)
|
||
|
||
|
||
let $res = $("<div>", {
|
||
class: "condition-list-res col-sm-12 col-sx-12"
|
||
});
|
||
|
||
$res.append($conditionListWrap)
|
||
$res.append($deleteConditionGroupWrap)
|
||
|
||
return $res;
|
||
}
|
||
|
||
|
||
// --------------------------------------
|
||
// Render SELECT Dropdown with Predefined Values
|
||
function createEditableDropdown(jsonPath, labelText, options, selectedValue, id) {
|
||
|
||
let $wrapper = $("<div>", {
|
||
class: "form-group col-sm-12 col-sx-12"
|
||
});
|
||
|
||
let $label = $("<label>", {
|
||
for: id,
|
||
class: "col-sm-4 col-xs-12 control-label "
|
||
}).text(labelText);
|
||
|
||
// Create select wrapper
|
||
let $selectWrapper = $("<div>", {
|
||
class: "col-sm-8 col-xs-12"
|
||
});
|
||
|
||
// Create select element
|
||
let $select = $("<select>", {
|
||
id: id,
|
||
jsonPath: jsonPath,
|
||
class: "form-control col-sm-8 col-xs-12"
|
||
});
|
||
|
||
|
||
// Add options to the select dropdown
|
||
$.each(options, function (_, option) {
|
||
let $option = $("<option>", { value: option }).text(option);
|
||
if (option === selectedValue) {
|
||
$option.attr("selected", "selected"); // Set the default selection
|
||
}
|
||
$select.append($option);
|
||
});
|
||
|
||
// Trigger onSave when the selection changes
|
||
$select.on("change", function() {
|
||
let newValue = $select.val();
|
||
console.log(`Selected new value: ${newValue}`);
|
||
console.log(`Selected new jsonPath: ${$select.attr("jsonPath")}`);
|
||
|
||
updateWorkflowObject(newValue, $select.attr("jsonPath")); // Call the onSave callback with the new value
|
||
|
||
});
|
||
|
||
$wrapper.append($label);
|
||
$wrapper.append($selectWrapper.append($select));
|
||
return $wrapper;
|
||
}
|
||
|
||
// --------------------------------------
|
||
// Render INPUT HTML element
|
||
function createEditableInput(jsonPath, labelText, value, id, className = "") {
|
||
|
||
// prepare wrapper
|
||
$wrapper = $("<div>", {
|
||
class: "form-group col-sm-12 col-xs-12"
|
||
});
|
||
|
||
let $label = $("<label>", {
|
||
for: id,
|
||
class: "col-sm-4 col-xs-12 control-label "
|
||
}).text(labelText);
|
||
|
||
// Create input wrapper
|
||
let $inputWrapper = $("<div>", {
|
||
class: "col-sm-8 col-xs-12"
|
||
});
|
||
|
||
// console.log(jsonPath);
|
||
|
||
let $input = $("<input>", {
|
||
type: "text",
|
||
jsonPath: jsonPath,
|
||
id: id,
|
||
value: value,
|
||
class: className + " col-sm-8 col-xs-12 form-control "
|
||
});
|
||
|
||
// Optional: Add a change event listener to update the workflow name
|
||
$input.on("change", function () {
|
||
let newValue = $input.val();
|
||
console.log(`Value changed to: ${newValue}`);
|
||
});
|
||
|
||
// Trigger onSave when the user presses Enter or the input loses focus
|
||
$input.on("blur keyup", function (e) {
|
||
if (e.type === "blur" || e.key === "Enter") {
|
||
let newValue = $input.val();
|
||
console.log(`Selected new value: ${newValue}`);
|
||
|
||
updateWorkflowObject(newValue, $input.attr("jsonPath")); // Call the onSave callback with the new value
|
||
}
|
||
});
|
||
|
||
$wrapper.append($label)
|
||
$wrapper.append($inputWrapper.append($input))
|
||
return $wrapper;
|
||
}
|
||
|
||
// --------------------------------------
|
||
// Render a single row in a target-conditions sub-form (cross-device query targeting v2)
|
||
function createTargetConditionRow(wfIndex, actionIndex, tcIdx, tc, targetBasePath) {
|
||
let basePath = `${targetBasePath}.conditions[${tcIdx}]`;
|
||
|
||
let $row = $("<div>", { class: "panel col-sm-12 col-sx-12 target-condition-row" });
|
||
|
||
let $icon = $("<i>", { class: "fa-solid fa-crosshairs bckg-icon-3-line" });
|
||
$row.append($icon);
|
||
|
||
let $inner = $("<div>", { class: "col-sm-11 col-sx-12" });
|
||
|
||
let $fieldDropdown = createEditableDropdown(
|
||
`${basePath}.field`,
|
||
getString("WF_Condition_field"),
|
||
fieldOptions,
|
||
tc.field || "",
|
||
`wf-${wfIndex}-act-${actionIndex}-tc-${tcIdx}-field`
|
||
);
|
||
|
||
let $operatorDropdown = createEditableDropdown(
|
||
`${basePath}.operator`,
|
||
getString("WF_Condition_operator"),
|
||
operatorTypes,
|
||
tc.operator || "equals",
|
||
`wf-${wfIndex}-act-${actionIndex}-tc-${tcIdx}-operator`
|
||
);
|
||
|
||
let $valueInput = createEditableInput(
|
||
`${basePath}.value`,
|
||
getString("WF_Condition_value"),
|
||
tc.value || "",
|
||
`wf-${wfIndex}-act-${actionIndex}-tc-${tcIdx}-value`,
|
||
"condition-value-input"
|
||
);
|
||
|
||
$inner.append($fieldDropdown).append($operatorDropdown).append($valueInput);
|
||
|
||
let $removeWrap = $("<div>", { class: "button-container col-sm-1 col-sx-12" });
|
||
let $removeBtn = $("<div>", {
|
||
class: "pointer red-hover-text remove-target-condition",
|
||
wfIndex: wfIndex,
|
||
actionIndex: actionIndex,
|
||
tcIdx: tcIdx
|
||
}).append($("<i>", { class: "fa-solid fa-trash" }));
|
||
$removeWrap.append($removeBtn);
|
||
|
||
$row.append($inner).append($removeWrap);
|
||
return $row;
|
||
}
|
||
|
||
// --------------------------------------
|
||
// Updating the in-memory workflow object
|
||
function updateWorkflowObject(newValue, jsonPath) {
|
||
// Load workflows from cache if available
|
||
let workflows = getWorkflowsJson()
|
||
|
||
console.log("Initial workflows:", workflows);
|
||
|
||
workflows = updateJsonByPath(workflows, jsonPath, newValue)
|
||
|
||
console.log("Updated workflows:", workflows);
|
||
|
||
updateWorkflowsJson(workflows)
|
||
|
||
renderWorkflows();
|
||
}
|
||
|
||
|
||
/**
|
||
* Updates the given JSON structure at the location specified by a JSON path.
|
||
* @param {object|array} json - The JSON object or array to update.
|
||
* @param {string} path - The JSON path string, e.g. "[1].conditions[0].conditions[2].conditions[0].field".
|
||
* @param {*} newValue - The new value to set at the given path.
|
||
* @returns {object|array} - The updated JSON.
|
||
*/
|
||
function updateJsonByPath(json, path, newValue) {
|
||
const tokens = parsePath(path);
|
||
recursiveUpdate(json, tokens, newValue);
|
||
return json;
|
||
}
|
||
|
||
/**
|
||
* Recursively traverses the JSON structure to update the property defined by tokens.
|
||
* @param {object|array} current - The current JSON object or array.
|
||
* @param {Array<string|number>} tokens - An array of tokens representing the path.
|
||
* @param {*} newValue - The value to set at the target location.
|
||
*/
|
||
function recursiveUpdate(current, tokens, newValue) {
|
||
// When only one token is left, update that property/element with newValue.
|
||
if (tokens.length === 1) {
|
||
const key = tokens[0];
|
||
current[key] = newValue;
|
||
return;
|
||
}
|
||
|
||
const token = tokens[0];
|
||
|
||
// If the next level does not exist, optionally create it.
|
||
if (current[token] === undefined) {
|
||
// Determine if the next token is an array index or a property.
|
||
current[token] = typeof tokens[1] === 'number' ? [] : {};
|
||
}
|
||
|
||
// Recursively update the next level.
|
||
recursiveUpdate(current[token], tokens.slice(1), newValue);
|
||
}
|
||
|
||
/**
|
||
* Parses a JSON path string into an array of tokens.
|
||
* For example, "[1].conditions[0].conditions[2].conditions[0].field" becomes:
|
||
* [1, "conditions", 0, "conditions", 2, "conditions", 0, "field"]
|
||
* @param {string} path - The JSON path string.
|
||
* @returns {Array<string|number>} - An array of tokens.
|
||
*/
|
||
function parsePath(path) {
|
||
const tokens = [];
|
||
const regex = /(\w+)|\[(\d+)\]/g;
|
||
let match;
|
||
while ((match = regex.exec(path)) !== null) {
|
||
if (match[1]) {
|
||
tokens.push(match[1]);
|
||
} else if (match[2]) {
|
||
tokens.push(Number(match[2]));
|
||
}
|
||
}
|
||
return tokens;
|
||
}
|
||
|
||
|
||
|
||
// ---------------------------------------------------
|
||
// Buttons functionality
|
||
// ---------------------------------------------------
|
||
|
||
// ---------------------------------------------------
|
||
// Function to add a new Workflow
|
||
function addWorkflow(workflows) {
|
||
workflows.push(getEmptyWorkflowJson());
|
||
|
||
updateWorkflowsJson(workflows)
|
||
|
||
// Re-render the UI
|
||
renderWorkflows();
|
||
}
|
||
|
||
// ---------------------------------------------------
|
||
// Function to remove a Workflow
|
||
function removeWorkflow(workflows, wfIndex) {
|
||
|
||
showModalWarning ('<?= lang('WF_Remove');?>', '<?= lang('WF_Remove_Copy');?>',
|
||
'<?= lang('Gen_Cancel');?>', '<?= lang('Gen_Delete');?>', `executeRemoveWorkflow`, wfIndex);
|
||
}
|
||
|
||
// ---------------------------------------------------
|
||
// Function to execute the remove of a Workflow
|
||
function executeRemoveWorkflow() {
|
||
|
||
workflows = getWorkflowsJson()
|
||
|
||
workflows.splice($('#modal-warning').attr("data-myparam-triggered-by"), 1);
|
||
|
||
updateWorkflowsJson(workflows)
|
||
|
||
// Re-render the UI
|
||
renderWorkflows();
|
||
}
|
||
|
||
// ---------------------------------------------------
|
||
// Function to duplicate a Workflow
|
||
function duplicateWorkflow(workflows, wfIndex) {
|
||
|
||
workflows.push(workflows[wfIndex])
|
||
|
||
updateWorkflowsJson(workflows)
|
||
|
||
// Re-render the UI
|
||
renderWorkflows();
|
||
}
|
||
|
||
// ---------------------------------------------------
|
||
// Function to export a Workflow
|
||
function exportWorkflow(workflows, wfIndex) {
|
||
|
||
// Add new icon as base64 string
|
||
showModalInput ('<i class="fa fa-file-export pointer"></i> <?= lang('WF_Export');?>', '<?= lang('WF_Export_Copy');?>',
|
||
'<?= lang('Gen_Cancel');?>', '<?= lang('Gen_Okay');?>', null, null, JSON.stringify(workflows[wfIndex], null, 2));
|
||
}
|
||
|
||
// ---------------------------------------------------
|
||
// Function to import a Workflow
|
||
function importWorkflow(workflows, wfIndex) {
|
||
|
||
// Add new icon as base64 string
|
||
showModalInput ('<i class="fa fa-file-import pointer"></i> <?= lang('WF_Import');?>', '<?= lang('WF_Import_Copy');?>',
|
||
'<?= lang('Gen_Cancel');?>', '<?= lang('Gen_Okay');?>', 'importWorkflowExecute', null, "" );
|
||
|
||
}
|
||
|
||
function importWorkflowExecute()
|
||
{
|
||
var json = JSON.parse($('#modal-input-textarea').val());
|
||
|
||
workflows = getWorkflowsJson()
|
||
|
||
workflows.push(json);
|
||
|
||
updateWorkflowsJson(workflows)
|
||
|
||
// Re-render the UI
|
||
renderWorkflows();
|
||
}
|
||
|
||
// ---------------------------------------------------
|
||
// Function to add a new condition
|
||
function addCondition(workflows, wfIndex, parentIndexPath) {
|
||
if (!parentIndexPath) return;
|
||
|
||
workflows = getWorkflowsJson()
|
||
|
||
// Navigate to the target nested object
|
||
let target = getNestedObject(workflows, parentIndexPath);
|
||
|
||
console.log("Target:", target);
|
||
|
||
if (!target || !target.conditions) {
|
||
console.error("❌ Invalid path or conditions array missing:", parentIndexPath);
|
||
return;
|
||
}
|
||
|
||
// Add a new condition to the conditions array
|
||
target.conditions.push({
|
||
field: fieldOptions[0], // First option from field dropdown
|
||
operator: operatorTypes[0], // First operator
|
||
value: "" // Default empty value
|
||
});
|
||
|
||
// Ensure the workflows object is updated in memory
|
||
workflows[wfIndex] = { ...workflows[wfIndex] };
|
||
|
||
updateWorkflowsJson(workflows)
|
||
|
||
// Re-render the UI
|
||
renderWorkflows();
|
||
}
|
||
|
||
// ---------------------------------------------------
|
||
// Function to add a new condition group
|
||
function addConditionGroup(workflows, wfIndex, parentIndexPath) {
|
||
if (!parentIndexPath) return;
|
||
|
||
// Navigate to the target nested object
|
||
let target = getNestedObject(workflows, parentIndexPath);
|
||
|
||
console.log("Target:", target);
|
||
|
||
if (!target || !target.conditions) {
|
||
console.error("❌ Invalid path or conditions array missing:", parentIndexPath);
|
||
return;
|
||
}
|
||
|
||
// Add a new condition group to the conditions array
|
||
target.conditions.push({
|
||
logic: "AND",
|
||
conditions: []
|
||
});
|
||
|
||
// Ensure the workflows object is updated in memory
|
||
workflows[wfIndex] = { ...workflows[wfIndex] };
|
||
|
||
updateWorkflowsJson(workflows)
|
||
|
||
// Re-render the UI
|
||
renderWorkflows();
|
||
}
|
||
|
||
// ---------------------------------------------------
|
||
// Function to add a new action
|
||
function addAction(workflows, wfIndex) {
|
||
let newAction = {
|
||
type: actionTypes[0],
|
||
field: fieldOptions[0],
|
||
value: ""
|
||
};
|
||
workflows[wfIndex].actions.push(newAction);
|
||
|
||
updateWorkflowsJson(workflows)
|
||
|
||
renderWorkflows();
|
||
}
|
||
|
||
function removeCondition(workflows, wfIndex, parentIndexPath, conditionIndex) {
|
||
if (!parentIndexPath || conditionIndex === undefined) return;
|
||
|
||
// Navigate to the target nested object
|
||
let target = getNestedObject(workflows, parentIndexPath);
|
||
|
||
console.log("Target before removal:", target);
|
||
|
||
if (!target || !Array.isArray(target.conditions)) {
|
||
console.error("❌ Invalid path or conditions array missing:", parentIndexPath);
|
||
return;
|
||
}
|
||
|
||
// Remove the specified condition
|
||
target.conditions.splice(conditionIndex, 1);
|
||
|
||
// Ensure the workflows object is updated in memory
|
||
workflows[wfIndex] = { ...workflows[wfIndex] };
|
||
|
||
updateWorkflowsJson(workflows)
|
||
|
||
// Re-render the UI
|
||
renderWorkflows();
|
||
}
|
||
|
||
function removeAction(workflows, wfIndex, actionIndex) {
|
||
if (!actionIndex || actionIndex === undefined) return;
|
||
|
||
// Navigate to the target nested object
|
||
let target = getNestedObject(workflows, wfIndex);
|
||
|
||
console.log("Target before removal:", target);
|
||
|
||
if (!target || !Array.isArray(target.actions)) {
|
||
console.error("❌ Invalid path or conditions array missing:", actionIndex);
|
||
return;
|
||
}
|
||
|
||
console.log(actionIndex);
|
||
|
||
// Remove the specified condition
|
||
target.actions.splice(actionIndex, 1);
|
||
|
||
// Ensure the workflows object is updated in memory
|
||
workflows[wfIndex] = { ...workflows[wfIndex] };
|
||
|
||
updateWorkflowsJson(workflows)
|
||
|
||
// Re-render the UI
|
||
renderWorkflows();
|
||
}
|
||
|
||
function removeConditionGroup(workflows, wfIndex, parentIndexPath) {
|
||
if (!parentIndexPath) return;
|
||
|
||
// Split the path by dots
|
||
const parts = parentIndexPath.split('.');
|
||
|
||
// Extract the last part (index)
|
||
const lastIndex = parts.pop().replace(/\D/g, ''); // Remove any non-numeric characters
|
||
|
||
// Rebuild the path without the last part
|
||
const newPath = parts.join('.');
|
||
|
||
console.log(parentIndexPath);
|
||
console.log(newPath);
|
||
|
||
// Navigate to the target nested object
|
||
let target = getNestedObject(workflows, newPath);
|
||
|
||
console.log("Target before removal:", target);
|
||
|
||
if (!target || !Array.isArray(target.conditions)) {
|
||
console.error("❌ Invalid path or conditions array missing:", parentIndexPath);
|
||
return;
|
||
}
|
||
|
||
// Remove the specified condition group
|
||
delete target.conditions.splice(lastIndex, 1);
|
||
|
||
// Ensure the workflows object is updated in memory
|
||
workflows[wfIndex] = { ...workflows[wfIndex] };
|
||
|
||
updateWorkflowsJson(workflows)
|
||
|
||
// Re-render the UI
|
||
renderWorkflows();
|
||
}
|
||
|
||
|
||
// ---------------------------------------------------
|
||
// Helper function to navigate to a nested object using the provided path
|
||
function getNestedObject(obj, path) {
|
||
// console.log("🔍 Getting nested object:", { obj, path });
|
||
|
||
// Convert bracket notation to dot notation (e.g., '[1].conditions[0]' → '1.conditions.0')
|
||
const keys = path.replace(/\[(\d+)\]/g, '.$1').split('.').filter(Boolean);
|
||
|
||
return keys.reduce((o, key, index) => {
|
||
if (o && o[key] !== undefined) {
|
||
// console.log(`✅ Found: ${keys.slice(0, index + 1).join('.')} ->`, o[key]);
|
||
return o[key];
|
||
} else {
|
||
console.warn(`❌ Failed at: ${keys.slice(0, index + 1).join('.')}`);
|
||
console.warn("🔍 Tried getting nested object:", { obj, path });
|
||
return null;
|
||
}
|
||
}, obj);
|
||
}
|
||
|
||
// ---------------------------------------------------
|
||
// Get workflows JSON
|
||
function getWorkflowsJson()
|
||
{
|
||
return workflows = JSON.parse(getCache('workflows')) || getEmptyWorkflowJson();
|
||
}
|
||
|
||
// ---------------------------------------------------
|
||
// Get workflows JSON
|
||
function updateWorkflowsJson(workflows)
|
||
{
|
||
// Store the updated workflows object back into cache
|
||
setCache('workflows', JSON.stringify(workflows));
|
||
}
|
||
|
||
// ---------------------------------------------------
|
||
// Get empty workflow JSON
|
||
function getEmptyWorkflowJson()
|
||
{
|
||
return emptyWorkflow;
|
||
}
|
||
|
||
// ---------------------------------------------------
|
||
// Save workflows JSON
|
||
function saveWorkflows()
|
||
{
|
||
showSpinner();
|
||
// encode for import
|
||
appConfBase64 = btoa(JSON.stringify(getWorkflowsJson()))
|
||
|
||
// import
|
||
$.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();
|
||
});
|
||
}
|
||
|
||
// ---------------------------------------------------
|
||
// Event listeners
|
||
$(document).on("click", ".add-workflow-btn", function () {
|
||
addWorkflow(getWorkflowsJson());
|
||
});
|
||
|
||
$(document).on("click", ".remove-wf", function () {
|
||
let wfIndex = $(this).attr("wfindex");
|
||
removeWorkflow(getWorkflowsJson(), wfIndex);
|
||
});
|
||
|
||
$(document).on("click", ".duplicate-wf", function () {
|
||
let wfIndex = $(this).attr("wfindex");
|
||
duplicateWorkflow(getWorkflowsJson(), wfIndex);
|
||
});
|
||
|
||
$(document).on("click", ".export-wf", function () {
|
||
let wfIndex = $(this).attr("wfindex");
|
||
exportWorkflow(getWorkflowsJson(), wfIndex);
|
||
});
|
||
|
||
$(document).on("click", ".import-wf", function () {
|
||
let wfIndex = $(this).attr("wfindex");
|
||
importWorkflow(getWorkflowsJson(), wfIndex);
|
||
});
|
||
|
||
$(document).on("click", ".add-condition", function () {
|
||
let wfIndex = $(this).attr("wfindex");
|
||
let parentIndexPath = $(this).attr("parentIndexPath");
|
||
addCondition(getWorkflowsJson(), wfIndex, parentIndexPath);
|
||
});
|
||
|
||
$(document).on("click", ".add-condition-group", function () {
|
||
let wfIndex = $(this).attr("wfindex");
|
||
let parentIndexPath = $(this).attr("parentIndexPath");
|
||
addConditionGroup(getWorkflowsJson(), wfIndex, parentIndexPath);
|
||
});
|
||
|
||
$(document).on("click", ".add-action", function () {
|
||
let wfIndex = $(this).attr("wfIndex");
|
||
addAction(getWorkflowsJson(), wfIndex);
|
||
});
|
||
|
||
// Event Listeners for Removing Conditions
|
||
$(document).on("click", ".remove-condition", function () {
|
||
let wfIndex = $(this).attr("wfindex");
|
||
let parentIndexPath = $(this).attr("parentIndexPath");
|
||
let conditionIndex = parseInt($(this).attr("conditionIndex"), 10);
|
||
|
||
removeCondition(getWorkflowsJson(), wfIndex, parentIndexPath, conditionIndex);
|
||
});
|
||
|
||
// Event Listeners for Removing Actions
|
||
$(document).on("click", ".remove-action", function () {
|
||
let wfIndex = $(this).attr("wfindex");
|
||
let actionIndex = $(this).attr("actionIndex");
|
||
|
||
removeAction(getWorkflowsJson(), wfIndex, actionIndex);
|
||
});
|
||
|
||
// Event Listeners for target condition rows (v2 cross-device targeting)
|
||
$(document).on("click", ".add-target-condition", function () {
|
||
let wfIndex = parseInt($(this).attr("wfIndex"), 10);
|
||
let actionIndex = parseInt($(this).attr("actionIndex"), 10);
|
||
let wfs = getWorkflowsJson();
|
||
|
||
if (!wfs[wfIndex].actions[actionIndex].target) {
|
||
wfs[wfIndex].actions[actionIndex].target = { strategy: "query", conditions: [] };
|
||
}
|
||
if (!wfs[wfIndex].actions[actionIndex].target.conditions) {
|
||
wfs[wfIndex].actions[actionIndex].target.conditions = [];
|
||
}
|
||
|
||
wfs[wfIndex].actions[actionIndex].target.conditions.push({
|
||
field: fieldOptions[0],
|
||
operator: "equals",
|
||
value: ""
|
||
});
|
||
|
||
updateWorkflowsJson(wfs);
|
||
renderWorkflows();
|
||
});
|
||
|
||
$(document).on("click", ".remove-target-condition", function () {
|
||
let wfIndex = parseInt($(this).attr("wfIndex"), 10);
|
||
let actionIndex = parseInt($(this).attr("actionIndex"), 10);
|
||
let tcIdx = parseInt($(this).attr("tcIdx"), 10);
|
||
let wfs = getWorkflowsJson();
|
||
|
||
if (wfs[wfIndex].actions[actionIndex].target && wfs[wfIndex].actions[actionIndex].target.conditions) {
|
||
wfs[wfIndex].actions[actionIndex].target.conditions.splice(tcIdx, 1);
|
||
updateWorkflowsJson(wfs);
|
||
renderWorkflows();
|
||
}
|
||
});
|
||
|
||
// Event Listeners for Removing Condition Groups
|
||
$(document).on("click", ".remove-condition-group", function () {
|
||
let wfIndex = $(this).attr("wfindex");
|
||
let parentIndexPath = $(this).attr("parentIndexPath");
|
||
removeConditionGroup(getWorkflowsJson(), wfIndex, parentIndexPath);
|
||
});
|
||
|
||
// ---------------------------------------------------
|
||
// Handling open/closed state of collapsible panels
|
||
$(document).ready(function() {
|
||
$(".panel-collapse").each(function() {
|
||
let panelId = $(this).attr("id");
|
||
let isOpen = localStorage.getItem(panelId) === "true";
|
||
|
||
if (isOpen) {
|
||
$(this).addClass("in");
|
||
}
|
||
});
|
||
|
||
$(document).on("shown.bs.collapse", ".panel-collapse", function() {
|
||
localStorage.setItem($(this).attr("id"), "true");
|
||
console.log("Panel opened:", $(this).attr("id"));
|
||
});
|
||
|
||
$(document).on("hidden.bs.collapse", ".panel-collapse", function() {
|
||
localStorage.setItem($(this).attr("id"), "false");
|
||
console.log("Panel closed:", $(this).attr("id"));
|
||
});
|
||
});
|
||
|
||
// --------------------------------------
|
||
// Initialize
|
||
$(document).ready(function () {
|
||
getData();
|
||
});
|
||
|
||
|
||
</script>
|