mirror of
https://github.com/bunny-lab-io/Borealis.git
synced 2025-12-15 00:35:47 -07:00
ENGINE: Migrated Assembly Management Functionality
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"version": 1,
|
||||
"name": "Query OS String [LINUX]",
|
||||
"description": "Simply returns the operating system in the output.",
|
||||
"category": "application",
|
||||
"type": "ansible",
|
||||
"script": "LS0tCi0gbmFtZTogR2V0IG9wZXJhdGluZyBzeXN0ZW0gaW5mb3JtYXRpb24KICBob3N0czogYWxsCiAgZ2F0aGVyX2ZhY3RzOiB5ZXMKCiAgdGFza3M6CiAgICAtIG5hbWU6IFByaW50IE9TIHN0cmluZwogICAgICBkZWJ1ZzoKICAgICAgICBtc2c6ICJ7eyBhbnNpYmxlX2Rpc3RyaWJ1dGlvbiB9fSB7eyBhbnNpYmxlX2Rpc3RyaWJ1dGlvbl92ZXJzaW9uIH19IHt7IGFuc2libGVfZGlzdHJpYnV0aW9uX3JlbGVhc2UgfCBkZWZhdWx0KCcnKSB9fSIK",
|
||||
"timeout_seconds": 3600,
|
||||
"sites": {
|
||||
"mode": "all",
|
||||
"values": []
|
||||
},
|
||||
"variables": [],
|
||||
"files": [],
|
||||
"script_encoding": "base64"
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"version": 1,
|
||||
"name": "Write Canary to C Drive Root",
|
||||
"description": "Write a basic canary file to the C:\\ drive to determine if SYSTEM-level access is functional within the script engine.",
|
||||
"category": "script",
|
||||
"type": "ansible",
|
||||
"script": "---\n- name: Create Canary.txt on local Windows machine\n hosts: localhost\n connection: local\n gather_facts: no\n\n tasks:\n - name: Write Canary.txt to C:\\\n ansible.windows.win_copy:\n content: \"This is a canary file created by Ansible.\"\n dest: C:\\Canary.txt\n",
|
||||
"timeout_seconds": 3600,
|
||||
"sites": {
|
||||
"mode": "all",
|
||||
"values": []
|
||||
},
|
||||
"variables": [],
|
||||
"files": []
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"version": 1,
|
||||
"name": "Remote Agent Update [WIN]",
|
||||
"description": "Reaches out to the remote Borealis agent and triggers an automatic unattended update from the Github repository.",
|
||||
"category": "script",
|
||||
"type": "powershell",
|
||||
"script": "W0NtZGxldEJpbmRpbmcoKV0KcGFyYW0oCiAgICBbUGFyYW1ldGVyKCldCiAgICBbc3RyaW5nXSRUYXNrTmFtZSA9ICJCb3JlYWxpcyBBZ2VudCIsCgogICAgW1BhcmFtZXRlcigpXQogICAgW3N0cmluZ10kVGFza1BhdGgKKQoKJHRhc2tQYXJhbXMgPSBAeyBUYXNrTmFtZSA9ICRUYXNrTmFtZTsgRXJyb3JBY3Rpb24gPSAnU3RvcCcgfQppZiAoJFRhc2tQYXRoKSB7ICR0YXNrUGFyYW1zLlRhc2tQYXRoID0gJFRhc2tQYXRoIH0KCnRyeSB7CiAgICAkdGFzayA9IEdldC1TY2hlZHVsZWRUYXNrIEB0YXNrUGFyYW1zCn0gY2F0Y2ggewogICAgdGhyb3cgIlNjaGVkdWxlZCB0YXNrICckVGFza05hbWUnIHdhcyBub3QgZm91bmQuIgp9CgokZXhlY0FjdGlvbiA9ICR0YXNrLkFjdGlvbnMgfCBXaGVyZS1PYmplY3QgeyAkXy5DaW1DbGFzcy5DaW1DbGFzc05hbWUgLWVxICdNU0ZUX1Rhc2tFeGVjQWN0aW9uJyB9IHwgU2VsZWN0LU9iamVjdCAtRmlyc3QgMQppZiAoLW5vdCAkZXhlY0FjdGlvbikgeyB0aHJvdyAiU2NoZWR1bGVkIHRhc2sgJyRUYXNrTmFtZScgZG9lcyBub3QgY29udGFpbiBhbiBleGVjdXRhYmxlIGFjdGlvbi4iIH0KCiR3b3JraW5nRGlyZWN0b3J5ID0gJGV4ZWNBY3Rpb24uV29ya2luZ0RpcmVjdG9yeQppZiAoW3N0cmluZ106OklzTnVsbE9yV2hpdGVTcGFjZSgkd29ya2luZ0RpcmVjdG9yeSkpIHsKICAgICRjYW5kaWRhdGUgPSBTcGxpdC1QYXRoIC1QYXRoICRleGVjQWN0aW9uLkV4ZWN1dGUgLVBhcmVudAogICAgaWYgKFtzdHJpbmddOjpJc051bGxPcldoaXRlU3BhY2UoJGNhbmRpZGF0ZSkpIHsKICAgICAgICB0aHJvdyAiVW5hYmxlIHRvIGRldGVybWluZSB3b3JraW5nIGRpcmVjdG9yeSBmb3IgJyRUYXNrTmFtZScuIgogICAgfQogICAgJHdvcmtpbmdEaXJlY3RvcnkgPSAkY2FuZGlkYXRlCn0KCnRyeSB7CiAgICAkYWdlbnRSb290ID0gUmVzb2x2ZS1QYXRoIC1QYXRoICR3b3JraW5nRGlyZWN0b3J5IC1FcnJvckFjdGlvbiBTdG9wCn0gY2F0Y2ggewogICAgdGhyb3cgIlRoZSB3b3JraW5nIGRpcmVjdG9yeSAnJHdvcmtpbmdEaXJlY3RvcnknIGRvZXMgbm90IGV4aXN0LiIKfQoKdHJ5IHsKICAgICRyZXBvUm9vdCA9IFJlc29sdmUtUGF0aCAtUGF0aCAoSm9pbi1QYXRoICRhZ2VudFJvb3QgJy4uXC4uJykgLUVycm9yQWN0aW9uIFN0b3AKfSBjYXRjaCB7CiAgICB0aHJvdyAiVW5hYmxlIHRvIHJlc29sdmUgQm9yZWFsaXMgcmVwb3NpdG9yeSByb290IGZyb20gJyRhZ2VudFJvb3QnLiIKfQoKJHVwZGF0ZVNjcmlwdCA9IEpvaW4tUGF0aCAkcmVwb1Jvb3QgJ1VwZGF0ZS5wczEnCmlmICgtbm90IChUZXN0LVBhdGggJHVwZGF0ZVNjcmlwdCAtUGF0aFR5cGUgTGVhZikpIHsKICAgIHRocm93ICJVcGRhdGUucHMxIG5vdCBmb3VuZCBhdCAnJHVwZGF0ZVNjcmlwdCcuIgp9CgpXcml0ZS1WZXJib3NlICJBZ2VudCBSb290OiAkYWdlbnRSb290IgpXcml0ZS1WZXJib3NlICJSZXBvIFJvb3Q6ICRyZXBvUm9vdCIKV3JpdGUtVmVyYm9zZSAiSW52b2tpbmcgVXBkYXRlLnBzMS4uLiIKClB1c2gtTG9jYXRpb24gJHJlcG9Sb290CiR1cGRhdGVTdWNjZWVkZWQgPSAkZmFsc2UKdHJ5IHsKICAgICYgJHVwZGF0ZVNjcmlwdAogICAgJHVwZGF0ZVN1Y2NlZWRlZCA9ICQ/Cn0gZmluYWxseSB7CiAgICBQb3AtTG9jYXRpb24KfQoKaWYgKC1ub3QgJHVwZGF0ZVN1Y2NlZWRlZCkgewogICAgdGhyb3cgJ1VwZGF0ZS5wczEgVXBkYXRlIEZhaWxlZC4nCn0=",
|
||||
"timeout_seconds": 3600,
|
||||
"sites": {
|
||||
"mode": "all",
|
||||
"values": []
|
||||
},
|
||||
"variables": [],
|
||||
"files": [],
|
||||
"script_encoding": "base64"
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"version": 1,
|
||||
"name": "Write Canary File to C Drive",
|
||||
"description": "Writes a simple text file to the C:\\ drive of the computer. Requires SYSTEM level execution context to work.",
|
||||
"category": "script",
|
||||
"type": "powershell",
|
||||
"script": "IyBEZWZpbmUgdGhlIGZpbGUgcGF0aAokZmlsZVBhdGggPSAiQzpcQ2FuYXJ5LnR4dCIKCiMgV3JpdGUgc29tZSBjb250ZW50IGludG8gdGhlIGZpbGUKJGVudjpjYW5hcnlNZXNzYWdlIHwgT3V0LUZpbGUgLUZpbGVQYXRoICRmaWxlUGF0aCAtRW5jb2RpbmcgVVRGOAo=",
|
||||
"timeout_seconds": 3600,
|
||||
"sites": {
|
||||
"mode": "all",
|
||||
"values": []
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"name": "canaryMessage",
|
||||
"label": "Canary Message",
|
||||
"type": "string",
|
||||
"default": "Hello world!",
|
||||
"required": true,
|
||||
"description": "This is the text that will be written into the canary file."
|
||||
}
|
||||
],
|
||||
"files": [],
|
||||
"script_encoding": "base64"
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"version": 1,
|
||||
"name": "Write Canary File to a Specific Folder",
|
||||
"description": "Writes a basic canary file to a specific folder with specific input.",
|
||||
"category": "script",
|
||||
"type": "powershell",
|
||||
"script": "JGVudjpjYW5hcnlNZXNzYWdlIHwgT3V0LUZpbGUgLUZpbGVQYXRoICRlbnY6Y2FuYXJ5TG9jYXRpb24gLUVuY29kaW5nIFVURjgK",
|
||||
"timeout_seconds": 3600,
|
||||
"sites": {
|
||||
"mode": "all",
|
||||
"values": []
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"name": "canaryLocation",
|
||||
"label": "Folder Location",
|
||||
"type": "string",
|
||||
"default": "C:\\Users\\example\\Desktop\\Canary.txt",
|
||||
"required": true,
|
||||
"description": "Location of Canary File"
|
||||
},
|
||||
{
|
||||
"name": "canaryMessage",
|
||||
"label": "Message",
|
||||
"type": "string",
|
||||
"default": "Hello World!",
|
||||
"required": false,
|
||||
"description": "Message to be written into the canary file."
|
||||
}
|
||||
],
|
||||
"files": [],
|
||||
"script_encoding": "base64"
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"version": 1,
|
||||
"name": "Ad-Hoc Powershell Command [WIN]",
|
||||
"description": "Run an arbitrary powershell command (or series of commands) ad-hoc. Useful for a variety of tasks.",
|
||||
"category": "script",
|
||||
"type": "powershell",
|
||||
"script": "IyBNb3ZlIGludG8gdGhlIGZvbGRlciBvZiB3aGVyZSB0aGlzIHNjcmlwdCBpcyBydW5uaW5nClNldC1Mb2NhdGlvbiAtUGF0aCAoU3BsaXQtUGF0aCAtUGFyZW50ICRQU0NvbW1hbmRQYXRoKQoKIyBSZXBvcnQgdGhlIGNvbW1hbmQgaXNzdWVkIHRvIHRoZSBTdGRPdXQKV3JpdGUtSG9zdCAiQ29tbWFuZCBFeGVjdXRlZDogJGVudjpjb21tYW5kIgoKIyBSdW4gdGhlIENvbW1hbmQKSW52b2tlLUV4cHJlc3Npb24gJGVudjpjb21tYW5kCg==",
|
||||
"timeout_seconds": 3600,
|
||||
"sites": {
|
||||
"mode": "all",
|
||||
"values": []
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"name": "command",
|
||||
"label": "Powershell Command",
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"required": true,
|
||||
"description": "Command to invoke in Powershell"
|
||||
}
|
||||
],
|
||||
"files": [],
|
||||
"script_encoding": "base64"
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"version": 1,
|
||||
"name": "Shutdown Device [WIN]",
|
||||
"description": "Shutdown the device with a configurable countdown.",
|
||||
"category": "script",
|
||||
"type": "powershell",
|
||||
"script": "c2h1dGRvd24uZXhlIC9zIC9mIC90ICRlbnY6dGltZW91dA==",
|
||||
"timeout_seconds": 3600,
|
||||
"sites": {
|
||||
"mode": "all",
|
||||
"values": []
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"name": "timeout",
|
||||
"label": "Shutdown Countdown",
|
||||
"type": "number",
|
||||
"default": "30",
|
||||
"required": true,
|
||||
"description": "Amount of time to wait before shutdown occurs."
|
||||
}
|
||||
],
|
||||
"files": [],
|
||||
"script_encoding": "base64"
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"version": 1,
|
||||
"name": "ipconfig Release & Renew",
|
||||
"description": "Releases the current IP address, waits n seconds, then renews the IP asking for a new one.",
|
||||
"category": "script",
|
||||
"type": "powershell",
|
||||
"script": "aXBjb25maWcgL3JlbGVhc2UgClN0YXJ0LVNsZWVwIC1TZWNvbmRzICRlbnY6c2xlZXBUaW1lCmlwY29uZmlnIC9yZW5ldw==",
|
||||
"timeout_seconds": 3600,
|
||||
"sites": {
|
||||
"mode": "all",
|
||||
"values": []
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"name": "sleepTime",
|
||||
"label": "Seconds Until Renew",
|
||||
"type": "string",
|
||||
"default": "5",
|
||||
"required": true,
|
||||
"description": "Number of seconds between releasing the IP and asking for a new one."
|
||||
}
|
||||
],
|
||||
"files": [],
|
||||
"script_encoding": "base64"
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"version": 1,
|
||||
"name": "RocketChat [WIN]",
|
||||
"description": "Silently pulls down the most recent x64 MSI installer of RocketChat from Github and installs it for all users on the device.",
|
||||
"category": "script",
|
||||
"type": "powershell",
|
||||
"script": "IyMjIFBvd2VyU2hlbGwgU2NyaXB0IHRvIERvd25sb2FkIGFuZCBJbnN0YWxsIHRoZSBMYXRlc3QgUm9ja2V0LkNoYXQgTVNJIEluc3RhbGxlcgoKIyBEZWZpbmUgdmFyaWFibGVzIGZvciB0aGUgR2l0SHViIHJlcG9zaXRvcnkgYW5kIHRoZSBsb2NhbCBkb3dubG9hZCBwYXRoCiRHaXRIdWJSZXBvID0gImh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3MvUm9ja2V0Q2hhdC9Sb2NrZXQuQ2hhdC5FbGVjdHJvbi9yZWxlYXNlcy9sYXRlc3QiCiREb3dubG9hZFBhdGggPSAiJGVudjpURU1QXHJvY2tldGNoYXQtbGF0ZXN0Lm1zaSIKCnRyeSB7CiAgICAjIFN0ZXAgMTogRmV0Y2ggdGhlIGxhdGVzdCByZWxlYXNlIGluZm9ybWF0aW9uIGZyb20gR2l0SHViCiAgICBXcml0ZS1Ib3N0ICJGZXRjaGluZyB0aGUgbGF0ZXN0IHJlbGVhc2UgaW5mb3JtYXRpb24gZnJvbSBHaXRIdWIuLi4iCiAgICAkUmVsZWFzZUluZm8gPSBJbnZva2UtUmVzdE1ldGhvZCAtVXJpICRHaXRIdWJSZXBvIC1Vc2VCYXNpY1BhcnNpbmcKCiAgICAjIFN0ZXAgMjogRXh0cmFjdCB0aGUgVVJMIG9mIHRoZSB4NjQgTVNJIGZpbGUgZnJvbSB0aGUgcmVsZWFzZSBhc3NldHMKICAgICRNU0lBc3NldCA9ICRSZWxlYXNlSW5mby5hc3NldHMgfCBXaGVyZS1PYmplY3QgeyAkXy5uYW1lIC1saWtlICIqeDY0Lm1zaSIgfSB8IFNlbGVjdC1PYmplY3QgLUZpcnN0IDEKICAgIGlmICgtbm90ICRNU0lBc3NldCkgewogICAgICAgIFdyaXRlLUVycm9yICJObyB4NjQgTVNJIGluc3RhbGxlciBmb3VuZCBpbiB0aGUgbGF0ZXN0IHJlbGVhc2UuIgogICAgICAgIGV4aXQgMQogICAgfQoKICAgICRNU0lVcmwgPSAkTVNJQXNzZXQuYnJvd3Nlcl9kb3dubG9hZF91cmwKICAgIFdyaXRlLUhvc3QgIkZvdW5kIHg2NCBNU0kgaW5zdGFsbGVyOiAkTVNJVXJsIgoKICAgICMgU3RlcCAzOiBEb3dubG9hZCB0aGUgTVNJIGluc3RhbGxlciB0byB0aGUgbG9jYWwgcGF0aAogICAgV3JpdGUtSG9zdCAiRG93bmxvYWRpbmcgdGhlIHg2NCBNU0kgaW5zdGFsbGVyLi4uIgogICAgSW52b2tlLVdlYlJlcXVlc3QgLVVyaSAkTVNJVXJsIC1PdXRGaWxlICREb3dubG9hZFBhdGgKCiAgICBpZiAoLU5vdCAoVGVzdC1QYXRoICREb3dubG9hZFBhdGgpKSB7CiAgICAgICAgV3JpdGUtRXJyb3IgIkZhaWxlZCB0byBkb3dubG9hZCB0aGUgTVNJIGluc3RhbGxlci4iCiAgICAgICAgZXhpdCAxCiAgICB9CgogICAgV3JpdGUtSG9zdCAieDY0IE1TSSBpbnN0YWxsZXIgZG93bmxvYWRlZCBzdWNjZXNzZnVsbHkgdG8gJERvd25sb2FkUGF0aCIKCiAgICAjIFN0ZXAgNDogSW5zdGFsbCB0aGUgTVNJIGluc3RhbGxlciBzaWxlbnRseSB3aXRoIGFkbWluIHByaXZpbGVnZXMKICAgIFdyaXRlLUhvc3QgIkluc3RhbGxpbmcgUm9ja2V0LkNoYXQgc2lsZW50bHkuLi4iCiAgICAkQXJndW1lbnRzID0gIi9pIGAiJERvd25sb2FkUGF0aGAiIC9xdWlldCAvbm9yZXN0YXJ0IEFMTFVTRVJTPTEiCiAgICBTdGFydC1Qcm9jZXNzIG1zaWV4ZWMuZXhlIC1Bcmd1bWVudExpc3QgJEFyZ3VtZW50cyAtV2FpdCAtTm9OZXdXaW5kb3cKCiAgICBXcml0ZS1Ib3N0ICJSb2NrZXQuQ2hhdCBpbnN0YWxsYXRpb24gY29tcGxldGVkIHN1Y2Nlc3NmdWxseS4iCgp9IGNhdGNoIHsKICAgICMgQ2F0Y2ggYW5kIGRpc3BsYXkgYW55IGVycm9ycyBkdXJpbmcgdGhlIHByb2Nlc3MKICAgIFdyaXRlLUVycm9yICJBbiBlcnJvciBvY2N1cnJlZDogJF8iCn0gZmluYWxseSB7CiAgICAjIFN0ZXAgNTogQ2xlYW4gdXAgdGhlIGRvd25sb2FkZWQgZmlsZSBpZiBuZWVkZWQKICAgIGlmIChUZXN0LVBhdGggJERvd25sb2FkUGF0aCkgewogICAgICAgIFJlbW92ZS1JdGVtICREb3dubG9hZFBhdGggLUZvcmNlCiAgICAgICAgV3JpdGUtSG9zdCAiQ2xlYW5lZCB1cCB0aGUgZG93bmxvYWRlZCBpbnN0YWxsZXIuIgogICAgfQp9Cg==",
|
||||
"timeout_seconds": 3600,
|
||||
"sites": {
|
||||
"mode": "all",
|
||||
"values": []
|
||||
},
|
||||
"variables": [],
|
||||
"files": [],
|
||||
"script_encoding": "base64"
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"version": 1,
|
||||
"name": "PuTTY [WIN]",
|
||||
"description": "",
|
||||
"category": "application",
|
||||
"type": "powershell",
|
||||
"script": "PCMgUHVUVFkgaW5zdGFsbGVyIChzaWxlbnQsIGluc3RhbGwtb25seSkgIz4KCmZ1bmN0aW9uIEVuYWJsZS1UbHMxMiB7CiAgICB0cnkgeyBbTmV0LlNlcnZpY2VQb2ludE1hbmFnZXJdOjpTZWN1cml0eVByb3RvY29sID0gW0VudW1dOjpUb09iamVjdChbTmV0LlNlY3VyaXR5UHJvdG9jb2xUeXBlXSwzMDcyKSB9IGNhdGNoIHt9Cn0KCmZ1bmN0aW9uIEdldC1BcmNoVXJsIHsKICAgICR2ZXIgPSAiMC44MyIKICAgIGlmIChbSW50UHRyXTo6U2l6ZSAtZXEgNCkgewogICAgICAgIHJldHVybiAiaHR0cHM6Ly90aGUuZWFydGgubGkvfnNndGF0aGFtL3B1dHR5LyR2ZXIvdzMyL3B1dHR5LSR2ZXItaW5zdGFsbGVyLm1zaSIKICAgIH0gZWxzZSB7CiAgICAgICAgcmV0dXJuICJodHRwczovL3RoZS5lYXJ0aC5saS9+c2d0YXRoYW0vcHV0dHkvJHZlci93NjQvcHV0dHktNjRiaXQtJHZlci1pbnN0YWxsZXIubXNpIgogICAgfQp9CgpmdW5jdGlvbiBUcnktRG93bmxvYWQgewogICAgcGFyYW0oW3N0cmluZ10kVXJsLFtzdHJpbmddJERlc3QpCiAgICBFbmFibGUtVGxzMTIKICAgIHRyeSB7CiAgICAgICAgJHdjID0gTmV3LU9iamVjdCBTeXN0ZW0uTmV0LldlYkNsaWVudAogICAgICAgICR3Yy5IZWFkZXJzLkFkZChbU3lzdGVtLk5ldC5IdHRwUmVxdWVzdEhlYWRlcl06OlVzZXJBZ2VudCwiTW96aWxsYS81LjAiKQogICAgICAgICR3Yy5Eb3dubG9hZEZpbGUoJFVybCwkRGVzdCkKICAgICAgICBpZiAoVGVzdC1QYXRoICREZXN0KSB7IHJldHVybiAkdHJ1ZSB9CiAgICB9IGNhdGNoIHt9CiAgICB0cnkgewogICAgICAgIEltcG9ydC1Nb2R1bGUgQml0c1RyYW5zZmVyIC1FcnJvckFjdGlvbiBTaWxlbnRseUNvbnRpbnVlIHwgT3V0LU51bGwKICAgICAgICBTdGFydC1CaXRzVHJhbnNmZXIgLVNvdXJjZSAkVXJsIC1EZXN0aW5hdGlvbiAkRGVzdCAtRXJyb3JBY3Rpb24gU3RvcAogICAgICAgIGlmIChUZXN0LVBhdGggJERlc3QpIHsgcmV0dXJuICR0cnVlIH0KICAgIH0gY2F0Y2gge30KICAgIHRyeSB7CiAgICAgICAgSW52b2tlLVdlYlJlcXVlc3QgLVVyaSAkVXJsIC1Vc2VCYXNpY1BhcnNpbmcgLU91dEZpbGUgJERlc3QgLUVycm9yQWN0aW9uIFN0b3AKICAgICAgICBpZiAoVGVzdC1QYXRoICREZXN0KSB7IHJldHVybiAkdHJ1ZSB9CiAgICB9IGNhdGNoIHt9CiAgICB0cnkgewogICAgICAgICRjdXJsID0gR2V0LUNvbW1hbmQgY3VybC5leGUgLUVycm9yQWN0aW9uIFNpbGVudGx5Q29udGludWUKICAgICAgICBpZiAoJGN1cmwpIHsKICAgICAgICAgICAgJiAkY3VybC5QYXRoIC1MIC1vICREZXN0ICRVcmwgfCBPdXQtTnVsbAogICAgICAgICAgICBpZiAoVGVzdC1QYXRoICREZXN0KSB7IHJldHVybiAkdHJ1ZSB9CiAgICAgICAgfQogICAgfSBjYXRjaCB7fQogICAgcmV0dXJuICRmYWxzZQp9CgojIHByZXZlbnQgaW5zdGFsbCB3aGlsZSBydW5uaW5nCmlmIChHZXQtUHJvY2VzcyAtTmFtZSAicHV0dHkiIC1FcnJvckFjdGlvbiBTaWxlbnRseUNvbnRpbnVlKSB7IGV4aXQgMSB9CgokdXJsICAgICA9IEdldC1BcmNoVXJsCiRtc2lQYXRoID0gSm9pbi1QYXRoIC1QYXRoICRlbnY6VEVNUCAtQ2hpbGRQYXRoICgicHV0dHlfezB9Lm1zaSIgLWYgW0RhdGVUaW1lXTo6VXRjTm93LlRvU3RyaW5nKCJ5eXl5TU1kZEhIbW1zcyIpKQoKaWYgKC1ub3QgKFRyeS1Eb3dubG9hZCAtVXJsICR1cmwgLURlc3QgJG1zaVBhdGgpKSB7IGV4aXQgMiB9CgokcHJvYyA9IFN0YXJ0LVByb2Nlc3MgIm1zaWV4ZWMuZXhlIiAtQXJndW1lbnRMaXN0ICIvaSBgIiRtc2lQYXRoYCIgL3FuIC9ub3Jlc3RhcnQiIC1XYWl0IC1QYXNzVGhydSAtRXJyb3JBY3Rpb24gU2lsZW50bHlDb250aW51ZQokY29kZSA9IGlmICgkcHJvYykgeyAkcHJvYy5FeGl0Q29kZSB9IGVsc2UgeyAxIH0KClJlbW92ZS1JdGVtICRtc2lQYXRoIC1Gb3JjZSAtRXJyb3JBY3Rpb24gU2lsZW50bHlDb250aW51ZQpleGl0ICRjb2RlCg==",
|
||||
"timeout_seconds": 3600,
|
||||
"sites": {
|
||||
"mode": "all",
|
||||
"values": []
|
||||
},
|
||||
"variables": [],
|
||||
"files": [],
|
||||
"script_encoding": "base64"
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"version": 1,
|
||||
"name": "Microsoft Visual C++ Redistributable (2015-2022) [WIN]",
|
||||
"description": "",
|
||||
"category": "application",
|
||||
"type": "powershell",
|
||||
"script": "V3JpdGUtSG9zdCAiTWljcm9zb2Z0IFZpc3VhbCBDKysgUmVkaXN0cmlidXRhYmxlICgyMDE14oCTMjAyMikiCldyaXRlLUhvc3QgIj09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PSIKCiMgVExTIGVuYWJsZW1lbnQKdHJ5IHsKICAgIFtOZXQuU2VydmljZVBvaW50TWFuYWdlcl06OlNlY3VyaXR5UHJvdG9jb2wgPSBbRW51bV06OlRvT2JqZWN0KFtOZXQuU2VjdXJpdHlQcm90b2NvbFR5cGVdLCAzMDcyKQp9IGNhdGNoIHt9CgpmdW5jdGlvbiBOZXctSHR0cENsaWVudCB7CiAgICAkaGFuZGxlciA9IE5ldy1PYmplY3QgU3lzdGVtLk5ldC5IdHRwLkh0dHBDbGllbnRIYW5kbGVyCiAgICB0cnkgewogICAgICAgICRwcm94eSA9IFtTeXN0ZW0uTmV0LldlYlJlcXVlc3RdOjpEZWZhdWx0V2ViUHJveHkKICAgICAgICBpZiAoJHByb3h5KSB7CiAgICAgICAgICAgICRoYW5kbGVyLlVzZVByb3h5ID0gJHRydWUKICAgICAgICAgICAgJGhhbmRsZXIuUHJveHkgICAgPSAkcHJveHkKICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAkaGFuZGxlci5Vc2VQcm94eSA9ICRmYWxzZQogICAgICAgIH0KICAgIH0gY2F0Y2ggewogICAgICAgICRoYW5kbGVyLlVzZVByb3h5ID0gJGZhbHNlCiAgICB9CiAgICAkY2xpZW50ID0gTmV3LU9iamVjdCBTeXN0ZW0uTmV0Lkh0dHAuSHR0cENsaWVudCgkaGFuZGxlcikKICAgICRjbGllbnQuRGVmYXVsdFJlcXVlc3RIZWFkZXJzLlVzZXJBZ2VudC5QYXJzZUFkZCgiTW96aWxsYS81LjAiKQogICAgcmV0dXJuICRjbGllbnQKfQoKZnVuY3Rpb24gU2F2ZS1SZW1vdGVGaWxlIHsKICAgIHBhcmFtKAogICAgICAgIFtQYXJhbWV0ZXIoTWFuZGF0b3J5PSR0cnVlKV1bc3RyaW5nXSRVcmksCiAgICAgICAgW1BhcmFtZXRlcihNYW5kYXRvcnk9JHRydWUpXVtzdHJpbmddJFBhdGgKICAgICkKICAgICRjbGllbnQgPSBOZXctSHR0cENsaWVudAogICAgdHJ5IHsKICAgICAgICAkcmVzcG9uc2UgPSAkY2xpZW50LkdldEFzeW5jKCRVcmkpLlJlc3VsdAogICAgICAgIGlmICgtbm90ICRyZXNwb25zZS5Jc1N1Y2Nlc3NTdGF0dXNDb2RlKSB7CiAgICAgICAgICAgIFdyaXRlLUhvc3QgIiEgRVJST1I6IERvd25sb2FkIGZhaWxlZDogJCgkcmVzcG9uc2UuU3RhdHVzQ29kZSkiOyBleGl0IDEKICAgICAgICB9CiAgICAgICAgW0lPLkZpbGVdOjpXcml0ZUFsbEJ5dGVzKCRQYXRoLCAkcmVzcG9uc2UuQ29udGVudC5SZWFkQXNCeXRlQXJyYXlBc3luYygpLlJlc3VsdCkKICAgICAgICBpZiAoLW5vdCAoVGVzdC1QYXRoICRQYXRoKSkgeyBXcml0ZS1Ib3N0ICIhIEVSUk9SOiBGaWxlIG5vdCBmb3VuZCBhZnRlciBkb3dubG9hZC4iOyBleGl0IDEgfQogICAgfSBmaW5hbGx5IHsKICAgICAgICAkY2xpZW50LkRpc3Bvc2UoKQogICAgfQp9CgpmdW5jdGlvbiBJbnN0YWxsLVZDIHsKICAgIHBhcmFtKFtQYXJhbWV0ZXIoTWFuZGF0b3J5PSR0cnVlKV1bVmFsaWRhdGVTZXQoJ3g4NicsJ3g2NCcpXVtzdHJpbmddJEFyY2gpCiAgICAkdXJsICA9ICJodHRwczovL2FrYS5tcy92cy8xNy9yZWxlYXNlL3ZjX3JlZGlzdC4kQXJjaC5leGUiCiAgICAkZGVzdCA9IEpvaW4tUGF0aCAoW1N5c3RlbS5JTy5QYXRoXTo6R2V0VGVtcFBhdGgoKSkgKCJ2Y18kQXJjaC5leGUiKQogICAgV3JpdGUtSG9zdCAiLSBEb3dubG9hZGluZzogJHVybCIKICAgIFNhdmUtUmVtb3RlRmlsZSAtVXJpICR1cmwgLVBhdGggJGRlc3QKICAgIFdyaXRlLUhvc3QgIi0gSW5zdGFsbGluZyAoJEFyY2gpIgogICAgJHAgPSBTdGFydC1Qcm9jZXNzICRkZXN0IC1Bcmd1bWVudExpc3QgIi9pbnN0YWxsIC9wYXNzaXZlIC9ub3Jlc3RhcnQiIC1XYWl0IC1QYXNzVGhydSAtTm9OZXdXaW5kb3cKICAgIHN3aXRjaCAoJHAuRXhpdENvZGUpIHsKICAgICAgICAwICAgIHsgV3JpdGUtSG9zdCAiLSAkQXJjaCBpbnN0YWxsZWQgc3VjY2Vzc2Z1bGx5LiIgfQogICAgICAgIDMwMTAgeyBXcml0ZS1Ib3N0ICItICRBcmNoIGluc3RhbGxlZDsgcmVib290IHJlcXVpcmVkLiIgfQogICAgICAgIGRlZmF1bHQgeyBXcml0ZS1Ib3N0ICIhIEVSUk9SOiAkQXJjaCBpbnN0YWxsZXIgZXhpdGVkIHdpdGggY29kZSAkKCRwLkV4aXRDb2RlKS4iIH0KICAgIH0KfQoKaWYgKFtFbnZpcm9ubWVudF06OklzNjRCaXRPcGVyYXRpbmdTeXN0ZW0pIHsKICAgIEluc3RhbGwtVkMgLUFyY2ggeDg2CiAgICBJbnN0YWxsLVZDIC1BcmNoIHg2NAp9IGVsc2UgewogICAgSW5zdGFsbC1WQyAtQXJjaCB4ODYKfQoKV3JpdGUtSG9zdCAiLSBPcGVyYXRpb25zIGNvbXBsZXRlLiIK",
|
||||
"timeout_seconds": 3600,
|
||||
"sites": {
|
||||
"mode": "all",
|
||||
"values": []
|
||||
},
|
||||
"variables": [],
|
||||
"files": [],
|
||||
"script_encoding": "base64"
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
{
|
||||
"tab_name": "Value Parser",
|
||||
"nodes": [
|
||||
{
|
||||
"data": {
|
||||
"body": "",
|
||||
"content": "Fetch JSON from HTTP or remote API endpoint, with CORS proxy option.",
|
||||
"intervalSec": "10",
|
||||
"label": "API Request",
|
||||
"result": "{\n \"status\": \"ok\"\n}",
|
||||
"url": "http://localhost:5000/health",
|
||||
"useProxy": "true"
|
||||
},
|
||||
"dragHandle": ".borealis-node-header",
|
||||
"dragging": false,
|
||||
"height": 124,
|
||||
"id": "node-1754799747658",
|
||||
"position": {
|
||||
"x": 27.333333333333314,
|
||||
"y": 28
|
||||
},
|
||||
"positionAbsolute": {
|
||||
"x": 27.333333333333314,
|
||||
"y": 28
|
||||
},
|
||||
"selected": false,
|
||||
"type": "API_Request",
|
||||
"width": 160
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"content": "Display prettified multi-line JSON from upstream node.",
|
||||
"jsonData": {
|
||||
"status": "ok"
|
||||
},
|
||||
"label": "Display JSON Data"
|
||||
},
|
||||
"dragHandle": ".borealis-node-header",
|
||||
"dragging": false,
|
||||
"height": 150,
|
||||
"id": "node-1754799750393",
|
||||
"position": {
|
||||
"x": 245.33333333333326,
|
||||
"y": 28
|
||||
},
|
||||
"positionAbsolute": {
|
||||
"x": 245.33333333333326,
|
||||
"y": 28
|
||||
},
|
||||
"selected": false,
|
||||
"type": "Node_JSON_Pretty_Display",
|
||||
"width": 260
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"content": "Provide a dot-separated key path (e.g. 'name.en'); outputs the extracted string or 'Key Not Found'.",
|
||||
"keyName": "status",
|
||||
"label": "JSON Value Extractor",
|
||||
"result": "ok"
|
||||
},
|
||||
"dragHandle": ".borealis-node-header",
|
||||
"dragging": false,
|
||||
"height": 145,
|
||||
"id": "node-1754799751513",
|
||||
"position": {
|
||||
"x": 559.3333333333333,
|
||||
"y": 28
|
||||
},
|
||||
"positionAbsolute": {
|
||||
"x": 559.3333333333333,
|
||||
"y": 28
|
||||
},
|
||||
"selected": false,
|
||||
"type": "JSON_Value_Extractor",
|
||||
"width": 160
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"animated": true,
|
||||
"id": "reactflow__edge-node-1754799747658-node-1754799750393",
|
||||
"source": "node-1754799747658",
|
||||
"sourceHandle": null,
|
||||
"style": {
|
||||
"stroke": "#58a6ff",
|
||||
"strokeDasharray": "6 3"
|
||||
},
|
||||
"target": "node-1754799750393",
|
||||
"targetHandle": null,
|
||||
"type": "bezier"
|
||||
},
|
||||
{
|
||||
"animated": true,
|
||||
"id": "reactflow__edge-node-1754799750393-node-1754799751513",
|
||||
"source": "node-1754799750393",
|
||||
"sourceHandle": null,
|
||||
"style": {
|
||||
"stroke": "#58a6ff",
|
||||
"strokeDasharray": "6 3"
|
||||
},
|
||||
"target": "node-1754799751513",
|
||||
"targetHandle": null,
|
||||
"type": "bezier"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
{
|
||||
"tab_name": "Logic Comparison",
|
||||
"nodes": [
|
||||
{
|
||||
"data": {
|
||||
"content": "Store a String or Number",
|
||||
"label": "String Input A",
|
||||
"value": "Bunny"
|
||||
},
|
||||
"dragHandle": ".borealis-node-header",
|
||||
"dragging": false,
|
||||
"height": 67,
|
||||
"id": "node-1754800111049",
|
||||
"position": {
|
||||
"x": 19.333333333333314,
|
||||
"y": 20.666666666666657
|
||||
},
|
||||
"positionAbsolute": {
|
||||
"x": 19.333333333333314,
|
||||
"y": 20.666666666666657
|
||||
},
|
||||
"selected": false,
|
||||
"type": "DataNode",
|
||||
"width": 160
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"accentColor": "#ff8c00",
|
||||
"content": "Store a String or Number",
|
||||
"label": "String Input B",
|
||||
"value": "Bunny"
|
||||
},
|
||||
"dragHandle": ".borealis-node-header",
|
||||
"dragging": false,
|
||||
"height": 67,
|
||||
"id": "node-1754800112609",
|
||||
"position": {
|
||||
"x": 19.33333333333337,
|
||||
"y": 121.33333333333337
|
||||
},
|
||||
"positionAbsolute": {
|
||||
"x": 19.33333333333337,
|
||||
"y": 121.33333333333337
|
||||
},
|
||||
"selected": false,
|
||||
"style": {
|
||||
"--borealis-accent": "#ff8c00",
|
||||
"--borealis-accent-dark": "#b36200",
|
||||
"--borealis-title": "#ff8c00"
|
||||
},
|
||||
"type": "DataNode",
|
||||
"width": 160
|
||||
},
|
||||
{
|
||||
"id": "node-1754800323495",
|
||||
"type": "ComparisonNode",
|
||||
"position": {
|
||||
"x": 271.3333333333333,
|
||||
"y": 69.33333333333333
|
||||
},
|
||||
"data": {
|
||||
"label": "Does A and B Match?",
|
||||
"content": "Compare A and B using Logic, with new range operator.",
|
||||
"value": "1",
|
||||
"accentColor": "#c0ff00",
|
||||
"inputType": "String",
|
||||
"operator": "Equal (==)"
|
||||
},
|
||||
"dragHandle": ".borealis-node-header",
|
||||
"width": 160,
|
||||
"height": 67,
|
||||
"positionAbsolute": {
|
||||
"x": 271.3333333333333,
|
||||
"y": 69.33333333333333
|
||||
},
|
||||
"selected": false,
|
||||
"dragging": false,
|
||||
"style": {
|
||||
"--borealis-accent": "#c0ff00",
|
||||
"--borealis-accent-dark": "#86b300",
|
||||
"--borealis-title": "#c0ff00"
|
||||
}
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"source": "node-1754800111049",
|
||||
"sourceHandle": null,
|
||||
"target": "node-1754800323495",
|
||||
"targetHandle": "a",
|
||||
"type": "bezier",
|
||||
"animated": true,
|
||||
"style": {
|
||||
"strokeDasharray": "6 3",
|
||||
"stroke": "#58a6ff"
|
||||
},
|
||||
"id": "reactflow__edge-node-1754800111049-node-1754800323495a"
|
||||
},
|
||||
{
|
||||
"source": "node-1754800112609",
|
||||
"sourceHandle": null,
|
||||
"target": "node-1754800323495",
|
||||
"targetHandle": "b",
|
||||
"type": "bezier",
|
||||
"animated": true,
|
||||
"style": {
|
||||
"strokeDasharray": "6 3",
|
||||
"stroke": "#58a6ff"
|
||||
},
|
||||
"id": "reactflow__edge-node-1754800112609-node-1754800323495b"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
{
|
||||
"tab_name": "Math Operations",
|
||||
"nodes": [
|
||||
{
|
||||
"id": "node-1754800111049",
|
||||
"type": "DataNode",
|
||||
"position": {
|
||||
"x": 19.333333333333314,
|
||||
"y": 20.666666666666657
|
||||
},
|
||||
"data": {
|
||||
"label": "Number Input A",
|
||||
"content": "Store a String or Number",
|
||||
"value": "5"
|
||||
},
|
||||
"dragHandle": ".borealis-node-header",
|
||||
"width": 160,
|
||||
"height": 67,
|
||||
"selected": false,
|
||||
"positionAbsolute": {
|
||||
"x": 19.333333333333314,
|
||||
"y": 20.666666666666657
|
||||
},
|
||||
"dragging": false
|
||||
},
|
||||
{
|
||||
"id": "node-1754800112609",
|
||||
"type": "DataNode",
|
||||
"position": {
|
||||
"x": 19.33333333333337,
|
||||
"y": 121.33333333333337
|
||||
},
|
||||
"data": {
|
||||
"label": "Number Input B",
|
||||
"content": "Store a String or Number",
|
||||
"accentColor": "#ff8c00",
|
||||
"value": "3"
|
||||
},
|
||||
"dragHandle": ".borealis-node-header",
|
||||
"width": 160,
|
||||
"height": 67,
|
||||
"selected": false,
|
||||
"positionAbsolute": {
|
||||
"x": 19.33333333333337,
|
||||
"y": 121.33333333333337
|
||||
},
|
||||
"dragging": false,
|
||||
"style": {
|
||||
"--borealis-accent": "#ff8c00",
|
||||
"--borealis-accent-dark": "#b36200",
|
||||
"--borealis-title": "#ff8c00"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "node-1754800119705",
|
||||
"type": "MathNode",
|
||||
"position": {
|
||||
"x": 276.66666666666663,
|
||||
"y": 69.33333333333334
|
||||
},
|
||||
"data": {
|
||||
"label": "Multiply A x B",
|
||||
"content": "Perform Math Operations",
|
||||
"value": "15",
|
||||
"operator": "Multiply",
|
||||
"accentColor": "#00d18c"
|
||||
},
|
||||
"dragHandle": ".borealis-node-header",
|
||||
"width": 160,
|
||||
"height": 67,
|
||||
"positionAbsolute": {
|
||||
"x": 276.66666666666663,
|
||||
"y": 69.33333333333334
|
||||
},
|
||||
"selected": false,
|
||||
"dragging": false,
|
||||
"style": {
|
||||
"--borealis-accent": "#00d18c",
|
||||
"--borealis-accent-dark": "#009262",
|
||||
"--borealis-title": "#00d18c"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "node-1754800128555",
|
||||
"type": "DataNode",
|
||||
"position": {
|
||||
"x": 517.3333333333334,
|
||||
"y": 69.33333333333333
|
||||
},
|
||||
"data": {
|
||||
"label": "Usable Result",
|
||||
"content": "Store a String or Number",
|
||||
"value": "15"
|
||||
},
|
||||
"dragHandle": ".borealis-node-header",
|
||||
"width": 160,
|
||||
"height": 67,
|
||||
"positionAbsolute": {
|
||||
"x": 517.3333333333334,
|
||||
"y": 69.33333333333333
|
||||
},
|
||||
"selected": true,
|
||||
"dragging": false
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"source": "node-1754800111049",
|
||||
"sourceHandle": null,
|
||||
"target": "node-1754800119705",
|
||||
"targetHandle": "a",
|
||||
"type": "bezier",
|
||||
"animated": true,
|
||||
"style": {
|
||||
"strokeDasharray": "6 3",
|
||||
"stroke": "#58a6ff"
|
||||
},
|
||||
"id": "reactflow__edge-node-1754800111049-node-1754800119705a"
|
||||
},
|
||||
{
|
||||
"source": "node-1754800112609",
|
||||
"sourceHandle": null,
|
||||
"target": "node-1754800119705",
|
||||
"targetHandle": "b",
|
||||
"type": "bezier",
|
||||
"animated": true,
|
||||
"style": {
|
||||
"strokeDasharray": "6 3",
|
||||
"stroke": "#58a6ff"
|
||||
},
|
||||
"id": "reactflow__edge-node-1754800112609-node-1754800119705b"
|
||||
},
|
||||
{
|
||||
"source": "node-1754800119705",
|
||||
"sourceHandle": null,
|
||||
"target": "node-1754800128555",
|
||||
"targetHandle": null,
|
||||
"type": "bezier",
|
||||
"animated": true,
|
||||
"style": {
|
||||
"strokeDasharray": "6 3",
|
||||
"stroke": "#58a6ff"
|
||||
},
|
||||
"id": "reactflow__edge-node-1754800119705-node-1754800128555"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
{
|
||||
"tab_name": "Text Recognition",
|
||||
"nodes": [
|
||||
{
|
||||
"id": "node-1754800498085",
|
||||
"type": "Borealis_Agent",
|
||||
"position": {
|
||||
"x": 25.999999999999943,
|
||||
"y": 24.000000000000014
|
||||
},
|
||||
"data": {
|
||||
"label": "Borealis Agent",
|
||||
"content": "Select and manage an Agent with dynamic roles",
|
||||
"agent_id": ""
|
||||
},
|
||||
"dragHandle": ".borealis-node-header",
|
||||
"positionAbsolute": {
|
||||
"x": 25.999999999999943,
|
||||
"y": 24.000000000000014
|
||||
},
|
||||
"width": 205,
|
||||
"height": 146,
|
||||
"selected": false,
|
||||
"dragging": false
|
||||
},
|
||||
{
|
||||
"id": "node-1754800514571",
|
||||
"type": "Agent_Role_Screenshot",
|
||||
"position": {
|
||||
"x": 278,
|
||||
"y": 24
|
||||
},
|
||||
"data": {
|
||||
"label": "Agent Role: Screenshot",
|
||||
"content": "Capture screenshot region via agent",
|
||||
"interval": "1000",
|
||||
"x": "250",
|
||||
"y": "100",
|
||||
"w": "300",
|
||||
"h": "200",
|
||||
"visible": "true",
|
||||
"alias": ""
|
||||
},
|
||||
"dragHandle": ".borealis-node-header",
|
||||
"width": 166,
|
||||
"height": 115,
|
||||
"selected": false,
|
||||
"positionAbsolute": {
|
||||
"x": 278,
|
||||
"y": 24
|
||||
},
|
||||
"dragging": false
|
||||
},
|
||||
{
|
||||
"id": "node-1754800556810",
|
||||
"type": "Image_Viewer",
|
||||
"position": {
|
||||
"x": 507.33333333333326,
|
||||
"y": 24
|
||||
},
|
||||
"data": {
|
||||
"label": "Raw Image Viewer",
|
||||
"content": "Visual preview of base64 image with zoom overlay."
|
||||
},
|
||||
"dragHandle": ".borealis-node-header",
|
||||
"width": 160,
|
||||
"height": 69,
|
||||
"selected": false,
|
||||
"positionAbsolute": {
|
||||
"x": 507.33333333333326,
|
||||
"y": 24
|
||||
},
|
||||
"dragging": false
|
||||
},
|
||||
{
|
||||
"id": "node-1754800584420",
|
||||
"type": "BWThresholdNode",
|
||||
"position": {
|
||||
"x": 507.33333333333337,
|
||||
"y": 110
|
||||
},
|
||||
"data": {
|
||||
"label": "BW Threshold",
|
||||
"content": "Applies black & white threshold to base64 image input."
|
||||
},
|
||||
"dragHandle": ".borealis-node-header",
|
||||
"width": 160,
|
||||
"height": 96,
|
||||
"selected": false,
|
||||
"positionAbsolute": {
|
||||
"x": 507.33333333333337,
|
||||
"y": 110
|
||||
},
|
||||
"dragging": false
|
||||
},
|
||||
{
|
||||
"id": "node-1754800597090",
|
||||
"type": "OCR_Text_Extraction",
|
||||
"position": {
|
||||
"x": 46.800008138020814,
|
||||
"y": 280.00000000000006
|
||||
},
|
||||
"data": {
|
||||
"label": "OCR Text Extraction",
|
||||
"content": "Extract Multi-Line Text from Upstream Image Node",
|
||||
"engine": "EasyOCR",
|
||||
"backend": "GPU",
|
||||
"dataType": "Mixed",
|
||||
"customRateEnabled": "true",
|
||||
"customRateMs": "1000",
|
||||
"changeThreshold": "0"
|
||||
},
|
||||
"dragHandle": ".borealis-node-header",
|
||||
"width": 231,
|
||||
"height": 160,
|
||||
"selected": false,
|
||||
"positionAbsolute": {
|
||||
"x": 46.800008138020814,
|
||||
"y": 280.00000000000006
|
||||
},
|
||||
"dragging": false
|
||||
},
|
||||
{
|
||||
"id": "node-1754800680302",
|
||||
"type": "ArrayIndexExtractor",
|
||||
"position": {
|
||||
"x": 497.3333333333333,
|
||||
"y": 280
|
||||
},
|
||||
"data": {
|
||||
"label": "Array Index Extractor",
|
||||
"content": "Output a Specific Array Index's Value",
|
||||
"lineNumber": "1"
|
||||
},
|
||||
"dragHandle": ".borealis-node-header",
|
||||
"width": 210,
|
||||
"height": 121,
|
||||
"selected": false,
|
||||
"positionAbsolute": {
|
||||
"x": 497.3333333333333,
|
||||
"y": 280
|
||||
},
|
||||
"dragging": false
|
||||
},
|
||||
{
|
||||
"id": "node-1754800736215",
|
||||
"type": "DataNode",
|
||||
"position": {
|
||||
"x": 328.6666666666665,
|
||||
"y": 568.6666666666666
|
||||
},
|
||||
"data": {
|
||||
"label": "String / Number",
|
||||
"content": "Store a String or Number",
|
||||
"value": "[ERROR] No base64 image data provided."
|
||||
},
|
||||
"dragHandle": ".borealis-node-header",
|
||||
"width": 226,
|
||||
"height": 67,
|
||||
"selected": true,
|
||||
"positionAbsolute": {
|
||||
"x": 328.6666666666665,
|
||||
"y": 568.6666666666666
|
||||
},
|
||||
"dragging": false
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"source": "node-1754800498085",
|
||||
"sourceHandle": "provisioner",
|
||||
"target": "node-1754800514571",
|
||||
"targetHandle": null,
|
||||
"type": "smoothstep",
|
||||
"animated": true,
|
||||
"style": {
|
||||
"strokeDasharray": "6 3",
|
||||
"stroke": "#58a6ff"
|
||||
},
|
||||
"id": "reactflow__edge-node-1754800498085provisioner-node-1754800514571",
|
||||
"label": "Capture the Screen",
|
||||
"labelBgStyle": {
|
||||
"fill": "#000000"
|
||||
},
|
||||
"labelStyle": {
|
||||
"fill": "#58a6ff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"source": "node-1754800514571",
|
||||
"sourceHandle": null,
|
||||
"target": "node-1754800556810",
|
||||
"targetHandle": null,
|
||||
"type": "bezier",
|
||||
"animated": true,
|
||||
"style": {
|
||||
"strokeDasharray": "6 3",
|
||||
"stroke": "#58a6ff"
|
||||
},
|
||||
"id": "reactflow__edge-node-1754800514571-node-1754800556810"
|
||||
},
|
||||
{
|
||||
"source": "node-1754800514571",
|
||||
"sourceHandle": null,
|
||||
"target": "node-1754800584420",
|
||||
"targetHandle": null,
|
||||
"type": "bezier",
|
||||
"animated": true,
|
||||
"style": {
|
||||
"strokeDasharray": "6 3",
|
||||
"stroke": "#58a6ff"
|
||||
},
|
||||
"id": "reactflow__edge-node-1754800514571-node-1754800584420"
|
||||
},
|
||||
{
|
||||
"source": "node-1754800584420",
|
||||
"sourceHandle": null,
|
||||
"target": "node-1754800597090",
|
||||
"targetHandle": null,
|
||||
"type": "smoothstep",
|
||||
"animated": true,
|
||||
"style": {
|
||||
"strokeDasharray": "6 3",
|
||||
"stroke": "#00d18c"
|
||||
},
|
||||
"id": "reactflow__edge-node-1754800584420-node-1754800597090",
|
||||
"label": "Process Agent Screenshot into Text",
|
||||
"labelBgStyle": {
|
||||
"fill": "#000000"
|
||||
},
|
||||
"labelStyle": {
|
||||
"fill": "#00d18c"
|
||||
}
|
||||
},
|
||||
{
|
||||
"source": "node-1754800597090",
|
||||
"sourceHandle": null,
|
||||
"target": "node-1754800680302",
|
||||
"targetHandle": null,
|
||||
"type": "straight",
|
||||
"animated": true,
|
||||
"style": {
|
||||
"strokeDasharray": "6 3",
|
||||
"stroke": "#00d18c"
|
||||
},
|
||||
"id": "reactflow__edge-node-1754800597090-node-1754800680302",
|
||||
"label": "Extract a Specific Line of Text",
|
||||
"labelBgStyle": {
|
||||
"fill": "#000000"
|
||||
},
|
||||
"labelStyle": {
|
||||
"fill": "#00d18c"
|
||||
}
|
||||
},
|
||||
{
|
||||
"source": "node-1754800680302",
|
||||
"sourceHandle": null,
|
||||
"target": "node-1754800736215",
|
||||
"targetHandle": null,
|
||||
"type": "smoothstep",
|
||||
"animated": true,
|
||||
"style": {
|
||||
"strokeDasharray": "6 3",
|
||||
"stroke": "#ff8c00"
|
||||
},
|
||||
"id": "reactflow__edge-node-1754800680302-node-1754800736215",
|
||||
"label": "Do something with the result",
|
||||
"labelStyle": {
|
||||
"fill": "#ff8c00"
|
||||
},
|
||||
"labelBgStyle": {
|
||||
"fill": "#000000"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -50,7 +50,7 @@ def _build_runtime_config() -> Dict[str, Any]:
|
||||
if api_groups_override:
|
||||
api_groups: Any = api_groups_override
|
||||
else:
|
||||
api_groups = ("core", "auth", "tokens", "enrollment", "devices")
|
||||
api_groups = ("core", "auth", "tokens", "enrollment", "devices", "assemblies")
|
||||
|
||||
return {
|
||||
"HOST": os.environ.get("BOREALIS_ENGINE_HOST", DEFAULT_HOST),
|
||||
|
||||
@@ -215,8 +215,17 @@ def _parse_api_groups(raw: Optional[Any]) -> Tuple[str, ...]:
|
||||
else:
|
||||
return tuple()
|
||||
cleaned = [part.lower() for part in parts if part]
|
||||
if "auth" not in cleaned:
|
||||
cleaned.insert(0, "auth")
|
||||
|
||||
required_prefix: List[str] = []
|
||||
for required in ("core", "auth"):
|
||||
if required not in cleaned:
|
||||
required_prefix.append(required)
|
||||
if required_prefix:
|
||||
cleaned = required_prefix + cleaned
|
||||
|
||||
if "assemblies" not in cleaned:
|
||||
cleaned.append("assemblies")
|
||||
|
||||
return tuple(dict.fromkeys(cleaned))
|
||||
|
||||
|
||||
@@ -277,7 +286,7 @@ def load_runtime_config(overrides: Optional[Mapping[str, Any]] = None) -> Engine
|
||||
runtime_config.get("API_GROUPS") or os.environ.get("BOREALIS_API_GROUPS")
|
||||
)
|
||||
if not api_groups:
|
||||
api_groups = ("auth", "tokens", "enrollment", "devices")
|
||||
api_groups = ("core", "auth", "tokens", "enrollment", "devices", "assemblies")
|
||||
|
||||
settings = EngineSettings(
|
||||
database_path=database_path,
|
||||
|
||||
@@ -31,10 +31,11 @@ from Modules.tokens import routes as token_routes
|
||||
|
||||
from ...server import EngineContext
|
||||
from .access_management.login import register_auth
|
||||
from .assemblies.management import register_assemblies
|
||||
from .devices.approval import register_admin_endpoints
|
||||
from .devices.management import register_management
|
||||
|
||||
DEFAULT_API_GROUPS: Sequence[str] = ("auth", "tokens", "enrollment", "devices")
|
||||
DEFAULT_API_GROUPS: Sequence[str] = ("core", "auth", "tokens", "enrollment", "devices", "assemblies")
|
||||
|
||||
_SERVER_SCOPE_PATTERN = re.compile(r"\\b(?:scope|context|agent_context)=([A-Za-z0-9_-]+)", re.IGNORECASE)
|
||||
_SERVER_AGENT_ID_PATTERN = re.compile(r"\\bagent_id=([^\\s,]+)", re.IGNORECASE)
|
||||
@@ -211,6 +212,7 @@ _GROUP_REGISTRARS: Mapping[str, Callable[[Flask, LegacyServiceAdapters], None]]
|
||||
"tokens": _register_tokens,
|
||||
"enrollment": _register_enrollment,
|
||||
"devices": _register_devices,
|
||||
"assemblies": register_assemblies,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,728 @@
|
||||
# ======================================================
|
||||
# Data\Engine\services\API\assemblies\management.py
|
||||
# Description: Placeholder for assembly management endpoints (work in progress).
|
||||
# Description: Assembly CRUD endpoints for workflows, scripts, and Ansible documents during the Engine migration.
|
||||
#
|
||||
# API Endpoints (if applicable): None
|
||||
# API Endpoints (if applicable):
|
||||
# - POST /api/assembly/create (Token Authenticated) - Creates a folder or assembly file within the requested island.
|
||||
# - POST /api/assembly/edit (Token Authenticated) - Replaces the contents of an existing assembly.
|
||||
# - POST /api/assembly/rename (Token Authenticated) - Renames an assembly file or folder.
|
||||
# - POST /api/assembly/move (Token Authenticated) - Moves an assembly file or folder to a new location.
|
||||
# - POST /api/assembly/delete (Token Authenticated) - Deletes an assembly file or folder.
|
||||
# - GET /api/assembly/list (Token Authenticated) - Lists assemblies and folders for a given island.
|
||||
# - GET /api/assembly/load (Token Authenticated) - Loads an assembly file and returns normalized metadata.
|
||||
# ======================================================
|
||||
|
||||
"Placeholder for API module assemblies/management.py."
|
||||
"""Assembly management endpoints for the Borealis Engine API."""
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Mapping, MutableMapping, Optional, Tuple
|
||||
|
||||
from flask import Blueprint, jsonify, request
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover - typing aide
|
||||
from .. import LegacyServiceAdapters
|
||||
|
||||
|
||||
_ISLAND_DIR_MAP: Mapping[str, str] = {
|
||||
"workflows": "Workflows",
|
||||
"workflow": "Workflows",
|
||||
"scripts": "Scripts",
|
||||
"script": "Scripts",
|
||||
"ansible": "Ansible_Playbooks",
|
||||
"ansible_playbooks": "Ansible_Playbooks",
|
||||
"ansible-playbooks": "Ansible_Playbooks",
|
||||
"playbooks": "Ansible_Playbooks",
|
||||
}
|
||||
|
||||
_BASE64_CLEANER = re.compile(r"\s+")
|
||||
|
||||
|
||||
|
||||
class AssemblyManagementService:
|
||||
"""Implements assembly CRUD helpers for Engine routes."""
|
||||
|
||||
def __init__(self, adapters: "LegacyServiceAdapters") -> None:
|
||||
self.adapters = adapters
|
||||
self.logger = adapters.context.logger or logging.getLogger(__name__)
|
||||
self.service_log = adapters.service_log
|
||||
self._base_root = self._discover_assemblies_root()
|
||||
self._log_action("init", f"assemblies root set to {self._base_root}")
|
||||
|
||||
def _discover_assemblies_root(self) -> Path:
|
||||
module_path = Path(__file__).resolve()
|
||||
for candidate in (module_path, *module_path.parents):
|
||||
engine_dir = candidate / "Engine"
|
||||
assemblies_dir = engine_dir / "Assemblies"
|
||||
if assemblies_dir.is_dir():
|
||||
return assemblies_dir.resolve()
|
||||
|
||||
raise RuntimeError("Engine assemblies directory not found; expected <ProjectRoot>/Engine/Assemblies.")
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Path helpers
|
||||
# ------------------------------------------------------------------
|
||||
def _normalize_relpath(self, value: str) -> str:
|
||||
return (value or "").replace("\\", "/").strip("/")
|
||||
|
||||
def _resolve_island_root(self, island: str) -> Optional[str]:
|
||||
subdir = _ISLAND_DIR_MAP.get((island or "").strip().lower())
|
||||
if not subdir:
|
||||
return None
|
||||
root = (self._base_root / subdir).resolve()
|
||||
return str(root)
|
||||
|
||||
def _resolve_assembly_path(self, island: str, rel_path: str) -> Tuple[str, str, str]:
|
||||
root = self._resolve_island_root(island)
|
||||
if not root:
|
||||
raise ValueError("invalid island")
|
||||
rel_norm = self._normalize_relpath(rel_path)
|
||||
abs_path = os.path.abspath(os.path.join(root, rel_norm))
|
||||
if not abs_path.startswith(root):
|
||||
raise ValueError("invalid path")
|
||||
return root, abs_path, rel_norm
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Document helpers
|
||||
# ------------------------------------------------------------------
|
||||
def _default_ext_for_island(self, island: str, item_type: str = "") -> str:
|
||||
isl = (island or "").lower().strip()
|
||||
if isl in ("workflows", "workflow"):
|
||||
return ".json"
|
||||
if isl in ("ansible", "ansible_playbooks", "ansible-playbooks", "playbooks"):
|
||||
return ".json"
|
||||
if isl in ("scripts", "script"):
|
||||
return ".json"
|
||||
typ = (item_type or "").lower().strip()
|
||||
if typ in ("bash", "batch", "powershell"):
|
||||
return ".json"
|
||||
return ".json"
|
||||
|
||||
def _default_type_for_island(self, island: str, item_type: str = "") -> str:
|
||||
isl = (island or "").lower().strip()
|
||||
if isl in ("ansible", "ansible_playbooks", "ansible-playbooks", "playbooks"):
|
||||
return "ansible"
|
||||
typ = (item_type or "").lower().strip()
|
||||
if typ in ("powershell", "batch", "bash", "ansible"):
|
||||
return typ
|
||||
return "powershell"
|
||||
|
||||
def _empty_document(self, default_type: str = "powershell") -> Dict[str, Any]:
|
||||
return {
|
||||
"version": 1,
|
||||
"name": "",
|
||||
"description": "",
|
||||
"category": "application" if (default_type or "").lower() == "ansible" else "script",
|
||||
"type": default_type or "powershell",
|
||||
"script": "",
|
||||
"timeout_seconds": 3600,
|
||||
"sites": {"mode": "all", "values": []},
|
||||
"variables": [],
|
||||
"files": [],
|
||||
}
|
||||
|
||||
def _decode_base64_text(self, value: Any) -> Optional[str]:
|
||||
if not isinstance(value, str):
|
||||
return None
|
||||
stripped = value.strip()
|
||||
if not stripped:
|
||||
return ""
|
||||
try:
|
||||
cleaned = _BASE64_CLEANER.sub("", stripped)
|
||||
except Exception:
|
||||
cleaned = stripped
|
||||
try:
|
||||
decoded = base64.b64decode(cleaned, validate=True)
|
||||
except Exception:
|
||||
return None
|
||||
try:
|
||||
return decoded.decode("utf-8")
|
||||
except Exception:
|
||||
return decoded.decode("utf-8", errors="replace")
|
||||
|
||||
def _decode_script_content(self, value: Any, encoding_hint: str = "") -> str:
|
||||
encoding = (encoding_hint or "").strip().lower()
|
||||
if isinstance(value, str):
|
||||
if encoding in ("base64", "b64", "base-64"):
|
||||
decoded = self._decode_base64_text(value)
|
||||
if decoded is not None:
|
||||
return decoded.replace("\r\n", "\n")
|
||||
decoded = self._decode_base64_text(value)
|
||||
if decoded is not None:
|
||||
return decoded.replace("\r\n", "\n")
|
||||
return value.replace("\r\n", "\n")
|
||||
return ""
|
||||
|
||||
def _encode_script_content(self, script_text: Any) -> str:
|
||||
if not isinstance(script_text, str):
|
||||
script_text = "" if script_text is None else str(script_text)
|
||||
normalized = script_text.replace("\r\n", "\n")
|
||||
if not normalized:
|
||||
return ""
|
||||
encoded = base64.b64encode(normalized.encode("utf-8"))
|
||||
return encoded.decode("ascii")
|
||||
|
||||
def _prepare_storage(self, doc: Dict[str, Any]) -> Dict[str, Any]:
|
||||
stored: Dict[str, Any] = {}
|
||||
for key, value in (doc or {}).items():
|
||||
if key == "script":
|
||||
stored[key] = self._encode_script_content(value)
|
||||
else:
|
||||
stored[key] = value
|
||||
stored["script_encoding"] = "base64"
|
||||
return stored
|
||||
|
||||
def _normalize_document(self, obj: Any, default_type: str, base_name: str) -> Dict[str, Any]:
|
||||
doc = self._empty_document(default_type)
|
||||
if not isinstance(obj, dict):
|
||||
obj = {}
|
||||
base = (base_name or "assembly").strip()
|
||||
doc["name"] = str(obj.get("name") or obj.get("display_name") or base)
|
||||
doc["description"] = str(obj.get("description") or "")
|
||||
category = str(obj.get("category") or doc["category"]).strip().lower()
|
||||
if category in ("script", "application"):
|
||||
doc["category"] = category
|
||||
typ = str(obj.get("type") or obj.get("script_type") or default_type or "powershell").strip().lower()
|
||||
if typ in ("powershell", "batch", "bash", "ansible"):
|
||||
doc["type"] = typ
|
||||
script_val = obj.get("script")
|
||||
content_val = obj.get("content")
|
||||
script_lines = obj.get("script_lines")
|
||||
if isinstance(script_lines, list):
|
||||
try:
|
||||
doc["script"] = "\n".join(str(line) for line in script_lines)
|
||||
except Exception:
|
||||
doc["script"] = ""
|
||||
elif isinstance(script_val, str):
|
||||
doc["script"] = script_val
|
||||
elif isinstance(content_val, str):
|
||||
doc["script"] = content_val
|
||||
else:
|
||||
doc["script"] = ""
|
||||
|
||||
encoding_hint = str(obj.get("script_encoding") or obj.get("scriptEncoding") or "").strip().lower()
|
||||
doc["script"] = self._decode_script_content(doc.get("script"), encoding_hint)
|
||||
if encoding_hint in ("base64", "b64", "base-64"):
|
||||
doc["script_encoding"] = "base64"
|
||||
else:
|
||||
doc["script_encoding"] = "plain"
|
||||
|
||||
timeout = obj.get("timeout_seconds")
|
||||
if isinstance(timeout, (int, float)) and timeout > 0:
|
||||
doc["timeout_seconds"] = int(timeout)
|
||||
|
||||
sites = obj.get("sites")
|
||||
if isinstance(sites, dict):
|
||||
mode = str(sites.get("mode") or doc["sites"]["mode"]).strip().lower()
|
||||
if mode in ("all", "include", "exclude"):
|
||||
doc["sites"]["mode"] = mode
|
||||
values = sites.get("values")
|
||||
if isinstance(values, list):
|
||||
doc["sites"]["values"] = [str(v) for v in values if str(v).strip()]
|
||||
|
||||
variables = obj.get("variables") or obj.get("variable_definitions")
|
||||
if isinstance(variables, list):
|
||||
normalized_vars: List[Dict[str, Any]] = []
|
||||
for entry in variables:
|
||||
if not isinstance(entry, dict):
|
||||
continue
|
||||
normalized_vars.append(
|
||||
{
|
||||
"name": str(entry.get("name") or entry.get("variable") or "").strip(),
|
||||
"label": str(entry.get("label") or "").strip(),
|
||||
"description": str(entry.get("description") or "").strip(),
|
||||
"type": str(entry.get("type") or "string").strip().lower() or "string",
|
||||
"default": entry.get("default"),
|
||||
"required": bool(entry.get("required")),
|
||||
}
|
||||
)
|
||||
doc["variables"] = normalized_vars
|
||||
|
||||
files = obj.get("files")
|
||||
if isinstance(files, list):
|
||||
normalized_files: List[Dict[str, Any]] = []
|
||||
for entry in files:
|
||||
if not isinstance(entry, dict):
|
||||
continue
|
||||
normalized_files.append(
|
||||
{
|
||||
"file_name": str(entry.get("file_name") or entry.get("name") or "").strip(),
|
||||
"content": entry.get("content") or "",
|
||||
}
|
||||
)
|
||||
doc["files"] = normalized_files
|
||||
|
||||
return doc
|
||||
|
||||
def _safe_read_json(self, path: str) -> Dict[str, Any]:
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as handle:
|
||||
return json.load(handle)
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
def _extract_tab_name(self, obj: Mapping[str, Any]) -> str:
|
||||
if not isinstance(obj, Mapping):
|
||||
return ""
|
||||
for key in ("tabName", "tab_name", "name", "title"):
|
||||
val = obj.get(key)
|
||||
if isinstance(val, str) and val.strip():
|
||||
return val.strip()
|
||||
return ""
|
||||
|
||||
def _detect_script_type(self, filename: str) -> str:
|
||||
lower = (filename or "").lower()
|
||||
if lower.endswith(".json") and os.path.isfile(filename):
|
||||
try:
|
||||
obj = self._safe_read_json(filename)
|
||||
if isinstance(obj, dict):
|
||||
typ = str(obj.get("type") or obj.get("script_type") or "").strip().lower()
|
||||
if typ in ("powershell", "batch", "bash", "ansible"):
|
||||
return typ
|
||||
except Exception:
|
||||
pass
|
||||
return "powershell"
|
||||
if lower.endswith(".yml"):
|
||||
return "ansible"
|
||||
if lower.endswith(".ps1"):
|
||||
return "powershell"
|
||||
if lower.endswith(".bat"):
|
||||
return "batch"
|
||||
if lower.endswith(".sh"):
|
||||
return "bash"
|
||||
return "unknown"
|
||||
|
||||
def _load_assembly_document(self, abs_path: str, island: str, type_hint: str = "") -> Dict[str, Any]:
|
||||
base_name = os.path.splitext(os.path.basename(abs_path))[0]
|
||||
default_type = self._default_type_for_island(island, type_hint)
|
||||
if abs_path.lower().endswith(".json"):
|
||||
data = self._safe_read_json(abs_path)
|
||||
return self._normalize_document(data, default_type, base_name)
|
||||
try:
|
||||
with open(abs_path, "r", encoding="utf-8", errors="replace") as handle:
|
||||
content = handle.read()
|
||||
except Exception:
|
||||
content = ""
|
||||
doc = self._empty_document(default_type)
|
||||
doc["name"] = base_name
|
||||
doc["script"] = (content or "").replace("\r\n", "\n")
|
||||
if default_type == "ansible":
|
||||
doc["category"] = "application"
|
||||
return doc
|
||||
|
||||
def _log_action(self, action: str, message: str) -> None:
|
||||
try:
|
||||
self.service_log("assemblies", f"{action}: {message}")
|
||||
except Exception:
|
||||
self.logger.debug("Failed to record assemblies log entry for %s", action, exc_info=True)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# CRUD operations
|
||||
# ------------------------------------------------------------------
|
||||
def create(self, payload: Mapping[str, Any]) -> Tuple[MutableMapping[str, Any], int]:
|
||||
island = (payload.get("island") or "").strip()
|
||||
kind = (payload.get("kind") or "").strip().lower()
|
||||
path_value = (payload.get("path") or "").strip()
|
||||
content_value = payload.get("content")
|
||||
item_type = (payload.get("type") or "").strip().lower()
|
||||
try:
|
||||
root, abs_path, rel_norm = self._resolve_assembly_path(island, path_value)
|
||||
if not rel_norm:
|
||||
return {"error": "path required"}, 400
|
||||
if kind == "folder":
|
||||
os.makedirs(abs_path, exist_ok=True)
|
||||
self._log_action("create-folder", f"island={island} rel_path={rel_norm}")
|
||||
return {"status": "ok"}, 200
|
||||
|
||||
if kind != "file":
|
||||
return {"error": "invalid kind"}, 400
|
||||
|
||||
base, ext = os.path.splitext(abs_path)
|
||||
if not ext:
|
||||
abs_path = base + self._default_ext_for_island(island, item_type)
|
||||
os.makedirs(os.path.dirname(abs_path), exist_ok=True)
|
||||
isl = (island or "").lower()
|
||||
if isl in ("workflows", "workflow"):
|
||||
obj = self._coerce_workflow_dict(content_value)
|
||||
base_name = os.path.splitext(os.path.basename(abs_path))[0]
|
||||
obj.setdefault("tab_name", base_name)
|
||||
with open(abs_path, "w", encoding="utf-8") as handle:
|
||||
json.dump(obj, handle, indent=2)
|
||||
else:
|
||||
obj = self._coerce_generic_dict(content_value)
|
||||
base_name = os.path.splitext(os.path.basename(abs_path))[0]
|
||||
normalized = self._normalize_document(obj, self._default_type_for_island(island, item_type), base_name)
|
||||
with open(abs_path, "w", encoding="utf-8") as handle:
|
||||
json.dump(self._prepare_storage(normalized), handle, indent=2)
|
||||
rel_new = os.path.relpath(abs_path, root).replace(os.sep, "/")
|
||||
self._log_action("create-file", f"island={island} rel_path={rel_new}")
|
||||
return {"status": "ok", "rel_path": rel_new}, 200
|
||||
except ValueError as err:
|
||||
return {"error": str(err)}, 400
|
||||
except Exception as exc: # pragma: no cover - defensive logging
|
||||
self.logger.exception("Failed to create assembly", exc_info=exc)
|
||||
return {"error": str(exc)}, 500
|
||||
|
||||
def edit(self, payload: Mapping[str, Any]) -> Tuple[MutableMapping[str, Any], int]:
|
||||
island = (payload.get("island") or "").strip()
|
||||
path_value = (payload.get("path") or "").strip()
|
||||
content_value = payload.get("content")
|
||||
data_type = (payload.get("type") or "").strip()
|
||||
try:
|
||||
root, abs_path, _ = self._resolve_assembly_path(island, path_value)
|
||||
if not os.path.isfile(abs_path):
|
||||
return {"error": "file not found"}, 404
|
||||
target_abs = abs_path
|
||||
if not abs_path.lower().endswith(".json"):
|
||||
base, _ = os.path.splitext(abs_path)
|
||||
target_abs = base + self._default_ext_for_island(island, data_type)
|
||||
|
||||
isl = (island or "").lower()
|
||||
if isl in ("workflows", "workflow"):
|
||||
obj = self._coerce_workflow_dict(content_value, strict=True)
|
||||
with open(target_abs, "w", encoding="utf-8") as handle:
|
||||
json.dump(obj, handle, indent=2)
|
||||
else:
|
||||
obj = self._coerce_generic_dict(content_value)
|
||||
base_name = os.path.splitext(os.path.basename(target_abs))[0]
|
||||
normalized = self._normalize_document(
|
||||
obj,
|
||||
self._default_type_for_island(island, obj.get("type") if isinstance(obj, dict) else ""),
|
||||
base_name,
|
||||
)
|
||||
with open(target_abs, "w", encoding="utf-8") as handle:
|
||||
json.dump(self._prepare_storage(normalized), handle, indent=2)
|
||||
|
||||
if target_abs != abs_path:
|
||||
try:
|
||||
os.remove(abs_path)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
rel_new = os.path.relpath(target_abs, root).replace(os.sep, "/")
|
||||
self._log_action("edit", f"island={island} rel_path={rel_new}")
|
||||
return {"status": "ok", "rel_path": rel_new}, 200
|
||||
except ValueError as err:
|
||||
return {"error": str(err)}, 400
|
||||
except Exception as exc: # pragma: no cover
|
||||
self.logger.exception("Failed to edit assembly", exc_info=exc)
|
||||
return {"error": str(exc)}, 500
|
||||
|
||||
def rename(self, payload: Mapping[str, Any]) -> Tuple[MutableMapping[str, Any], int]:
|
||||
island = (payload.get("island") or "").strip()
|
||||
kind = (payload.get("kind") or "").strip().lower()
|
||||
path_value = (payload.get("path") or "").strip()
|
||||
new_name = (payload.get("new_name") or "").strip()
|
||||
item_type = (payload.get("type") or "").strip().lower()
|
||||
if not new_name:
|
||||
return {"error": "new_name required"}, 400
|
||||
try:
|
||||
root, old_abs, _ = self._resolve_assembly_path(island, path_value)
|
||||
if kind == "folder":
|
||||
if not os.path.isdir(old_abs):
|
||||
return {"error": "folder not found"}, 404
|
||||
new_abs = os.path.join(os.path.dirname(old_abs), new_name)
|
||||
elif kind == "file":
|
||||
if not os.path.isfile(old_abs):
|
||||
return {"error": "file not found"}, 404
|
||||
base, ext = os.path.splitext(new_name)
|
||||
if not ext:
|
||||
new_name = base + self._default_ext_for_island(island, item_type)
|
||||
new_abs = os.path.join(os.path.dirname(old_abs), os.path.basename(new_name))
|
||||
else:
|
||||
return {"error": "invalid kind"}, 400
|
||||
|
||||
new_abs_norm = os.path.abspath(new_abs)
|
||||
if not new_abs_norm.startswith(root):
|
||||
return {"error": "invalid destination"}, 400
|
||||
|
||||
os.rename(old_abs, new_abs_norm)
|
||||
|
||||
isl = (island or "").lower()
|
||||
if kind == "file" and isl in ("workflows", "workflow"):
|
||||
try:
|
||||
obj = self._safe_read_json(new_abs_norm)
|
||||
base_name = os.path.splitext(os.path.basename(new_abs_norm))[0]
|
||||
for key in ("tabName", "tab_name", "name", "title"):
|
||||
if key in obj:
|
||||
obj[key] = base_name
|
||||
obj.setdefault("tab_name", base_name)
|
||||
with open(new_abs_norm, "w", encoding="utf-8") as handle:
|
||||
json.dump(obj, handle, indent=2)
|
||||
except Exception:
|
||||
self.logger.debug("Failed to normalize workflow metadata after rename", exc_info=True)
|
||||
|
||||
rel_new = os.path.relpath(new_abs_norm, root).replace(os.sep, "/")
|
||||
self._log_action("rename", f"island={island} from={path_value} to={rel_new}")
|
||||
return {"status": "ok", "rel_path": rel_new}, 200
|
||||
except ValueError as err:
|
||||
return {"error": str(err)}, 400
|
||||
except Exception as exc: # pragma: no cover
|
||||
self.logger.exception("Failed to rename assembly", exc_info=exc)
|
||||
return {"error": str(exc)}, 500
|
||||
|
||||
def move(self, payload: Mapping[str, Any]) -> Tuple[MutableMapping[str, Any], int]:
|
||||
island = (payload.get("island") or "").strip()
|
||||
path_value = (payload.get("path") or "").strip()
|
||||
new_path = (payload.get("new_path") or "").strip()
|
||||
kind = (payload.get("kind") or "").strip().lower()
|
||||
try:
|
||||
_, old_abs, _ = self._resolve_assembly_path(island, path_value)
|
||||
root, new_abs, _ = self._resolve_assembly_path(island, new_path)
|
||||
if kind == "folder":
|
||||
if not os.path.isdir(old_abs):
|
||||
return {"error": "folder not found"}, 404
|
||||
else:
|
||||
if not os.path.isfile(old_abs):
|
||||
return {"error": "file not found"}, 404
|
||||
os.makedirs(os.path.dirname(new_abs), exist_ok=True)
|
||||
shutil.move(old_abs, new_abs)
|
||||
rel_new = os.path.relpath(new_abs, root).replace(os.sep, "/")
|
||||
self._log_action("move", f"island={island} from={path_value} to={rel_new}")
|
||||
return {"status": "ok", "rel_path": rel_new}, 200
|
||||
except ValueError as err:
|
||||
return {"error": str(err)}, 400
|
||||
except Exception as exc: # pragma: no cover
|
||||
self.logger.exception("Failed to move assembly", exc_info=exc)
|
||||
return {"error": str(exc)}, 500
|
||||
|
||||
def delete(self, payload: Mapping[str, Any]) -> Tuple[MutableMapping[str, Any], int]:
|
||||
island = (payload.get("island") or "").strip()
|
||||
kind = (payload.get("kind") or "").strip().lower()
|
||||
path_value = (payload.get("path") or "").strip()
|
||||
try:
|
||||
root, abs_path, rel_norm = self._resolve_assembly_path(island, path_value)
|
||||
if not rel_norm:
|
||||
return {"error": "cannot delete root"}, 400
|
||||
if kind == "folder":
|
||||
if not os.path.isdir(abs_path):
|
||||
return {"error": "folder not found"}, 404
|
||||
shutil.rmtree(abs_path)
|
||||
elif kind == "file":
|
||||
if not os.path.isfile(abs_path):
|
||||
return {"error": "file not found"}, 404
|
||||
os.remove(abs_path)
|
||||
else:
|
||||
return {"error": "invalid kind"}, 400
|
||||
self._log_action("delete", f"island={island} rel_path={rel_norm} kind={kind}")
|
||||
return {"status": "ok"}, 200
|
||||
except ValueError as err:
|
||||
return {"error": str(err)}, 400
|
||||
except Exception as exc: # pragma: no cover
|
||||
self.logger.exception("Failed to delete assembly", exc_info=exc)
|
||||
return {"error": str(exc)}, 500
|
||||
|
||||
def list_items(self, island: str) -> Tuple[MutableMapping[str, Any], int]:
|
||||
island = (island or "").strip()
|
||||
try:
|
||||
root = self._resolve_island_root(island)
|
||||
if not root:
|
||||
return {"error": "invalid island"}, 400
|
||||
os.makedirs(root, exist_ok=True)
|
||||
|
||||
items: List[Dict[str, Any]] = []
|
||||
folders: List[str] = []
|
||||
isl = island.lower()
|
||||
|
||||
if isl in ("workflows", "workflow"):
|
||||
exts = (".json",)
|
||||
for dirpath, dirnames, filenames in os.walk(root):
|
||||
rel_root = os.path.relpath(dirpath, root)
|
||||
if rel_root != ".":
|
||||
folders.append(rel_root.replace(os.sep, "/"))
|
||||
for fname in filenames:
|
||||
if not fname.lower().endswith(exts):
|
||||
continue
|
||||
fp = os.path.join(dirpath, fname)
|
||||
rel_path = os.path.relpath(fp, root).replace(os.sep, "/")
|
||||
try:
|
||||
mtime = os.path.getmtime(fp)
|
||||
except Exception:
|
||||
mtime = 0.0
|
||||
obj = self._safe_read_json(fp)
|
||||
tab = self._extract_tab_name(obj)
|
||||
items.append(
|
||||
{
|
||||
"file_name": fname,
|
||||
"rel_path": rel_path,
|
||||
"type": "workflow",
|
||||
"tab_name": tab,
|
||||
"last_edited": time.strftime("%Y-%m-%dT%H:%M:%S", time.localtime(mtime)),
|
||||
"last_edited_epoch": mtime,
|
||||
}
|
||||
)
|
||||
elif isl in ("scripts", "script"):
|
||||
exts = (".json", ".ps1", ".bat", ".sh")
|
||||
for dirpath, dirnames, filenames in os.walk(root):
|
||||
rel_root = os.path.relpath(dirpath, root)
|
||||
if rel_root != ".":
|
||||
folders.append(rel_root.replace(os.sep, "/"))
|
||||
for fname in filenames:
|
||||
if not fname.lower().endswith(exts):
|
||||
continue
|
||||
fp = os.path.join(dirpath, fname)
|
||||
rel_path = os.path.relpath(fp, root).replace(os.sep, "/")
|
||||
try:
|
||||
mtime = os.path.getmtime(fp)
|
||||
except Exception:
|
||||
mtime = 0.0
|
||||
stype = self._detect_script_type(fp)
|
||||
doc = self._load_assembly_document(fp, "scripts", stype)
|
||||
items.append(
|
||||
{
|
||||
"file_name": fname,
|
||||
"rel_path": rel_path,
|
||||
"type": doc.get("type", stype),
|
||||
"name": doc.get("name"),
|
||||
"category": doc.get("category"),
|
||||
"description": doc.get("description"),
|
||||
"last_edited": time.strftime("%Y-%m-%dT%H:%M:%S", time.localtime(mtime)),
|
||||
"last_edited_epoch": mtime,
|
||||
}
|
||||
)
|
||||
else:
|
||||
exts = (".json", ".yml")
|
||||
for dirpath, dirnames, filenames in os.walk(root):
|
||||
rel_root = os.path.relpath(dirpath, root)
|
||||
if rel_root != ".":
|
||||
folders.append(rel_root.replace(os.sep, "/"))
|
||||
for fname in filenames:
|
||||
if not fname.lower().endswith(exts):
|
||||
continue
|
||||
fp = os.path.join(dirpath, fname)
|
||||
rel_path = os.path.relpath(fp, root).replace(os.sep, "/")
|
||||
try:
|
||||
mtime = os.path.getmtime(fp)
|
||||
except Exception:
|
||||
mtime = 0.0
|
||||
stype = self._detect_script_type(fp)
|
||||
doc = self._load_assembly_document(fp, "ansible", stype)
|
||||
items.append(
|
||||
{
|
||||
"file_name": fname,
|
||||
"rel_path": rel_path,
|
||||
"type": doc.get("type", "ansible"),
|
||||
"name": doc.get("name"),
|
||||
"category": doc.get("category"),
|
||||
"description": doc.get("description"),
|
||||
"last_edited": time.strftime("%Y-%m-%dT%H:%M:%S", time.localtime(mtime)),
|
||||
"last_edited_epoch": mtime,
|
||||
}
|
||||
)
|
||||
|
||||
items.sort(key=lambda row: row.get("last_edited_epoch", 0.0), reverse=True)
|
||||
return {"root": root, "items": items, "folders": folders}, 200
|
||||
except ValueError as err:
|
||||
return {"error": str(err)}, 400
|
||||
except Exception as exc: # pragma: no cover
|
||||
self.logger.exception("Failed to list assemblies", exc_info=exc)
|
||||
return {"error": str(exc)}, 500
|
||||
|
||||
def load(self, island: str, rel_path: str) -> Tuple[MutableMapping[str, Any], int]:
|
||||
island = (island or "").strip()
|
||||
rel_path = (rel_path or "").strip()
|
||||
try:
|
||||
root, abs_path, _ = self._resolve_assembly_path(island, rel_path)
|
||||
if not os.path.isfile(abs_path):
|
||||
return {"error": "file not found"}, 404
|
||||
isl = island.lower()
|
||||
if isl in ("workflows", "workflow"):
|
||||
obj = self._safe_read_json(abs_path)
|
||||
return obj, 200
|
||||
doc = self._load_assembly_document(abs_path, island)
|
||||
rel = os.path.relpath(abs_path, root).replace(os.sep, "/")
|
||||
result: Dict[str, Any] = {
|
||||
"file_name": os.path.basename(abs_path),
|
||||
"rel_path": rel,
|
||||
"type": doc.get("type"),
|
||||
"assembly": doc,
|
||||
"content": doc.get("script"),
|
||||
}
|
||||
return result, 200
|
||||
except ValueError as err:
|
||||
return {"error": str(err)}, 400
|
||||
except Exception as exc: # pragma: no cover
|
||||
self.logger.exception("Failed to load assembly", exc_info=exc)
|
||||
return {"error": str(exc)}, 500
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Content coercion
|
||||
# ------------------------------------------------------------------
|
||||
def _coerce_generic_dict(self, value: Any) -> Dict[str, Any]:
|
||||
obj = value
|
||||
if isinstance(obj, str):
|
||||
try:
|
||||
obj = json.loads(obj)
|
||||
except Exception:
|
||||
obj = {}
|
||||
if not isinstance(obj, dict):
|
||||
obj = {}
|
||||
return obj
|
||||
|
||||
def _coerce_workflow_dict(self, value: Any, strict: bool = False) -> Dict[str, Any]:
|
||||
obj = value
|
||||
if isinstance(obj, str):
|
||||
obj = json.loads(obj)
|
||||
if not isinstance(obj, dict):
|
||||
if strict:
|
||||
raise ValueError("invalid content for workflow")
|
||||
obj = {}
|
||||
return obj
|
||||
|
||||
|
||||
def register_assemblies(app, adapters: "LegacyServiceAdapters") -> None:
|
||||
"""Register assembly CRUD endpoints on the Flask app."""
|
||||
|
||||
service = AssemblyManagementService(adapters)
|
||||
blueprint = Blueprint("assemblies", __name__)
|
||||
|
||||
@blueprint.route("/api/assembly/create", methods=["POST"])
|
||||
def _create():
|
||||
payload = request.get_json(silent=True) or {}
|
||||
response, status = service.create(payload)
|
||||
return jsonify(response), status
|
||||
|
||||
@blueprint.route("/api/assembly/edit", methods=["POST"])
|
||||
def _edit():
|
||||
payload = request.get_json(silent=True) or {}
|
||||
response, status = service.edit(payload)
|
||||
return jsonify(response), status
|
||||
|
||||
@blueprint.route("/api/assembly/rename", methods=["POST"])
|
||||
def _rename():
|
||||
payload = request.get_json(silent=True) or {}
|
||||
response, status = service.rename(payload)
|
||||
return jsonify(response), status
|
||||
|
||||
@blueprint.route("/api/assembly/move", methods=["POST"])
|
||||
def _move():
|
||||
payload = request.get_json(silent=True) or {}
|
||||
response, status = service.move(payload)
|
||||
return jsonify(response), status
|
||||
|
||||
@blueprint.route("/api/assembly/delete", methods=["POST"])
|
||||
def _delete():
|
||||
payload = request.get_json(silent=True) or {}
|
||||
response, status = service.delete(payload)
|
||||
return jsonify(response), status
|
||||
|
||||
@blueprint.route("/api/assembly/list", methods=["GET"])
|
||||
def _list():
|
||||
response, status = service.list_items(request.args.get("island", ""))
|
||||
return jsonify(response), status
|
||||
|
||||
@blueprint.route("/api/assembly/load", methods=["GET"])
|
||||
def _load():
|
||||
response, status = service.load(request.args.get("island", ""), request.args.get("path", ""))
|
||||
return jsonify(response), status
|
||||
|
||||
app.register_blueprint(blueprint)
|
||||
|
||||
Reference in New Issue
Block a user