diff --git a/distributed.py b/distributed.py
index e9a7c0f..1a1a14e 100644
--- a/distributed.py
+++ b/distributed.py
@@ -297,6 +297,11 @@ async def update_worker_endpoint(request):
worker.pop("extra_args", None)
else:
worker["extra_args"] = data["extra_args"]
+
+ # Handle type field
+ if "type" in data:
+ worker["type"] = data["type"]
+
worker_found = True
break
@@ -310,7 +315,8 @@ async def update_worker_endpoint(request):
"port": data["port"],
"cuda_device": data["cuda_device"],
"enabled": data.get("enabled", False),
- "extra_args": data.get("extra_args", "")
+ "extra_args": data.get("extra_args", ""),
+ "type": data.get("type", "local")
}
if "workers" not in config:
config["workers"] = []
diff --git a/web/main.js b/web/main.js
index ecd7efe..efc1a63 100644
--- a/web/main.js
+++ b/web/main.js
@@ -266,13 +266,17 @@ class DistributedExtension {
getWorkerUrl(worker, endpoint = '') {
const host = worker.host || window.location.hostname;
- // Simple rule: port 443 = HTTPS, anything else = HTTP
- const useHttps = worker.port === 443;
+ // Cloud workers always use HTTPS
+ const isCloud = worker.type === 'cloud';
+ // Simple rule: port 443 = HTTPS, cloud workers = HTTPS, anything else = HTTP
+ const useHttps = isCloud || worker.port === 443;
const protocol = useHttps ? 'https' : 'http';
+ // Cloud workers use default HTTPS port (443)
+ const effectivePort = isCloud ? 443 : worker.port;
// Don't add port for standard HTTPS/HTTP ports
const defaultPort = useHttps ? 443 : 80;
- const port = worker.port === defaultPort ? '' : `:${worker.port}`;
+ const port = effectivePort === defaultPort ? '' : `:${effectivePort}`;
return `${protocol}://${host}${port}${endpoint}`;
}
@@ -695,10 +699,19 @@ class DistributedExtension {
}
isRemoteWorker(worker) {
+ // Check if explicitly marked as cloud worker
+ if (worker.type === "cloud") {
+ return true;
+ }
+ // Otherwise check by host (backward compatibility)
const host = worker.host || window.location.hostname;
return host !== "localhost" && host !== "127.0.0.1" && host !== window.location.hostname;
}
+ isCloudWorker(worker) {
+ return worker.type === "cloud";
+ }
+
getMasterUrl() {
// Always use the detected/configured master IP for consistency
if (this.config?.master?.host) {
@@ -873,7 +886,9 @@ class DistributedExtension {
// Get form values
const name = document.getElementById(`name-${workerId}`).value;
- const isRemote = document.getElementById(`remote-${workerId}`).checked;
+ const workerType = document.getElementById(`worker-type-${workerId}`).value;
+ const isRemote = workerType === 'remote' || workerType === 'cloud';
+ const isCloud = workerType === 'cloud';
const host = isRemote ? document.getElementById(`host-${workerId}`).value : window.location.hostname;
const port = parseInt(document.getElementById(`port-${workerId}`).value);
const cudaDevice = isRemote ? undefined : parseInt(document.getElementById(`cuda-${workerId}`).value);
@@ -890,7 +905,7 @@ class DistributedExtension {
return;
}
- if (isRemote && !host.trim()) {
+ if ((workerType === 'remote' || workerType === 'cloud') && !host.trim()) {
app.extensionManager.toast.add({
severity: "error",
summary: "Validation Error",
@@ -900,7 +915,7 @@ class DistributedExtension {
return;
}
- if (isNaN(port) || port < 1 || port > 65535) {
+ if (!isCloud && (isNaN(port) || port < 1 || port > 65535)) {
app.extensionManager.toast.add({
severity: "error",
summary: "Validation Error",
@@ -963,6 +978,7 @@ class DistributedExtension {
try {
await this.api.updateWorker(workerId, {
name: name.trim(),
+ type: workerType,
host: isRemote ? host.trim() : null,
port: port,
cuda_device: isRemote ? null : cudaDevice,
@@ -971,6 +987,7 @@ class DistributedExtension {
// Update local config
worker.name = name.trim();
+ worker.type = workerType;
if (isRemote) {
worker.host = host.trim();
delete worker.cuda_device;
diff --git a/web/ui.js b/web/ui.js
index de41240..2421cdf 100644
--- a/web/ui.js
+++ b/web/ui.js
@@ -49,7 +49,12 @@ const cardConfigs = {
},
infoText: (data, extension) => {
const isRemote = extension.isRemoteWorker(data);
- if (isRemote) {
+ const isCloud = data.type === 'cloud';
+
+ if (isCloud) {
+ // For cloud workers, don't show port (it's always 443)
+ return `${data.name}
${data.host}`;
+ } else if (isRemote) {
return `${data.name}
${data.host}:${data.port}`;
} else {
const cudaInfo = data.cuda_device !== undefined ? `CUDA ${data.cuda_device} • ` : '';
@@ -525,66 +530,130 @@ export class DistributedUI {
const nameGroup = this.createFormGroup("Name:", worker.name, `name-${worker.id}`);
form.appendChild(nameGroup.group);
- // Remote checkbox
- const remoteGroup = document.createElement("div");
- remoteGroup.style.cssText = "display: flex; align-items: center; gap: 8px; margin: 5px 0;";
+ // Worker type dropdown
+ const typeGroup = document.createElement("div");
+ typeGroup.style.cssText = "display: flex; flex-direction: column; gap: 4px; margin: 5px 0;";
- const remoteCheckbox = document.createElement("input");
- remoteCheckbox.type = "checkbox";
- remoteCheckbox.id = `remote-${worker.id}`;
- remoteCheckbox.checked = extension.isRemoteWorker(worker);
- remoteCheckbox.onchange = (e) => {
- const isRemote = e.target.checked;
+ const typeLabel = document.createElement("label");
+ typeLabel.htmlFor = `worker-type-${worker.id}`;
+ typeLabel.textContent = "Worker Type:";
+ typeLabel.style.cssText = "font-size: 12px; color: #ccc;";
+
+ const typeSelect = document.createElement("select");
+ typeSelect.id = `worker-type-${worker.id}`;
+ typeSelect.style.cssText = "padding: 4px 8px; background: #333; color: #fff; border: 1px solid #555; border-radius: 4px; font-size: 12px;";
+
+ // Create options
+ const localOption = document.createElement("option");
+ localOption.value = "local";
+ localOption.textContent = "Local";
+
+ const remoteOption = document.createElement("option");
+ remoteOption.value = "remote";
+ remoteOption.textContent = "Remote";
+
+ const cloudOption = document.createElement("option");
+ cloudOption.value = "cloud";
+ cloudOption.textContent = "Cloud";
+
+ typeSelect.appendChild(localOption);
+ typeSelect.appendChild(remoteOption);
+ typeSelect.appendChild(cloudOption);
+
+ // Create powered by Runpod text (initially hidden)
+ const runpodText = document.createElement("a");
+ runpodText.id = `runpod-text-${worker.id}`;
+ runpodText.href = "https://www.runpod.io";
+ runpodText.target = "_blank";
+ 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;";
+
+ typeSelect.onchange = (e) => {
+ const workerType = e.target.value;
// Show/hide relevant fields
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}`);
- if (isRemote) {
+ if (workerType === "local") {
+ hostGroup.style.display = "none";
+ portGroup.style.display = "flex";
+ cudaGroup.style.display = "flex";
+ argsGroup.style.display = "flex";
+ runpodTextElem.style.display = "none";
+ } else if (workerType === "remote") {
hostGroup.style.display = "flex";
+ portGroup.style.display = "flex";
cudaGroup.style.display = "none";
argsGroup.style.display = "none";
+ runpodTextElem.style.display = "none";
+ // Update placeholder for remote workers
+ hostInput.placeholder = "e.g., 192.168.1.100";
// If switching to remote and host is localhost, clear it
- const hostInput = document.getElementById(`host-${worker.id}`);
if (hostInput.value === "localhost" || hostInput.value === "127.0.0.1") {
hostInput.value = "";
}
- } else {
- hostGroup.style.display = "none";
- cudaGroup.style.display = "flex";
- argsGroup.style.display = "flex";
+ } else if (workerType === "cloud") {
+ hostGroup.style.display = "flex";
+ portGroup.style.display = "flex"; // Keep port visible for cloud workers
+ cudaGroup.style.display = "none";
+ argsGroup.style.display = "none";
+ runpodTextElem.style.display = "block";
+ // Update placeholder for cloud workers
+ hostInput.placeholder = "e.g., your-cloud-worker.trycloudflare.com";
+ // Set port to 443 for cloud workers
+ portInput.value = "443";
+ // If switching to cloud and host is localhost, clear it
+ if (hostInput.value === "localhost" || hostInput.value === "127.0.0.1") {
+ hostInput.value = "";
+ }
}
};
- const remoteLabel = document.createElement("label");
- remoteLabel.htmlFor = `remote-${worker.id}`;
- remoteLabel.textContent = "Remote Worker";
- remoteLabel.style.cssText = "font-size: 12px; color: #ccc; cursor: pointer;";
+ typeGroup.appendChild(typeLabel);
+ typeGroup.appendChild(typeSelect);
+ typeGroup.appendChild(runpodText);
+ form.appendChild(typeGroup);
- remoteGroup.appendChild(remoteCheckbox);
- remoteGroup.appendChild(remoteLabel);
- form.appendChild(remoteGroup);
+ // Set initial value based on worker type
+ if (worker.type === "cloud") {
+ typeSelect.value = "cloud";
+ // Show Runpod text immediately for cloud workers
+ runpodText.style.display = "block";
+ } else if (extension.isRemoteWorker(worker)) {
+ typeSelect.value = "remote";
+ } else {
+ typeSelect.value = "local";
+ }
+
+ // Trigger initial state
+ typeSelect.dispatchEvent(new Event('change'));
// Host field (only for remote workers)
const hostGroup = this.createFormGroup("Host:", worker.host || "", `host-${worker.id}`, "text", "e.g., 192.168.1.100");
hostGroup.group.id = `host-group-${worker.id}`;
- hostGroup.group.style.display = extension.isRemoteWorker(worker) ? "flex" : "none";
+ hostGroup.group.style.display = (extension.isRemoteWorker(worker) || worker.type === "cloud") ? "flex" : "none";
form.appendChild(hostGroup.group);
// Port field
const portGroup = this.createFormGroup("Port:", worker.port, `port-${worker.id}`, "number");
+ portGroup.group.id = `port-group-${worker.id}`;
form.appendChild(portGroup.group);
// CUDA Device field (only for local workers)
const cudaGroup = this.createFormGroup("CUDA Device:", worker.cuda_device || 0, `cuda-${worker.id}`, "number");
cudaGroup.group.id = `cuda-group-${worker.id}`;
- cudaGroup.group.style.display = extension.isRemoteWorker(worker) ? "none" : "flex";
+ cudaGroup.group.style.display = (extension.isRemoteWorker(worker) || worker.type === "cloud") ? "none" : "flex";
form.appendChild(cudaGroup.group);
// Extra Args field (only for local workers)
const argsGroup = this.createFormGroup("Extra Args:", worker.extra_args || "", `args-${worker.id}`);
argsGroup.group.id = `args-group-${worker.id}`;
- argsGroup.group.style.display = extension.isRemoteWorker(worker) ? "none" : "flex";
+ argsGroup.group.style.display = (extension.isRemoteWorker(worker) || worker.type === "cloud") ? "none" : "flex";
form.appendChild(argsGroup.group);
// Buttons
@@ -728,7 +797,9 @@ export class DistributedUI {
if (config.dynamic && data) {
if (isRemote) {
- const remoteInfo = this.createButton("Remote worker", null, BUTTON_STYLES.info);
+ const isCloud = data.type === 'cloud';
+ const workerTypeText = isCloud ? "Cloud worker" : "Remote worker";
+ const remoteInfo = this.createButton(workerTypeText, null, BUTTON_STYLES.info);
remoteInfo.style.cssText = BUTTON_STYLES.base + BUTTON_STYLES.workerControl + BUTTON_STYLES.info + " color: #999; cursor: default;";
remoteInfo.disabled = true;
controlsWrapper.appendChild(remoteInfo);