-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdatalink-poll.html
More file actions
314 lines (276 loc) · 12.6 KB
/
datalink-poll.html
File metadata and controls
314 lines (276 loc) · 12.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
<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;
}
// Get config node from editor (works for undeployed nodes)
const configNode = RED.nodes.node(seqeraConfigId);
const params = new URLSearchParams({
search: value,
seqeraConfig: seqeraConfigId,
});
// Pass config values for undeployed nodes
if (configNode) {
if (configNode.baseUrl) params.append("baseUrl", configNode.baseUrl);
if (configNode.workspaceId) params.append("workspaceId", configNode.workspaceId);
}
// Add workspace ID override if configured (takes precedence)
const workspaceIdOverride = $("#node-input-workspaceId").typedInput("value");
const workspaceIdType = $("#node-input-workspaceId").typedInput("type");
if (workspaceIdType === "str" && workspaceIdOverride && workspaceIdOverride.trim()) {
params.set("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>