@seqera/node-red-seqera
Version:
Node-RED nodes for interacting with the Seqera Platform API
248 lines (219 loc) • 9.99 kB
HTML
<script type="text/html" data-template-name="seqera-datalink-list">
<div class="form-row">
<label for="node-input-seqera"><i class="icon-globe"></i> Seqera config</label>
<input type="text" id="node-input-seqera" data-type="seqera-config" />
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Node Name</label>
<input type="text" id="node-input-name" />
</div>
<div class="form-row">
<label for="node-input-dataLinkName"><i class="fa fa-link"></i> Data link name</label>
<input type="text" id="node-input-dataLinkName" />
</div>
<div class="form-row">
<label for="node-input-returnType"><i class="fa fa-filter"></i> Return type</label>
<select id="node-input-returnType">
<option value="files">Files only</option>
<option value="folders">Folders only</option>
<option value="all">Everything</option>
</select>
</div>
<div class="form-row">
<label for="node-input-basePath"><i class="fa fa-folder-open-o"></i> Base path</label>
<input type="text" id="node-input-basePath" />
</div>
<div class="form-row">
<label for="node-input-prefix"><i class="fa fa-filter"></i> Prefix</label>
<input type="text" id="node-input-prefix" />
</div>
<div class="form-row">
<label for="node-input-pattern"><i class="fa fa-file-code-o"></i> Pattern (regex)</label>
<input type="text" id="node-input-pattern" />
</div>
<div class="form-row">
<label for="node-input-maxResults"><i class="fa fa-sort-numeric-asc"></i> Max results</label>
<input type="text" id="node-input-maxResults" />
</div>
<div class="form-row">
<label for="node-input-depth"><i class="fa fa-level-down"></i> Depth</label>
<input type="text" id="node-input-depth" />
</div>
<div class="form-row">
<label for="node-input-workspaceId"><i class="icon-tasks"></i> Workspace ID</label>
<input type="text" id="node-input-workspaceId" />
</div>
</script>
<!-- prettier-ignore -->
<script type="text/markdown" data-help-name="seqera-datalink-list">
Lists files from a Seqera Platform Data Explorer link.
### Inputs
: dataLinkName (string) : The name of the data explorer link.
: basePath (string) : Path within the data link to start browsing. Leave blank for the root.
: prefix (string) : Optional prefix filter for results (applies to folders and files)
: pattern (string) : Optional regex pattern filter for results (applies to files only)
: returnType (string) : Select whether to return files, folders or everything.
: maxResults (number) : Maximum number of results to return (default 100).
: workspaceId (string) : Override the workspace ID from the config node.
All inputs support msg.*, flow.*, global.*, env, or JSONata expressions via the **typedInput**.
### Outputs
1. Standard output
: payload (array) : Array of file objects aggregated from the API.
: files (array) : Array of file names.
### Details
This node queries the Seqera Platform Data Explorer **list** API to find a data-link ID,
then uses the **browse** API to fetch files and folders.
It automatically handles pagination and listing nested files.
The node uses the **Seqera config** node for the base URL, workspace ID, and API token.
Any of these can be overridden using the corresponding input properties.
### References
- [Seqera Platform API docs](https://docs.seqera.io/platform/latest/api) - Data Explorer endpoints
</script>
<script type="text/javascript">
RED.nodes.registerType("seqera-datalink-list", {
category: "seqera",
color: "#A9A1C6",
inputs: 1,
outputs: 1,
icon: "icons/data-explorer.svg",
align: "left",
paletteLabel: "List files",
label: function () {
return this.name || "List files";
},
outputLabels: ["files"],
defaults: {
name: { value: "" },
seqera: { value: "", type: "seqera-config" },
dataLinkName: {
value: "",
required: true,
},
dataLinkNameType: { value: "str" },
basePath: { value: "" },
basePathType: { value: "str" },
prefix: { value: "" },
prefixType: { value: "str" },
pattern: { value: "" },
patternType: { value: "str" },
maxResults: { value: "100" },
maxResultsType: { value: "num" },
depth: { value: "0" },
depthType: { value: "num" },
workspaceId: { value: "" },
workspaceIdType: { value: "str" },
returnType: { value: "files" },
},
oneditprepare: function () {
function ti(id, val, type, def = "str") {
const types = ["str", "num", "msg", "flow", "global", "env", "jsonata"];
if (def === "num") types.splice(types.indexOf("str"), 1);
$(id).typedInput({ default: def, types });
$(id).typedInput("value", val);
$(id).typedInput("type", type);
}
ti("#node-input-dataLinkName", this.dataLinkName || "", this.dataLinkNameType || "str");
ti("#node-input-basePath", this.basePath || "", this.basePathType || "str");
ti("#node-input-prefix", this.prefix || "", this.prefixType || "str");
ti("#node-input-pattern", this.pattern || "", this.patternType || "str");
ti("#node-input-maxResults", this.maxResults || "100", this.maxResultsType || "num", "num");
ti("#node-input-depth", this.depth || "0", this.depthType || "num", "num");
ti("#node-input-workspaceId", this.workspaceId || "", this.workspaceIdType || "str");
// Set dropdown value for returnType
$("#node-input-returnType").val(this.returnType || "files");
// Add auto-complete for datalink name when type is "str"
const setupAutoComplete = () => {
const dataLinkNameInput = $("#node-input-dataLinkName");
const currentType = dataLinkNameInput.typedInput("type");
// Ensure our custom CSS is added only once
if (!document.getElementById("seqera-autocomplete-style")) {
const style = document.createElement("style");
style.id = "seqera-autocomplete-style";
style.innerHTML = `.red-ui-autoComplete li a { display:block; padding:4px 8px; width:100%; box-sizing:border-box; }`;
document.head.appendChild(style);
}
// Get the actual visible input element from the typedInput widget
const actualInput = dataLinkNameInput.parent().find(".red-ui-typedInput-input");
// Remove existing auto-complete if any
if (actualInput.data("autoCompleteEnabled")) {
actualInput.autoComplete("destroy");
actualInput.data("autoCompleteEnabled", false);
}
if (currentType === "str" && actualInput.length > 0) {
// Add a small delay to ensure typedInput is fully initialized
setTimeout(() => {
actualInput.autoComplete({
search: function (value, done) {
// Only search if we have some input
if (!value || value.length < 1) {
done([]);
return;
}
// Make API call to get datalink names
const nodeId =
(RED.editor.edit_node && RED.editor.edit_node.id) ||
(RED.editor.edit_stack && RED.editor.edit_stack.length > 0 && RED.editor.edit_stack[0].id) ||
"temp-" + Date.now();
const seqeraConfigId = $("#node-input-seqera").val();
if (!seqeraConfigId) {
done([]);
return;
}
const params = new URLSearchParams({
search: value,
seqeraConfig: seqeraConfigId,
});
// Add workspace ID override if configured
const workspaceIdOverride = $("#node-input-workspaceId").typedInput("value");
const workspaceIdType = $("#node-input-workspaceId").typedInput("type");
if (workspaceIdType === "str" && workspaceIdOverride && workspaceIdOverride.trim()) {
params.append("workspaceId", workspaceIdOverride);
}
$.ajax({
url: `admin/seqera/datalinks/${nodeId}?${params.toString()}`,
method: "GET",
success: function (data) {
// data is already in the correct format: [{value, label}]
done(data || []);
},
error: function () {
done([]);
},
});
},
});
actualInput.data("autoCompleteEnabled", true);
}, 100);
}
};
// Set up auto-complete initially
setupAutoComplete();
// Re-setup auto-complete when type changes
$("#node-input-dataLinkName").on("change", function (event, type) {
if (type) {
// Add delay to avoid conflicts with ongoing autocomplete operations
setTimeout(setupAutoComplete, 200);
}
});
// Re-setup auto-complete when Seqera config changes
$("#node-input-seqera").on("change", function () {
setupAutoComplete();
});
},
oneditsave: function () {
function save(id, prop, propType) {
this[prop] = $(id).typedInput("value");
this[propType] = $(id).typedInput("type");
}
save.call(this, "#node-input-dataLinkName", "dataLinkName", "dataLinkNameType");
save.call(this, "#node-input-basePath", "basePath", "basePathType");
save.call(this, "#node-input-prefix", "prefix", "prefixType");
save.call(this, "#node-input-pattern", "pattern", "patternType");
save.call(this, "#node-input-maxResults", "maxResults", "maxResultsType");
save.call(this, "#node-input-depth", "depth", "depthType");
save.call(this, "#node-input-workspaceId", "workspaceId", "workspaceIdType");
// Save returnType dropdown
this.returnType = $("#node-input-returnType").val();
},
});
</script>