ENGINE: Migrated Assembly Management Functionality

This commit is contained in:
2025-10-28 21:46:19 -06:00
parent fd6601c337
commit fce49f697c
30 changed files with 2636 additions and 8 deletions

View File

@@ -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"
}

View File

@@ -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

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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"
}

View File

@@ -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"
}
]
}

View File

@@ -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"
}
]
}

View File

@@ -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"
}
]
}

View File

@@ -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

View File

@@ -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),

View File

@@ -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,

View File

@@ -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,
}

View File

@@ -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)