@seqera/node-red-seqera
Version:
Node-RED nodes for interacting with the Seqera Platform API
247 lines (210 loc) • 9.16 kB
HTML
<script type="text/html" data-template-name="seqera-config">
<div class="form-row">
<label for="node-config-input-baseUrl">Base URL</label>
<input type="text" id="node-config-input-baseUrl" placeholder="https://api.cloud.seqera.io" />
</div>
<div class="form-row">
<label for="node-config-input-token">
<a href="https://cloud.seqera.io/tokens" target="_blank" style="color: var(--red-ui-text-color-link)">
API Token
<i class="fa fa-external-link"></i>
</a>
</label>
<input type="password" id="node-config-input-token" placeholder="Bearer token" autocomplete="off" />
</div>
<div class="form-row">
<label for="node-config-input-workspaceId">Workspace</label>
<select id="node-config-input-workspaceId" disabled>
<option value="">Enter API key</option>
</select>
</div>
<hr />
<h4>Connectivity check</h4>
<div id="connectivity-status">
<p id="connectivity-message">No API token</p>
<div id="connectivity-spinner" style="display: none;">
<i class="fa fa-spinner fa-spin"></i> Checking connection...
</div>
</div>
</script>
<script type="text/markdown" data-help-name="seqera-config">
Global Seqera Platform configuration node to store connection details.
### Details
Configure the following options:
- **Base URL**: The base URL for the Seqera API (defaults to `https://api.cloud.seqera.io`)
- **API Token**: Your personal access token for authenticating with Seqera Platform. Create a Seqera access token via [_Your Tokens_](https://cloud.seqera.io/tokens) in the user menu.
- **Workspace**: Default Seqera workspace to use for all nodes (optional)
This configuration node is used by all other Seqera nodes to authenticate with the Seqera Platform API.
### References
- [Seqera Platform API docs](https://docs.seqera.io/platform/latest/api) - information on how to get an API token
</script>
<script type="text/javascript">
RED.nodes.registerType("seqera-config", {
category: "config",
defaults: {
baseUrl: { value: "https://api.cloud.seqera.io", required: true },
workspaceId: {
value: null,
validate: function (v) {
return v === null || v === "" || (!isNaN(v) && parseInt(v) > 0);
},
},
},
credentials: {
token: { type: "password" },
},
label: function () {
const wsPart = this.workspaceId ? `, WS ${this.workspaceId}` : "";
return this.baseUrl ? `Seqera (${this.baseUrl}${wsPart})` : "Seqera config";
},
oneditprepare: function () {
let debounceTimer;
const debounceDelay = 1000; // 1 second debounce
function updateConnectivityStatus(message, isValid = null, userInfo = null) {
const statusElement = document.getElementById("connectivity-message");
const spinnerElement = document.getElementById("connectivity-spinner");
spinnerElement.style.display = "none";
if (isValid === null) {
// Neutral state
statusElement.innerHTML = message;
statusElement.style.color = "";
} else if (isValid) {
// Valid connection
const displayMessage = userInfo
? `<span style="color: green;">✓ Connected as <code>${userInfo.userName}</code> (${userInfo.email})</span>`
: `<span style="color: green;">✓ ${message}</span>`;
statusElement.innerHTML = displayMessage;
} else {
// Invalid connection
statusElement.innerHTML = `<span style="color: red;">✗ ${message}</span>`;
}
}
function showSpinner() {
document.getElementById("connectivity-spinner").style.display = "block";
document.getElementById("connectivity-message").innerHTML = "";
}
function clearWorkspaceDropdown(message = "Enter API key") {
const workspaceSelect = document.getElementById("node-config-input-workspaceId");
workspaceSelect.innerHTML = `<option value="">${message}</option>`;
workspaceSelect.disabled = true;
}
async function populateWorkspaceDropdown() {
const baseUrl = document.getElementById("node-config-input-baseUrl").value.trim();
const token = document.getElementById("node-config-input-token").value.trim();
const workspaceSelect = document.getElementById("node-config-input-workspaceId");
if (!token || !baseUrl) {
clearWorkspaceDropdown();
return;
}
try {
const queryParams = new URLSearchParams({
baseUrl: baseUrl,
token: token,
});
const response = await fetch(`/seqera-config/workspaces?${queryParams}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (response.ok) {
const data = await response.json();
if (data.success && data.organizations) {
// Store current selection to preserve it if possible
const currentValue = workspaceSelect.value;
// Clear and rebuild dropdown
workspaceSelect.innerHTML = '<option value="">[ leave unset ]</option>';
// Add organizations and workspaces
data.organizations.forEach((org) => {
if (org.workspaces && org.workspaces.length > 0) {
const optgroup = document.createElement("optgroup");
optgroup.label = org.orgName;
org.workspaces.forEach((workspace) => {
const option = document.createElement("option");
option.value = workspace.id;
option.textContent = workspace.name;
optgroup.appendChild(option);
});
workspaceSelect.appendChild(optgroup);
}
});
// Restore previous selection if it still exists
if (currentValue && Array.from(workspaceSelect.options).some((opt) => opt.value === currentValue)) {
workspaceSelect.value = currentValue;
}
workspaceSelect.disabled = false;
} else {
clearWorkspaceDropdown("Failed to load workspaces");
}
} else {
clearWorkspaceDropdown("Failed to load workspaces");
}
} catch (error) {
console.error("Workspace fetch error:", error);
clearWorkspaceDropdown("Failed to load workspaces");
}
}
async function checkConnectivity() {
const baseUrl = document.getElementById("node-config-input-baseUrl").value.trim();
const token = document.getElementById("node-config-input-token").value.trim();
if (!token) {
updateConnectivityStatus("No API token");
clearWorkspaceDropdown();
return;
}
if (!baseUrl) {
updateConnectivityStatus("No base URL specified", false);
clearWorkspaceDropdown();
return;
}
showSpinner();
try {
const queryParams = new URLSearchParams({
baseUrl: baseUrl,
token: token,
});
const response = await fetch(`/seqera-config/connectivity-check?${queryParams}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (response.ok) {
const data = await response.json();
if (data.success) {
updateConnectivityStatus("API token is valid", true, data.user);
// Populate workspace dropdown when connectivity check succeeds
await populateWorkspaceDropdown();
} else {
if (data.isEmptyToken) {
updateConnectivityStatus("No API token");
clearWorkspaceDropdown();
} else {
updateConnectivityStatus(data.message, false);
clearWorkspaceDropdown("Invalid API key");
}
}
} else {
updateConnectivityStatus("Server error occurred", false);
clearWorkspaceDropdown("Server error");
}
} catch (error) {
console.error("Connectivity check error:", error);
updateConnectivityStatus("Connection failed - unable to reach server", false);
clearWorkspaceDropdown("Connection failed");
}
}
function debouncedCheckConnectivity() {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(checkConnectivity, debounceDelay);
}
// Initialize workspace dropdown
clearWorkspaceDropdown();
// Check connectivity when the dialog opens
setTimeout(checkConnectivity, 100);
// Add event listeners for token and base URL changes
document.getElementById("node-config-input-token").addEventListener("input", debouncedCheckConnectivity);
document.getElementById("node-config-input-baseUrl").addEventListener("input", debouncedCheckConnectivity);
},
});
</script>