UNPKG

@seqera/node-red-seqera

Version:

Node-RED nodes for interacting with the Seqera Platform API

289 lines (246 loc) 10.9 kB
<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 () { const node = this; let debounceTimer; const debounceDelay = 1000; // 1 second debounce const hasExistingToken = node.credentials && node.credentials.has_token; 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"); // Check if token is empty or is Node-RED's password placeholder const isTokenPlaceholder = !token || token === "__PWRD__"; // If no token in field or it's a placeholder, check if we have an existing token if (isTokenPlaceholder) { if (hasExistingToken) { // Token exists but is not visible (editing existing config) // Keep workspace dropdown enabled but don't update it return; } else { clearWorkspaceDropdown(); return; } } if (!baseUrl) { clearWorkspaceDropdown(); return; } try { const queryParams = new URLSearchParams({ baseUrl: baseUrl, }); // If we have a real token (not placeholder), send it // Otherwise, send the node ID so server can look up the saved token if (isTokenPlaceholder && hasExistingToken) { queryParams.set("nodeId", node.id); } else { queryParams.set("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(); // Check if token is empty or is Node-RED's password placeholder const isTokenPlaceholder = !token || token === "__PWRD__"; if (!baseUrl) { updateConnectivityStatus("No base URL specified", false); clearWorkspaceDropdown(); return; } // If token is placeholder but no existing token, show error if (isTokenPlaceholder && !hasExistingToken) { updateConnectivityStatus("No API token"); clearWorkspaceDropdown(); return; } showSpinner(); try { const queryParams = new URLSearchParams({ baseUrl: baseUrl, }); // If we have a real token (not placeholder), send it // Otherwise, send the node ID so server can look up the saved token if (isTokenPlaceholder && hasExistingToken) { queryParams.set("nodeId", node.id); } else { queryParams.set("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 { const errorText = await response.text(); console.error("Server error response:", { status: response.status, statusText: response.statusText, body: errorText, }); updateConnectivityStatus(`Server error (${response.status}): ${response.statusText}`, 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>