diff --git a/web/tests/serverlessWorkers.test.js b/web/tests/serverlessWorkers.test.js index b804923..5c6d25e 100644 --- a/web/tests/serverlessWorkers.test.js +++ b/web/tests/serverlessWorkers.test.js @@ -108,6 +108,50 @@ describe("Runpod serverless worker panel", () => { expect(isRemoteWorker({}, { type: "serverless" })).toBe(true); }); + it("initializes a saved serverless form before it is attached to the document", () => { + // Real document lookups cannot see a form while its card is being built. + vi.spyOn(document, "getElementById").mockReturnValue(null); + const worker = { + id: "saved", name: "Runpod", type: "serverless", endpoint_id: "ep_123", + jobs_per_run: 2, concurrency: 1, timeout_seconds: 900, api_key_configured: true, + }; + const form = createWorkerSettingsForm(createFormUi(), {}, worker); + + expect(form.parentNode).toBeUndefined(); + expect(elements.get("worker-type-saved").value).toBe("serverless"); + for (const field of ["host", "port", "cuda", "args"]) { + expect(elements.get(`${field}-group-saved`).style.display).toBe("none"); + } + expect(elements.get("runpod-text-saved").style.display).toBe("none"); + for (const field of ["endpoint-id", "api-key", "clear-api-key", "jobs-per-run", "concurrency", "timeout-seconds", "serverless-test"]) { + expect(elements.get(`${field}-group-saved`).style.display).toBe("flex"); + } + expect(elements.get("endpoint-id-saved").value).toBe("ep_123"); + expect(elements.get("jobs-per-run-saved").value).toBe("2"); + expect(elements.get("concurrency-saved").value).toBe("1"); + expect(elements.get("timeout-seconds-saved").value).toBe("900"); + expect(elements.get("api-key-saved").value).toBe(""); + expect(elements.get("api-key-saved").placeholder).toMatch(/leave blank/i); + }); + + it("preserves a saved cloud port on first render and applies defaults only on type changes", () => { + vi.spyOn(document, "getElementById").mockReturnValue(null); + createWorkerSettingsForm(createFormUi(), {}, { + id: "cloud", name: "Cloud", type: "cloud", host: "worker.example", port: 8443, + }); + const type = elements.get("worker-type-cloud"); + expect(type.value).toBe("cloud"); + expect(elements.get("port-cloud").value).toBe("8443"); + expect(elements.get("host-cloud").value).toBe("worker.example"); + expect(elements.get("runpod-text-cloud").style.display).toBe("block"); + + type.value = "remote"; + type.dispatchEvent(); + type.value = "cloud"; + type.dispatchEvent(); + expect(elements.get("port-cloud").value).toBe("443"); + }); + it("shows only serverless settings and keeps credentials write-only", () => { const worker = { id: "w1", name: "Runpod", type: "remote", host: "worker.example", port: 8188, diff --git a/web/ui/settingsForm.js b/web/ui/settingsForm.js index bef0f69..ec0149e 100644 --- a/web/ui/settingsForm.js +++ b/web/ui/settingsForm.js @@ -49,71 +49,61 @@ export function createWorkerSettingsForm(ui, extension, worker) { runpodText.textContent = "Deploy Cloud Worker with Runpod"; runpodText.style.cssText = "font-size: 12px; color: #4a90e2; text-decoration: none; margin-top: 4px; display: none; cursor: pointer;"; - const createOnChangeHandler = () => { - return (e) => { - const workerType = e.target.value; - const hostGroup = document.getElementById(`host-group-${worker.id}`); - const hostInput = document.getElementById(`host-${worker.id}`); - const portGroup = document.getElementById(`port-group-${worker.id}`); - const portInput = document.getElementById(`port-${worker.id}`); - const cudaGroup = document.getElementById(`cuda-group-${worker.id}`); - const argsGroup = document.getElementById(`args-group-${worker.id}`); - const runpodTextElem = document.getElementById(`runpod-text-${worker.id}`); - const serverlessGroups = [ - `endpoint-id-group-${worker.id}`, - `api-key-group-${worker.id}`, - `clear-api-key-group-${worker.id}`, - `jobs-per-run-group-${worker.id}`, - `concurrency-group-${worker.id}`, - `timeout-seconds-group-${worker.id}`, - `serverless-test-group-${worker.id}`, - ].map((id) => document.getElementById(id)); + const updateWorkerType = (workerType, applyDefaults = false) => { + // The form is initialized before its card is attached to document. + // Use this form's own elements, not document-wide ID lookups. + const hostInput = hostGroup.input; + const portInput = portGroup.input; + const serverlessGroups = [ + endpointGroup.group, + apiKeyGroup.group, + clearApiKeyGroup, + jobsPerRunGroup.group, + concurrencyGroup.group, + timeoutGroup.group, + testServerlessGroup, + ]; - if (!hostGroup || !portGroup || !cudaGroup || !argsGroup || !runpodTextElem || !hostInput || !portInput || serverlessGroups.some((group) => !group)) { - return; + const hideServerless = () => serverlessGroups.forEach((group) => { group.style.display = "none"; }); + + if (workerType === "local") { + hideServerless(); + hostGroup.group.style.display = "none"; + portGroup.group.style.display = "flex"; + cudaGroup.group.style.display = "flex"; + argsGroup.group.style.display = "flex"; + runpodText.style.display = "none"; + } else if (workerType === "remote") { + hideServerless(); + hostGroup.group.style.display = "flex"; + portGroup.group.style.display = "flex"; + cudaGroup.group.style.display = "none"; + argsGroup.group.style.display = "none"; + runpodText.style.display = "none"; + hostInput.placeholder = "e.g., 192.168.1.100"; + if (applyDefaults && (hostInput.value === "localhost" || hostInput.value === "127.0.0.1")) { + hostInput.value = ""; } - - const hideServerless = () => serverlessGroups.forEach((group) => { group.style.display = "none"; }); - - if (workerType === "local") { - hideServerless(); - hostGroup.style.display = "none"; - portGroup.style.display = "flex"; - cudaGroup.style.display = "flex"; - argsGroup.style.display = "flex"; - runpodTextElem.style.display = "none"; - } else if (workerType === "remote") { - hideServerless(); - hostGroup.style.display = "flex"; - portGroup.style.display = "flex"; - cudaGroup.style.display = "none"; - argsGroup.style.display = "none"; - runpodTextElem.style.display = "none"; - hostInput.placeholder = "e.g., 192.168.1.100"; - if (hostInput.value === "localhost" || hostInput.value === "127.0.0.1") { - hostInput.value = ""; - } - } else if (workerType === "cloud") { - hideServerless(); - hostGroup.style.display = "flex"; - portGroup.style.display = "flex"; - cudaGroup.style.display = "none"; - argsGroup.style.display = "none"; - runpodTextElem.style.display = "block"; - hostInput.placeholder = "e.g., your-cloud-worker.trycloudflare.com"; - portInput.value = "443"; - if (hostInput.value === "localhost" || hostInput.value === "127.0.0.1") { - hostInput.value = ""; - } - } else if (workerType === "serverless") { - hostGroup.style.display = "none"; - portGroup.style.display = "none"; - cudaGroup.style.display = "none"; - argsGroup.style.display = "none"; - runpodTextElem.style.display = "none"; - serverlessGroups.forEach((group) => { group.style.display = "flex"; }); + } else if (workerType === "cloud") { + hideServerless(); + hostGroup.group.style.display = "flex"; + portGroup.group.style.display = "flex"; + cudaGroup.group.style.display = "none"; + argsGroup.group.style.display = "none"; + runpodText.style.display = "block"; + hostInput.placeholder = "e.g., your-cloud-worker.trycloudflare.com"; + if (applyDefaults) portInput.value = "443"; + if (applyDefaults && (hostInput.value === "localhost" || hostInput.value === "127.0.0.1")) { + hostInput.value = ""; } - }; + } else if (workerType === "serverless") { + hostGroup.group.style.display = "none"; + portGroup.group.style.display = "none"; + cudaGroup.group.style.display = "none"; + argsGroup.group.style.display = "none"; + runpodText.style.display = "none"; + serverlessGroups.forEach((group) => { group.style.display = "flex"; }); + } }; typeGroup.appendChild(typeLabel); @@ -217,7 +207,7 @@ export function createWorkerSettingsForm(ui, extension, worker) { const buttonGroup = ui.createButtonGroup([saveBtn, cancelBtn, deleteBtn], " margin-top: 8px;"); form.appendChild(buttonGroup); - typeSelect.onchange = createOnChangeHandler(); + typeSelect.onchange = (event) => updateWorkerType(event.target.value, true); if (worker.type === "serverless") { typeSelect.value = "serverless"; @@ -230,7 +220,7 @@ export function createWorkerSettingsForm(ui, extension, worker) { typeSelect.value = "local"; } - typeSelect.dispatchEvent(new Event('change')); + updateWorkerType(typeSelect.value); return form; }