Runpod support

This commit is contained in:
robertvoy
2025-07-15 08:19:13 +10:00
parent d561bba5aa
commit d4ee572ecc
3 changed files with 128 additions and 34 deletions
+7 -1
View File
@@ -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"] = []
+23 -6
View File
@@ -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;
+98 -27
View File
@@ -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 `<strong>${data.name}</strong><br><small style="color: ${UI_COLORS.MUTED_TEXT};">${data.host}</small>`;
} else if (isRemote) {
return `<strong>${data.name}</strong><br><small style="color: ${UI_COLORS.MUTED_TEXT};">${data.host}:${data.port}</small>`;
} 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);