From dabc1b9bd72efeead2906b03ce7a1eeeeb3bc1cd Mon Sep 17 00:00:00 2001 From: LumbarJam Date: Tue, 9 Dec 2025 15:41:17 -0300 Subject: [PATCH] Initial commit: Docker Sandbox Runner complete setup --- .DS_Store | Bin 0 -> 10244 bytes ._.DS_Store | Bin 0 -> 4096 bytes .___init__.py | Bin 0 -> 4096 bytes ._example_workflow.json | Bin 0 -> 4096 bytes ._sandbox_node.py | Bin 0 -> 4096 bytes README.md | 128 +++++++++ __init__.py | 20 ++ __pycache__/__init__.cpython-312.pyc | Bin 0 -> 525 bytes __pycache__/sandbox_node.cpython-312.pyc | Bin 0 -> 8983 bytes example_workflow.json | 348 +++++++++++++++++++++++ requirements.txt | 1 + sandbox_node.py | 188 ++++++++++++ web/.DS_Store | Bin 0 -> 6148 bytes web/._.DS_Store | Bin 0 -> 4096 bytes web/js/._sandbox.js | Bin 0 -> 4096 bytes web/js/sandbox.js | 79 +++++ 16 files changed, 764 insertions(+) create mode 100755 .DS_Store create mode 100755 ._.DS_Store create mode 100755 .___init__.py create mode 100755 ._example_workflow.json create mode 100755 ._sandbox_node.py create mode 100644 README.md create mode 100644 __init__.py create mode 100644 __pycache__/__init__.cpython-312.pyc create mode 100644 __pycache__/sandbox_node.cpython-312.pyc create mode 100755 example_workflow.json create mode 100644 requirements.txt create mode 100644 sandbox_node.py create mode 100755 web/.DS_Store create mode 100755 web/._.DS_Store create mode 100755 web/js/._sandbox.js create mode 100644 web/js/sandbox.js diff --git a/.DS_Store b/.DS_Store new file mode 100755 index 0000000000000000000000000000000000000000..173ddf913de927ea64fc734e2b717ffdd11a5b97 GIT binary patch literal 10244 zcmeHMTWl0n7(V~B*cm#?w6ulgIsf1Rs3B1|>XbqCTPzCPrfd{^y+8+KV+YUINLSWX?b5 zznsfA|2MPeEFlD1b9y}?Dj`I~i%Lbt>Q4%_(`#B0e9g@$0pkft6AiBcVvyuC??4eC z5FijB5FijB5Fl_pAb{U&TEucLgE~MUKp;S190B@%Q1PNN;>%Gk*{cI9wgo_0ifUHy z9`^w(M)hUHm!n*=Q0$ZM9>5O;e~SS;9PKJ~M;YUXHup*=x?F?mj(DL&$dk#6VSjuuz=plBTT>r~f z@6(pQ^49-`cV=MXEF#z~DIob*Iqkj1>xt!Y zZ1`)@-L#d$xlyyPngajX2^KdWr1Lr6R3a=^Q#6EtVBGk8tmF*0HP|j%#|R zWA`MLN*dIep)Gfhk#$ifJmZi@18DD){1d&dll7u(goKHMQm-*u>Ut0?xm16tPTEMQg^BiclfJv`LY zkS)6DIov3R)J{q4p`EqpI`%`?y)zUK;qy1`yonsZhs3EMDi=g4OcRZkDlW!r2uD`& zM);$_Z`d7!AsmTtHGZd;k_HkZ?PNd6lE=s~a-6(DPLYqvX>x{KAeYIv!2B0paZr;CnTT;dZ8aQcn}7`0vlX-2u9#x7==gS88`;d z!gKHvybQ0v33v_Og7@Hk_y9hH&*4k>3NFESa0PyV9|aI*3*~|=)Cfz32BA@C67CY# z2@zqZ&?}^c0X}=7$i;qtHNFrI;!TlwZr*>d2&ZSF*S7oH+B>$l-3au$G=~zZs=8+0 z{97r@)^CcA=OWMEV!}?O0(wYI%}5>X&=K3RW6rO-5;VF0;o`)CUMV9X;;8o1`*WqnA38&y4 zco)vVCvXL(GyFCsuREvYHT`+)=k>&|bd`Rsx`}_K$E|;Sm8V-Le}(Jl zAbwmvp0zE$Vqox1Ojhs@R)|o50+1L3ClDJkFz{^v(m+1nBL)UWIhYCu0iY;W;207T z1d#ygV5q>VXjE`C1V%$(Gz3ONU^E0qLtr!nMnhmU1V%$(Gz3ONU^E1%90H6$^FSC3 z)_k;5qu_Jq~>f99K=~j z;J-f!$&hb5=uZaF1K5cG_a2_G3)6!kX$4^4$*Hy<0G31fkJ$MBu^llRP?N@O{eCUV zY7+IQL3VXTo-64pQR=FcVJNhmA3QTX66@GFZkG$=987=-FaajO1egF5U;<2l2`~XB zzy!V#FrQ7MS2+G9F0qhhRuzH7C@T_-Q!P^+XQ@!?hDt^n&q|~T{e`=&zqZf&->1yL GX__Yt7bnvI literal 0 HcmV?d00001 diff --git a/._example_workflow.json b/._example_workflow.json new file mode 100755 index 0000000000000000000000000000000000000000..bdf878af5e4746fcae14527e0ef16dec6dc6d02c GIT binary patch literal 4096 zcmeH~u};G<5QZ;B2oO@m0u!pdFs60VG_})(wloq$DS}F{At!azsA&=$SAy4JX5cY+ z4ql`OX&eab9qo($M zSa&?WS(TJG;9dFauPf#2W!wWe4zqck<$1#Ej|(PPD&v%^lu_0QfV1!XoJ%%gl6kY? z#iiu)yD1mk7umc3Y!j>F(k5{on{@nG@F;ug__#-Hi}Fi^tE-MS^#>O1 zxBEEAlemx=zZjK=qw^ri0&gm1?ll?~uDfozEU&GsK}irasq30y$Vqox1Ojhs@R)|o50+1L3ClDJkFz{^v(m+1nBL)UWIUt(=a103vf+zv$ zU|0ae0uVl&WCXGn05MQpDmgz_FR`E?Csi*evACqPI5j0Du_RSFAQ+@NG|3_pL`xd| zV+GNpU^E0qLtr!nMnhmU1V%$(Gz3ONU^E0qLtr!nMnhmU1aO4_sIv*eU?3Ngky)%z zlv`sudo apt update && sudo apt install ca-certificates curl gnupg`
**2. Add GPG Key:**
`sudo install -m 0755 -d /etc/apt/keyrings`
`curl -fsSL https://download.docker.com/linux/ubuntu/gpg \| sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg`
**3. Add Repository:**
`echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" \| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null`
**4. Install Docker:**
`sudo apt update && sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin` | `sudo systemctl enable docker --now` | +| **Arch/Manjaro** | **1. Install Package:**
`sudo pacman -Syu docker`
**2. Start Service:**
`sudo systemctl start docker.service` | `sudo systemctl status docker.service` | +| **Fedora/RHEL** | **1. Install Package:**
`sudo dnf -y install docker-ce docker-ce-cli containerd.io --repo docker-ce --repo docker-ce-stable`
**2. Start Service:**
`sudo systemctl start docker` | `sudo systemctl enable docker --now` | +| **macOS** | **Install Docker Desktop (Recommended):** Download the official `.dmg` installer from the Docker website. | Start the application. CLI tools (`docker`) are installed automatically. | +| **Windows** | **Install Docker Desktop (Recommended):** Download the official installer from the Docker website.
**Prerequisite:** Ensure **WSL 2** is installed and enabled for optimal performance. | Start the application. Verify in PowerShell: `docker info` | + +----- + +### Clarification on Windows Installation + +**WSL 2 and Windows:** Docker Desktop for Windows uses a lightweight Linux virtual machine (VM) powered by **WSL 2** by default. This VM hosts the Docker Engine. Therefore, while you install the *Desktop app*, you must have **WSL 2 enabled** for the container engine to function correctly and offer the best performance. + +----- + +## Usage Guide + +### 1\. Add the Node + +Find **"Docker Sandbox Runner"** in the **Advanced/Scripting** menu. + +### 2\. Define Inputs + +1. **Code (`code`):** Insert your Python script. The desired outputs must be assigned to variables `r1` through `r6`. +2. **Dynamic Inputs:** Right-click the node and use **"+ Add Input (Dynamic)"** to inject variables (e.g., `my_int (INT)`). + +### 3\. Example Code + +```python +# Access the injected variable "my_value" from the input slot +# Libraries like math, random, json, re, time are pre-loaded. + +import math + +if my_value > 50: + r1 = "Processed successfully." + r2 = my_value * math.pi +else: + r1 = "Value too low." + r2 = None +``` + +### 4\. Resource Management + +The persistent container `comfyui-sandbox-worker` will run until explicitly stopped. + + * **To Stop Consumption:** Stop the container manually via the Docker Desktop GUI or by CLI: + ```bash + docker stop comfyui-sandbox-worker + ``` + +----- + +## License + +This project is licensed under the **MIT License**. + +``` +MIT License + +Copyright (c) 2025 [Your Name or Project Name] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..f481959 --- /dev/null +++ b/__init__.py @@ -0,0 +1,20 @@ +# __init__.py +from .sandbox_node import DockerSandboxRunner + +# Mapeia a string usada no JSON do workflow para a classe Python +NODE_CLASS_MAPPINGS = { + "DockerSandboxRunner": DockerSandboxRunner +} + +# Mapeia o nome da classe para o nome de exibição no menu do ComfyUI +NODE_DISPLAY_NAME_MAPPINGS = { + "DockerSandboxRunner": "Docker Sandbox Runner" +} + +# ESTE CAMPO É CRÍTICO para o carregamento do JavaScript! +WEB_DIRECTORY = "./web" + +__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', 'WEB_DIRECTORY'] + +# No final do arquivo __init__.py +print("✅ CUSTOM NODE DOCKER SANDBOX REGISTRADO COM SUCESSO.") \ No newline at end of file diff --git a/__pycache__/__init__.cpython-312.pyc b/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f156d15dd20aa59d7df164b40cb363e9eec811f8 GIT binary patch literal 525 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 literal 0 HcmV?d00001 diff --git a/example_workflow.json b/example_workflow.json new file mode 100755 index 0000000..23ce23d --- /dev/null +++ b/example_workflow.json @@ -0,0 +1,348 @@ +{ + "id": "f32fcb48-b15c-444d-93c9-1e4ecf3d9806", + "revision": 0, + "last_node_id": 9, + "last_link_id": 7, + "nodes": [ + { + "id": 6, + "type": "PreviewAny", + "pos": [ + 985, + 215 + ], + "size": [ + 210, + 76 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "name": "source", + "type": "*", + "link": 4 + } + ], + "outputs": [], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.76", + "ue_properties": { + "widget_ue_connectable": {}, + "input_ue_unconnectable": {} + }, + "Node name for S&R": "PreviewAny" + }, + "widgets_values": [], + "color": "#4a7b7b", + "bgcolor": "#335555", + "shape": 1 + }, + { + "id": 8, + "type": "PreviewAny", + "pos": [ + 985, + 499 + ], + "size": [ + 210, + 165 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "name": "source", + "type": "*", + "link": 6 + } + ], + "outputs": [], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.76", + "ue_properties": { + "widget_ue_connectable": {}, + "input_ue_unconnectable": {} + }, + "Node name for S&R": "PreviewAny" + }, + "widgets_values": [], + "color": "#4a7b7b", + "bgcolor": "#335555", + "shape": 1 + }, + { + "id": 4, + "type": "DockerSandboxRunner", + "pos": [ + 555, + 215 + ], + "size": [ + 400, + 256 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "name": "code", + "type": "STRING", + "widget": { + "name": "code" + }, + "link": 2 + }, + { + "name": "my_value (INT)", + "type": "INT", + "link": 3 + } + ], + "outputs": [ + { + "name": "r1", + "type": "*", + "links": [ + 4 + ] + }, + { + "name": "r2", + "type": "*", + "links": [ + 7 + ] + }, + { + "name": "r3", + "type": "*", + "links": null + }, + { + "name": "r4", + "type": "*", + "links": null + }, + { + "name": "r5", + "type": "*", + "links": null + }, + { + "name": "r6", + "type": "*", + "links": null + }, + { + "name": "log", + "type": "STRING", + "links": [ + 6 + ] + } + ], + "properties": { + "Node name for S&R": "DockerSandboxRunner", + "ue_properties": { + "widget_ue_connectable": {}, + "input_ue_unconnectable": {}, + "version": "7.5.2" + } + }, + "widgets_values": [ + "", + 10, + "512m" + ], + "color": "#4a7b7b", + "bgcolor": "#335555", + "shape": 1 + }, + { + "id": 9, + "type": "PreviewAny", + "pos": [ + 985, + 351 + ], + "size": [ + 210, + 88 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "name": "source", + "type": "*", + "link": 7 + } + ], + "outputs": [], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.76", + "ue_properties": { + "widget_ue_connectable": {}, + "input_ue_unconnectable": {} + }, + "Node name for S&R": "PreviewAny" + }, + "widgets_values": [], + "color": "#4a7b7b", + "bgcolor": "#335555", + "shape": 1 + }, + { + "id": 5, + "type": "PrimitiveInt", + "pos": [ + 105, + 215 + ], + "size": [ + 420, + 82 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "INT", + "type": "INT", + "links": [ + 3 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.76", + "ue_properties": { + "widget_ue_connectable": {}, + "input_ue_unconnectable": {} + }, + "Node name for S&R": "PrimitiveInt" + }, + "widgets_values": [ + 60, + "fixed" + ], + "color": "#4a7b7b", + "bgcolor": "#335555", + "shape": 1 + }, + { + "id": 2, + "type": "PrimitiveStringMultiline", + "pos": [ + 105, + 357 + ], + "size": [ + 420, + 255 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "STRING", + "type": "STRING", + "links": [ + 2 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.76", + "Node name for S&R": "PrimitiveStringMultiline", + "ue_properties": { + "widget_ue_connectable": {}, + "input_ue_unconnectable": {}, + "version": "7.5.2" + } + }, + "widgets_values": [ + "# Access the injected variable \"my_value\" from the input slot\n# Libraries like math, random, json, re, time are pre-loaded.\n\nimport math \n\nif my_value > 50:\n r1 = \"Processed successfully.\"\n r2 = my_value * math.pi\nelse:\n r1 = \"Value too low.\"\n r2 = None\n\n\nprint (\"✅✅✅ Finished ✅✅✅\")" + ], + "color": "#4a7b7b", + "bgcolor": "#335555", + "shape": 1 + } + ], + "links": [ + [ + 2, + 2, + 0, + 4, + 0, + "STRING" + ], + [ + 3, + 5, + 0, + 4, + 1, + "INT" + ], + [ + 4, + 4, + 0, + 6, + 0, + "*" + ], + [ + 6, + 4, + 6, + 8, + 0, + "*" + ], + [ + 7, + 4, + 1, + 9, + 0, + "*" + ] + ], + "groups": [], + "config": {}, + "extra": { + "ue_links": [], + "ds": { + "scale": 1.021848739495798, + "offset": [ + 103.33333333333337, + -34.98958333333326 + ] + }, + "links_added_by_ue": [], + "frontendVersion": "1.33.10", + "VHS_latentpreview": false, + "VHS_latentpreviewrate": 0, + "VHS_MetadataImage": true, + "VHS_KeepIntermediate": true + }, + "version": 0.4 +} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..bdb9670 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +docker diff --git a/sandbox_node.py b/sandbox_node.py new file mode 100644 index 0000000..97e8653 --- /dev/null +++ b/sandbox_node.py @@ -0,0 +1,188 @@ +import docker +import json +import re +import uuid +import base64 +import time + +class AnyType(str): + def __ne__(self, __value: object) -> bool: + return False + def __eq__(self, __value: object) -> bool: + return True + +# CLASSE RENOMEADA +class DockerSandboxRunner: + CONTAINER_NAME = "comfyui-sandbox-worker" + DOCKER_IMAGE = "python:3.9-slim" + SANDBOX_USER = "nobody" + + def __init__(self): + self.client = None + try: + self.client = docker.from_env() + except Exception: + print("⚠️ AVISO: Não foi possível conectar ao Docker. Verifique se o Docker Desktop está rodando.") + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "code": ("STRING", {"multiline": True, "placeholder": "# Código limpo, focado apenas na lógica de execução."}), + # CAMPOS OCULTOS PARA CONFIGURAÇÃO TÉCNICA + "timeout": ("INT", {"default": 10, "min": 1, "max": 60, "step": 1, "hidden": True}), + "memory_limit": ("STRING", {"default": "512m", "placeholder": "Ex: 512m, 1g, 2gb", "hidden": True}), + }, + "optional": { + # Campo de ação removido (como solicitado anteriormente). + } + } + + RETURN_TYPES = ( + AnyType("*"), AnyType("*"), AnyType("*"), AnyType("*"), AnyType("*"), AnyType("*"), + "STRING" + ) + RETURN_NAMES = ("r1", "r2", "r3", "r4", "r5", "r6", "log") + FUNCTION = "execute_sandbox" + CATEGORY = "Advanced/Scripting" + + def _ensure_container_running(self, mem_limit): + """Garante que o container worker está vivo e esperando comandos""" + if not self.client: + raise RuntimeError("Docker Client não inicializado. O Docker Desktop está rodando?") + + try: + container = self.client.containers.get(self.CONTAINER_NAME) + if container.status != 'running': + print(f"🛠️ Docker Sandbox: Iniciando container: {self.CONTAINER_NAME}") + container.start() + return container + except docker.errors.NotFound: + print(f"🛠️ Criando container sandbox: {self.CONTAINER_NAME} com {mem_limit} de RAM...") + return self.client.containers.run( + self.DOCKER_IMAGE, + command="tail -f /dev/null", + name=self.CONTAINER_NAME, + detach=True, + mem_limit=mem_limit, + network_mode="none", + user=self.SANDBOX_USER + ) + + def execute_sandbox(self, code, timeout, memory_limit="512m", **kwargs): + log_messages = [] + + final_results = [None] * 6 + object_references = {} + variable_injection_commands = [] + + try: + container = self._ensure_container_running(memory_limit) + except Exception as e: + log_messages.append(f"Docker Error (Startup/Connect): {str(e)}") + return (*final_results, "\n".join(log_messages)) + + # 1. Prepara Variáveis (usando Literal Python) + for key, value in kwargs.items(): + clean_key = key.split(" (")[0].strip().replace(" ", "_") + + if value is None or isinstance(value, bool): + variable_injection_commands.append(f"{clean_key} = {str(value)}") + elif isinstance(value, (int, float, str)): + variable_injection_commands.append(f"{clean_key} = {repr(value)}") + elif isinstance(value, (list, dict)): + try: + variable_injection_commands.append(f"{clean_key} = {repr(value)}") + except: + ref_id = f"__REF__{uuid.uuid4().hex}" + object_references[ref_id] = value + variable_injection_commands.append(f"{clean_key} = '{ref_id}'") + else: + ref_id = f"__REF__{uuid.uuid4().hex}" + object_references[ref_id] = value + variable_injection_commands.append(f"{clean_key} = '{ref_id}'") + + # 2. Constrói o Payload Final (Base64) + injection_script = "\n".join(variable_injection_commands) + + payload_script = f""" +import json +import sys +import math, random, time, re +import signal + +TIMEOUT_SECONDS = {timeout} + +def signal_handler(signum, frame): + print(f"__ERROR_START__TimeoutError: Script execution exceeded {{TIMEOUT_SECONDS}} seconds.__ERROR_END__") + sys.exit(1) + +signal.signal(signal.SIGALRM, signal_handler) +signal.alarm(TIMEOUT_SECONDS) + +{injection_script} + +_user_error = None +try: + exec({repr(code)}) +finally: + signal.alarm(0) + +outputs = {{}} +if _user_error: + outputs['error'] = _user_error +else: + for i in range(1, 7): + vname = f"r{{i}}" + if vname in locals(): + val = locals()[vname] + try: + json.dumps(val) + outputs[vname] = val + except: + outputs[vname] = str(val) + else: + outputs[vname] = None + +sys.stdout.write("__COMFY_RESULT_START__" + json.dumps(outputs) + "__COMFY_RESULT_END__") +""" + + b64_payload = base64.b64encode(payload_script.encode('utf-8')).decode('utf-8') + cmd = f"python3 -c \"import base64; exec(base64.b64decode('{b64_payload}'))\"" + + try: + exec_result = container.exec_run( + cmd=cmd, + workdir="/tmp" + ) + output_text = exec_result.output.decode('utf-8', errors='ignore') + + err_match = re.search(r"__ERROR_START__(.*?)__ERROR_END__", output_text, re.DOTALL) + if err_match: + log_messages.append(f"Script Error (in Docker):\n{err_match.group(1)}") + return (*final_results, "\n".join(log_messages) + "\n" + output_text) + + match = re.search(r"__COMFY_RESULT_START__(.*?)__COMFY_RESULT_END__", output_text, re.DOTALL) + if match: + json_str = match.group(1) + extracted_dict = json.loads(json_str) + + if 'error' in extracted_dict: + log_messages.append(f"Runtime Error:\n{extracted_dict['error']}") + return (*final_results, "\n".join(log_messages) + "\n" + output_text) + + for i in range(6): + key = f"r{i+1}" + val = extracted_dict.get(key) + + if isinstance(val, str) and val in object_references: + final_results[i] = object_references[val] + else: + final_results[i] = val + + log_messages.append("SUCCESS: Script executado com sucesso.") + return (*final_results, "\n".join(log_messages) + "\n" + output_text) + + except Exception as e: + log_messages.append(f"Execution Infrastructure Error (Docker exec failed): {str(e)}") + return (*final_results, "\n".join(log_messages)) \ No newline at end of file diff --git a/web/.DS_Store b/web/.DS_Store new file mode 100755 index 0000000000000000000000000000000000000000..237ef2ae2aa92757684917da10cd8bf0521795e1 GIT binary patch literal 6148 zcmeHK%}N6?5T2?&3yP#{VfCID@nyXDT7L6WZE?M-$P6Uk zWPY;A2b)QVh*!`3l4wCh4VoZ}G9hA~bj>{Y6v#41Hw*;|&FIlxe;#D*vfQyBH|22s zW4T|hH^X*kTR4B+2X2S9?aI($+`L?rtLvw?n@?Z=t#9{mIG+$65)1?b!9Xw&4EzQI zQmP@%nPK$7Krj#td@vyALqZeGj@3|)4s`ki0M21nfv#6uU=jl`J61zjAZ)2XOJ#pz zu%*MEJT5y{LrW*tFJm76^8WF{ezE4H;l$Z6`d}a!IA-9)h6}0xxA>JNANj{AF$xBP zfq%vT&)apo!lt~oe%YSX+Jtt8CL(cF6bN)J#Xv2^7|1ztsZR4J(c#aI)lg;;yQTx< NBA|ps7YzIW1Mf|6CkOxl literal 0 HcmV?d00001 diff --git a/web/._.DS_Store b/web/._.DS_Store new file mode 100755 index 0000000000000000000000000000000000000000..28c42fb20a1f27e695fb64323501fc6476578b13 GIT binary patch literal 4096 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDJkFz{^v(m+1nBL)UWIhYCu0iY;W;207T z1d#ygV5q>VXjE`C1V%$(Gz3ONU^E0qLtr!nMnhmU1V%$(Gz3ONU^E1%90H6$^FSC3 z$Vqox1Ojhs@R)|o50+1L3ClDJkFz{^v(m+1nBL)UWIUt(=a103vf+zv$ zU|0ae0uVl&WCXGn05MQpDmgz_FR`E?Csi*evACqPI5j0Du_RSFAQ+_je7r>_h(5Gy zzY2&R1*0J_8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*A%H6cK%Gqx1_QZ}jLc$% zqSWI2(xT*4g|z&lY=z9clGMDC%>2B>oSaI9oYb@ug`}Lsylh}!7^-VXQ>gxjdqsvp H?*D%PC$uUp literal 0 HcmV?d00001 diff --git a/web/js/sandbox.js b/web/js/sandbox.js new file mode 100644 index 0000000..dfc05fa --- /dev/null +++ b/web/js/sandbox.js @@ -0,0 +1,79 @@ +import { app } from "/scripts/app.js"; + +app.registerExtension({ + name: "DockerSandbox.Extensions", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + // CORRIGIDO: Checa pelo nome da classe Python (sem espaços) + if (nodeData.name === "DockerSandboxRunner") { + + const onNodeCreated = nodeType.prototype.onNodeCreated; + nodeType.prototype.onNodeCreated = function () { + const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined; + this.setSize([350, 150]); + return r; + }; + + // --- FUNÇÃO PARA INJETAR OPÇÕES DE MENU --- + const getExtraMenuOptions = nodeType.prototype.getExtraMenuOptions; + nodeType.prototype.getExtraMenuOptions = function (_, options) { + const me = this; + + // 1. ADD INPUT + options.push({ + content: "+ Add Input (Dynamic)", + callback: () => { + const name = prompt("Enter variable name (e.g., width, my_text):"); + if (!name) return; + + const validTypes = ["STRING", "INT", "FLOAT", "BOOLEAN", "*"]; + let type = prompt( + `Enter type exactly as shown:\n\n${validTypes.join("\n")}\n\n(* = Any/Wildcard)`, + "*" + ); + + if (!type) return; + type = type.toUpperCase(); + + if (!validTypes.includes(type)) { + alert(`Invalid type! Please use one of: ${validTypes.join(", ")}`); + return; + } + + const finalName = `${name} (${type})`; + const exists = me.findInputSlot(finalName); + + if (exists !== -1) { + alert("An input with this name already exists."); + return; + } + + me.addInput(finalName, type); + app.graph.setDirtyCanvas(true, true); + } + }); + + // 2. REMOVE UNUSED INPUTS + options.push({ + content: "- Remove Unused Inputs", + callback: () => { + for (let i = me.inputs.length - 1; i >= 0; i--) { + const input = me.inputs[i]; + // Ignora os inputs fixos (code, timeout, memory_limit) + if (["code", "timeout", "memory_limit"].includes(input.name)) continue; + + // Remove se não estiver conectado + if (!input.link) { + me.removeInput(i); + } + } + app.graph.setDirtyCanvas(true, true); + } + }); + + if (getExtraMenuOptions) { + return getExtraMenuOptions.apply(this, arguments); + } + }; + } + } +}); \ No newline at end of file