@seqera/node-red-seqera
Version:
Node-RED nodes for interacting with the Seqera Platform API
306 lines (269 loc) • 12.5 kB
HTML
<script type="text/html" data-template-name="seqera-datalink-poll">
<!-- Seqera config and node name -->
<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>
<!-- Data link parameters (same as list node) -->
<div class="form-row">
<label for="node-input-dataLinkName"><i class="fa fa-link"></i> Data link</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>
<!-- Polling specific -->
<div class="form-row">
<label for="node-input-pollFrequency"><i class="fa fa-clock-o"></i> Poll frequency</label>
<input type="text" id="node-input-pollFrequency" style="text-align:end; width:50px !important" />
<select id="node-input-pollUnits" style="width:120px !important">
<option value="seconds">Seconds</option>
<option value="minutes">Minutes</option>
<option value="hours">Hours</option>
<option value="days">Days</option>
</select>
</div>
<div class="form-row">
<label for="node-input-outputAllPolls">
<i class="fa fa-refresh" aria-hidden="true"></i>
Every poll
</label>
<label for="node-input-outputAllPolls" style="width:70%">
<input
type="checkbox"
id="node-input-outputAllPolls"
style="display:inline-block; width:22px; vertical-align:top;"
autocomplete="off"
/>
Show output port
</label>
</div>
<!-- More specifics -->
<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-poll">
Polls a Seqera Platform Data Explorer link at a fixed interval.
### Inputs
: dataLinkName (string) : The name of the data explorer link.
: returnType (string) : Select whether to return files, folders or everything.
: pollFrequency (string) : Poll frequency (default `15 minutes`). Can be configured in seconds, minutes, hours, or days.
: outputAllPolls (boolean) : Show the "every poll" output port. Emits an output file listing on every poll event, irrespective of changes (disabled by default).
: 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)
: maxResults (number) : Maximum number of results to return (default 100).
: depth (number) : Folder recursion depth (default 0).
: workspaceId (string) : Override the workspace ID from the config node.
All inputs support `msg`, `flow`, `global`, `env`, or JSONata expressions via the **typedInput**.
### Outputs
The node has two or three outputs depending on configuration:
1. **All results** (optional, disabled by default) - Emitted every poll with the full list of files.
2. **New results** - Emitted only when new objects are detected since the previous poll.
3. **Deleted results** - Emitted only when objects are deleted since the previous poll.
All outputs have the following properties:
: payload (array) : File information aggregated from the API (array of objects).
: files (array) : File names (array of strings).
The "All results" output also includes:
: payload.nextPoll (string) : ISO timestamp of the next scheduled poll.
: payload.pollIntervalSeconds (number) : Poll interval duration in seconds.
</script>
<script type="text/javascript">
RED.nodes.registerType("seqera-datalink-poll", {
category: "seqera",
color: "#A9A1C6",
inputs: 0,
outputs: 2,
icon: "icons/data-explorer.svg",
align: "left",
paletteLabel: "Poll files",
label: function () {
return this.name || "Poll files";
},
outputLabels: function (index) {
if (this.outputAllPolls) {
// 3 outputs: All, New, Deleted
return ["All objects", "New objects", "Deleted objects"][index];
}
// 2 outputs: New, Deleted
return ["New objects", "Deleted objects"][index];
},
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" },
pollFrequency: { value: "15" },
pollUnits: { value: "minutes" },
returnType: { value: "files" },
outputAllPolls: { value: false },
},
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");
// Poll frequency – simple number input with units
$("#node-input-pollFrequency").val(this.pollFrequency || "15");
$("#node-input-pollUnits").val(this.pollUnits || "minutes");
// Output all polls checkbox
$("#node-input-outputAllPolls").prop("checked", this.outputAllPolls || false);
$("#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 poll frequency and units
this.pollFrequency = $("#node-input-pollFrequency").val();
this.pollUnits = $("#node-input-pollUnits").val();
// Save output all polls checkbox and update outputs count
this.outputAllPolls = $("#node-input-outputAllPolls").prop("checked");
this.outputs = this.outputAllPolls ? 3 : 2;
this.returnType = $("#node-input-returnType").val();
},
});
</script>