Skip to content

Commit 16ccf08

Browse files
committed
New Feature: Quiet Trigger Modifier: Optionally run scheduled jobs invisible from the UI, and/or ephemeral so they auto-self-delete upon completion.
1 parent 8d14b2f commit 16ccf08

13 files changed

Lines changed: 164 additions & 26 deletions

File tree

docs/data.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -890,6 +890,14 @@ If the [Job.state](#job-state) is `queued`, this property will indicate what pos
890890

891891
While the job is running, i.e. in the data passed to the Event Plugin, the job object will contain a `serverData` property. This will be a copy of the [Server.userData](#server-userdata) object, if applicable for the current server.
892892

893+
### Job.invisible
894+
895+
If set to `true`, the job is running invisibly to the UI. See [Quiet Trigger](triggers.md#quiet) for details.
896+
897+
### Job.ephemeral
898+
899+
If set to `true`, the job will self-delete upon completion. See [Quiet Trigger](triggers.md#quiet) for details.
900+
893901
## Monitor
894902

895903
A monitor keeps track on a specific numeric server metric. These are graphed in the UI so you can see trends over time, and you can also point alerts at them. Here is an example monitor in JSON format:

docs/triggers.md

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ Example:
266266

267267
### Range
268268

269-
Restrict scheduling to a date/time window. Prevents launches before `start` and after `end` (unless time is inside another range). Endpoints are inclusive.
269+
Restrict scheduling to a date/time window. Prevents launches before `start` and after `end` (unless time is inside another range). Endpoints are inclusive. As a "modifier" this option only takes effect when jobs are launched from a scheduler trigger (i.e. not launched manually via UI or API).
270270

271271
Parameters:
272272

@@ -293,7 +293,7 @@ Example: Only run between March 1 and May 31 (inclusive):
293293

294294
### Blackout
295295

296-
Prevent any automatic launches during a specific date/time window. Endpoints are inclusive.
296+
Prevent any automatic launches during a specific date/time window. Endpoints are inclusive. As a "modifier" this option only takes effect when jobs are launched from a scheduler trigger (i.e. not launched manually via UI or API).
297297

298298
Parameters:
299299

@@ -320,7 +320,7 @@ Example:
320320

321321
### Delay
322322

323-
Add a starting delay to all scheduler-launched jobs for the event. Does not affect manual/API runs. Mutually exclusive with `interval` and `precision`.
323+
Add a starting delay to all scheduler-launched jobs for the event. Does not affect manual/API runs. Mutually exclusive with `interval` and `precision`. As a "modifier" this option only takes effect when jobs are launched from a scheduler trigger (i.e. not launched manually via UI or API).
324324

325325
Parameters:
326326

@@ -340,7 +340,7 @@ Example (delay all launches by 2 minutes):
340340

341341
### Precision
342342

343-
Launch within the scheduled minute at specific second offsets. Augments other automatic triggers to achieve sub-minute starts. Mutually exclusive with `interval` and `delay`.
343+
Launch within the scheduled minute at specific second offsets. Augments other automatic triggers to achieve sub-minute starts. Mutually exclusive with `interval` and `delay`. As a "modifier" this option only takes effect when jobs are launched from a scheduler trigger (i.e. not launched manually via UI or API).
344344

345345
Parameters:
346346

@@ -363,6 +363,23 @@ Example (launch at :05, :20, :35, :50 within each matched minute):
363363
}
364364
```
365365

366+
### Quiet
367+
368+
The "Quiet" modifier allows you to configure jobs to run silently (i.e. completely invisible to the UI), and also optionally ephemeral (so they self-delete upon completion). As a "modifier" this option only takes effect when jobs are launched from a scheduler trigger (i.e. not launched manually via UI or API). Each quiet mode can be enabled or disabled separately:
369+
370+
| Name | Type | Required | Description |
371+
|------|------|----------|-------------|
372+
| `invisible` | Boolean | Yes | Upcoming, queued and running jobs are completely hidden from the UI. |
373+
| `ephemeral` | Boolean | Yes | Auto-delete jobs upon completion (no permanent storage). |
374+
375+
A few notes about behaviors:
376+
377+
- Invisible mode affects running jobs, queued jobs, as well as upcoming jobs.
378+
- You can still access running invisible jobs via the API (i.e. [get_job](api.md#get_job), [get_jobs](api.md#get_job)).
379+
- As soon as jobs complete, they will be visible in the UI (unless `ephemeral` is also set).
380+
- Ephemeral mode will automatically disable itself if the job produces output files.
381+
- Both invisible and ephemeral modes are passed down to child sub-jobs if set on a workflow.
382+
366383
### Plugin
367384

368385
Use a custom [Trigger Plugin](plugins.md#trigger-plugins) to decide whether to launch a job or not. The plugin runs with configured parameters and returns a launch/no-launch decision per each scheduled run. This is a "modifier" so it needs to be used in conjunction with a standard schedule trigger.

htdocs/js/pages/Base.class.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1253,6 +1253,7 @@ Page.Base = class Base extends Page {
12531253
if (job.icon) icon = '<i class="mdi mdi-' + job.icon + '"></i>';
12541254
else if (job.type == 'workflow') icon = '<i class="mdi mdi-clipboard-play-outline"></i>';
12551255
else if (job.workflow) icon = '<i class="mdi mdi-clipboard-clock-outline"></i>';
1256+
else if (job.invisible) icon = '<i class="mdi mdi-selection-ellipse"></i>';
12561257

12571258
var html = '<span class="nowrap">';
12581259
if (link) {
@@ -1928,6 +1929,10 @@ Page.Base = class Base extends Page {
19281929
var schedules = triggers.filter( function(trigger) { return trigger.type.match(/^(schedule|single|interval)$/); } );
19291930
if (!schedules.length) return false;
19301931

1932+
// quiet (invisible) mode
1933+
var quiet = find_object( triggers, { type: 'quiet' } );
1934+
if (quiet && quiet.invisible) return false;
1935+
19311936
// setup all unique timezones (intl formatters)
19321937
schedules.forEach( function(trigger) {
19331938
if (trigger.type != 'schedule') return;

htdocs/js/pages/Events.class.js

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ Page.Events = class Events extends Page.PageUtils {
171171
{ id: 'blackout', title: "Blackout", icon: 'circle' },
172172
{ id: 'delay', title: "Delay", icon: 'chat-sleep-outline' },
173173
{ id: 'precision', title: "Precision", icon: 'progress-clock' },
174+
{ id: 'quiet', title: "Quiet", icon: 'volume-mute' },
174175
{ id: 'plugin', title: "Plugin", icon: 'power-plug' }
175176
].concat(
176177
this.buildOptGroup( scheduler_plugins, "Trigger Plugins:", 'power-plug-outline', 'p_' )
@@ -941,7 +942,7 @@ Page.Events = class Events extends Page.PageUtils {
941942
var { nice_icon, nice_type, nice_desc } = self.getTriggerDisplayArgs(item);
942943

943944
var tds = [
944-
'<div class="nowrap ellip">' + nice_desc + '</div>',
945+
'<div class="nowrap ellip">' + nice_desc.replace(/\&nbsp\;/g, '') + '</div>',
945946
'<div class="td_big nowrap">' + nice_icon + nice_type + '</div>',
946947
];
947948

@@ -3190,21 +3191,21 @@ Page.Events = class Events extends Page.PageUtils {
31903191
html += this.getFormRow({
31913192
id: 'd_et_range_desc',
31923193
label: 'Description:',
3193-
content: 'This option allows you to set a starting and/or ending date/time for the event. Jobs will not be scheduled before your start date/time, nor after your end date/time. This is designed to accompany other triggers.'
3194+
content: 'This modifier allows you to set a starting and/or ending date/time for the event. Jobs will not be scheduled before your start date/time, nor after your end date/time. This is designed to accompany other triggers.'
31943195
});
31953196

31963197
// blackout
31973198
html += this.getFormRow({
31983199
id: 'd_et_blackout_desc',
31993200
label: 'Description:',
3200-
content: 'This option allows you to set a "blackout" period for the event, meaning jobs will not be scheduled during this time. Examples include company holidays, and maintenance windows. This is designed to accompany other triggers.'
3201+
content: 'This modifier allows you to set a "blackout" period for the event, meaning jobs will not be scheduled during this time. Examples include company holidays, and maintenance windows. This is designed to accompany other triggers.'
32013202
});
32023203

32033204
// delay
32043205
html += this.getFormRow({
32053206
id: 'd_et_delay_desc',
32063207
label: 'Description:',
3207-
content: 'This option allows you to set a custom delay for each job launched by the scheduler. This does not affect jobs launched manually in the UI or via the API.'
3208+
content: 'This modifier allows you to set a custom delay for each job launched by the scheduler. This does not affect jobs launched manually in the UI or via the API.'
32083209
});
32093210
html += this.getFormRow({
32103211
id: 'd_et_delay',
@@ -3272,7 +3273,7 @@ Page.Events = class Events extends Page.PageUtils {
32723273
html += this.getFormRow({
32733274
id: 'd_et_precision_desc',
32743275
label: 'Description:',
3275-
content: 'This option allows you to set the precise seconds when each job should launch via the scheduler. This does not affect jobs launched manually in the UI or via the API.'
3276+
content: 'This modifier allows you to set the precise seconds when each job should launch via the scheduler. This does not affect jobs launched manually in the UI or via the API.'
32763277
});
32773278

32783279
// precision seconds
@@ -3292,6 +3293,37 @@ Page.Events = class Events extends Page.PageUtils {
32923293
})
32933294
});
32943295

3296+
// quiet desc
3297+
html += this.getFormRow({
3298+
id: 'd_et_quiet_desc',
3299+
label: 'Description:',
3300+
content: 'This modifier allows you to hide jobs from the UI, and/or delete jobs after completion. This does not affect jobs launched manually in the UI or via the API.'
3301+
});
3302+
3303+
// quiet invisible
3304+
html += this.getFormRow({
3305+
id: 'd_et_quiet_invisible',
3306+
label: 'Visibility:',
3307+
content: this.getFormCheckbox({
3308+
id: 'fe_et_quiet_invisible',
3309+
label: 'Invisible Jobs',
3310+
checked: !!trigger.invisible
3311+
}),
3312+
caption: 'Make all running jobs completely invisible to the UI.'
3313+
});
3314+
3315+
// quiet ephemeral
3316+
html += this.getFormRow({
3317+
id: 'd_et_quiet_ephemeral',
3318+
label: 'Permanence:',
3319+
content: this.getFormCheckbox({
3320+
id: 'fe_et_quiet_ephemeral',
3321+
label: 'Ephemeral Jobs',
3322+
checked: !!trigger.ephemeral
3323+
}),
3324+
caption: 'Delete all jobs after completion. Note that if a job produces output files, it automatically disables ephemeral mode.'
3325+
});
3326+
32953327
// timezone (shared by schedule and crontab types)
32963328
var zones = [
32973329
['', "Server Default (" + app.config.tz + ")"],
@@ -3519,6 +3551,15 @@ Page.Events = class Events extends Page.PageUtils {
35193551
}
35203552
break;
35213553

3554+
case 'quiet':
3555+
// quiet mode
3556+
trigger.invisible = $('#fe_et_quiet_invisible').is(':checked');
3557+
trigger.ephemeral = $('#fe_et_quiet_ephemeral').is(':checked');
3558+
if (!trigger.invisible && !trigger.ephemeral) {
3559+
return app.doError("You must select at least one mode for the quiet modifier.");
3560+
}
3561+
break;
3562+
35223563
case 'plugin':
35233564
trigger.plugin_id = $('#fe_et_plugin').val();
35243565
if (!trigger.plugin_id) return app.badField('#fe_et_plugin', "Please select a Plugin for scheduling.");
@@ -3680,6 +3721,13 @@ Page.Events = class Events extends Page.PageUtils {
36803721
new_btn_label = 'Add Modifier';
36813722
break;
36823723

3724+
case 'quiet':
3725+
$('#d_et_quiet_desc').show();
3726+
$('#d_et_quiet_invisible').show();
3727+
$('#d_et_quiet_ephemeral').show();
3728+
new_btn_label = 'Add Modifier';
3729+
break;
3730+
36833731
case 'plugin':
36843732
$('#d_et_plugin').show();
36853733
$('#d_et_plugin_params').show();

htdocs/js/pages/Job.class.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ Page.Job = class Job extends Page.PageUtils {
6969
this.live = false;
7070

7171
if (job.icon) icon = job.icon;
72+
else if (job.invisible) icon = 'selection-ellipse';
7273
else {
7374
if (is_workflow) icon = 'clipboard';
7475
else icon = 'timer';

htdocs/js/pages/PageUtils.class.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3939,7 +3939,7 @@ Page.PageUtils = class PageUtils extends Page.Base {
39393939
if (!trigger.enabled) short_desc = '(Disabled)';
39403940
if (trigger.type.match(/^(interval|single|startup)$/)) nice_title = alt_type;
39413941

3942-
if (trigger.type.match(/^(catchup|range|blackout|delay|precision|plugin)$/)) {
3942+
if (trigger.type.match(/^(catchup|range|blackout|delay|precision|quiet|plugin)$/)) {
39433943
// option triggers are rendered as pure circles with no pole
39443944
nice_title = alt_type;
39453945
inner_classes.push('wf_option');
@@ -5163,6 +5163,26 @@ Page.PageUtils = class PageUtils extends Page.Base {
51635163
nice_desc = '<i class="mdi mdi-progress-clock">&nbsp;</i><b>Seconds:</b> ' + short_desc;
51645164
break;
51655165

5166+
case 'quiet':
5167+
nice_icon = '<i class="mdi mdi-cog-outline"></i>';
5168+
nice_type = 'Modifier';
5169+
alt_type = 'Quiet';
5170+
short_desc = '';
5171+
nice_desc = '';
5172+
if (item.invisible) {
5173+
short_desc += 'Invisible';
5174+
nice_desc += '<i class="mdi mdi-selection-ellipse">&nbsp;</i><b>Invisible</b>';
5175+
}
5176+
if (item.ephemeral) {
5177+
if (short_desc) short_desc += ', ';
5178+
if (nice_desc) nice_desc += ', ';
5179+
short_desc += 'Ephemeral';
5180+
nice_desc += '<i class="mdi mdi-trash-can-outline">&nbsp;</i><b>Ephemeral</b>';
5181+
}
5182+
if (!short_desc) short_desc = '(None)';
5183+
if (!nice_desc) nice_desc = '(None)';
5184+
break;
5185+
51665186
case 'plugin':
51675187
nice_icon = '<i class="mdi mdi-power-plug"></i>';
51685188
nice_type = alt_type = 'Plugin';

htdocs/js/pages/Workflows.class.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1994,12 +1994,12 @@ Page.Workflows = class Workflows extends Page.Events {
19941994
// compute x/y for new node
19951995
if (this.wfPausedSolder) {
19961996
// resume paused solder
1997-
if (trigger.type.match(/^(catchup|range|blackout|delay|precision)$/)) {
1997+
if (trigger.type.match(/^(catchup|range|blackout|delay|precision|quiet|plugin)$/)) {
19981998
// special trigger type has no solder poles, so abort the solder, but place the node in the correct location
19991999
var solder = this.wfPausedSolder;
20002000
delete this.wfPausedSolder;
2001-
node.x = solder.x - (width / 2);
2002-
node.y = solder.y - (height / 2);
2001+
node.x = solder.x - (node_width / 2);
2002+
node.y = solder.y - (node_height / 2);
20032003
}
20042004
else {
20052005
this.resumePausedSolder(node.id, node_width, node_height);

htdocs/js/utils.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ function summarize_event_timings(event) {
6767
case 'blackout': opts.push("Blackout"); break;
6868
case 'delay': opts.push("Delay"); break;
6969
case 'precision': opts.push("Precision"); break;
70+
case 'quiet': opts.push("Quiet"); break;
7071
case 'plugin':
7172
var plugin = find_object( app.plugins, { id: trigger.plugin_id } );
7273
if (plugin) opts.push(plugin.title);

internal/ui.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,6 +1146,7 @@
11461146
{ "id": "blackout", "title": "Blackout", "icon": "circle" },
11471147
{ "id": "delay", "title": "Delay", "icon": "chat-sleep-outline" },
11481148
{ "id": "precision", "title": "Precision", "icon": "progress-clock" },
1149+
{ "id": "quiet", "title": "Quiet", "icon": "volume-mute" },
11491150
{ "id": "plugin", "title": "Plugin", "icon": "power-plug" }
11501151
],
11511152

lib/api/events.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -942,6 +942,10 @@ class Events {
942942
if (!trigger.seconds) return this.doError('api', err_prefix + ": Precision rule is missing seconds array.", callback);
943943
break;
944944

945+
case 'quiet':
946+
if (!trigger.invisible && !trigger.ephemeral) return this.doError('api', err_prefix + ": Quiet rule must have one or more modes enabled.", callback);
947+
break;
948+
945949
case 'plugin':
946950
if (!Tools.findObject(this.plugins, { id: trigger.plugin_id, type: 'scheduler' })) {
947951
return this.doError('api', "Scheduler Plugin not found: " + trigger.plugin_id, callback);
@@ -969,6 +973,9 @@ class Events {
969973
if (Tools.findObjects(etriggers, { type: 'precision' }).length > 1) {
970974
return this.doError('api', "Only one precision rule is allowed in trigger list.", callback);
971975
}
976+
if (Tools.findObjects(etriggers, { type: 'quiet' }).length > 1) {
977+
return this.doError('api', "Only one quiet rule is allowed in trigger list.", callback);
978+
}
972979
if (Tools.findObjects(etriggers, { type: 'plugin' }).length > 1) {
973980
return this.doError('api', "Only one plugin rule is allowed in trigger list.", callback);
974981
}

0 commit comments

Comments
 (0)