@pixlcore/xycli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +543 -0
- package/docs/help.md +2587 -0
- package/docs/screenshots.md +219 -0
- package/docs/sync.md +920 -0
- package/docs/tutorial.md +204 -0
- package/index.js +444 -0
- package/lib/alerts.js +735 -0
- package/lib/apikey.js +425 -0
- package/lib/buckets.js +607 -0
- package/lib/categories.js +323 -0
- package/lib/channels.js +290 -0
- package/lib/config.js +86 -0
- package/lib/dashboard.js +237 -0
- package/lib/doc.js +131 -0
- package/lib/events.js +1314 -0
- package/lib/groups.js +1038 -0
- package/lib/jobs.js +1254 -0
- package/lib/log.js +125 -0
- package/lib/marketplace.js +386 -0
- package/lib/monitors.js +412 -0
- package/lib/plugins.js +491 -0
- package/lib/secrets.js +441 -0
- package/lib/servers.js +1502 -0
- package/lib/snapshots.js +498 -0
- package/lib/sync.js +963 -0
- package/lib/system.js +612 -0
- package/lib/tags.js +250 -0
- package/lib/tickets.js +681 -0
- package/lib/transfer.js +447 -0
- package/lib/utils.js +2745 -0
- package/lib/webhook.js +414 -0
- package/logo.webp +0 -0
- package/package.json +38 -0
- package/test/README.md +97 -0
- package/test/api-validation.test.js +170 -0
- package/test/categories.test.js +246 -0
- package/test/channels.test.js +209 -0
- package/test/config.test.js +96 -0
- package/test/doc.test.js +84 -0
- package/test/groups.test.js +211 -0
- package/test/help.test.js +26 -0
- package/test/helpers/common.js +127 -0
- package/test/helpers/pagination-preload.cjs +18 -0
- package/test/log.test.js +103 -0
- package/test/markdown.test.js +61 -0
- package/test/marketplace.test.js +229 -0
- package/test/monitors.test.js +227 -0
- package/test/pagination.test.js +138 -0
- package/test/plugins.test.js +351 -0
- package/test/secrets.test.js +271 -0
- package/test/servers.test.js +303 -0
- package/test/snapshots.test.js +167 -0
- package/test/sync-unit.test.js +446 -0
- package/test/sync.test.js +542 -0
- package/test/system.test.js +94 -0
- package/test/tags.test.js +230 -0
- package/test/tickets.test.js +261 -0
- package/test/toast.test.js +73 -0
- package/test/transfer-apikey.test.js +96 -0
- package/test/transfer-workflow.test.js +122 -0
- package/test/transfer.test.js +314 -0
- package/test/webhook.test.js +332 -0
- package/test.js +49 -0
package/lib/events.js
ADDED
|
@@ -0,0 +1,1314 @@
|
|
|
1
|
+
// Events Layer
|
|
2
|
+
|
|
3
|
+
const cli = require('pixl-cli');
|
|
4
|
+
const Tools = cli.Tools;
|
|
5
|
+
const chalk = cli.chalk;
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
|
|
9
|
+
async cmd_events() {
|
|
10
|
+
// alias for cmd_get_events
|
|
11
|
+
await this.cmd_get_events();
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
async cmd_event() {
|
|
15
|
+
// router, e.g. `xy event get`
|
|
16
|
+
var cmd = this.args.other.shift();
|
|
17
|
+
switch (cmd) {
|
|
18
|
+
case 'list': await this.cmd_get_events(); break;
|
|
19
|
+
case 'get': await this.cmd_get_event(); break;
|
|
20
|
+
case 'run': await this.cmd_run_event(); break;
|
|
21
|
+
|
|
22
|
+
case 'create': await this.cmd_create_event(); break;
|
|
23
|
+
case 'update': await this.cmd_update_event(); break;
|
|
24
|
+
case 'delete': await this.cmd_delete_event(); break;
|
|
25
|
+
|
|
26
|
+
default:
|
|
27
|
+
// assume cmd_get_event, and other arg was the id
|
|
28
|
+
this.args.other.unshift(cmd);
|
|
29
|
+
await this.cmd_get_event();
|
|
30
|
+
break;
|
|
31
|
+
} // switch cmd
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
async cmd_run() {
|
|
35
|
+
// alias for `xy event run`
|
|
36
|
+
await this.cmd_run_event();
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
async cmd_get_events() {
|
|
40
|
+
// show event list
|
|
41
|
+
// args: raw, (any filters)
|
|
42
|
+
await this.getMultiple();
|
|
43
|
+
|
|
44
|
+
// apply user filters
|
|
45
|
+
var is_filtered = false;
|
|
46
|
+
var events = this.events;
|
|
47
|
+
|
|
48
|
+
// xy events foobar == search for title:foobar
|
|
49
|
+
if (this.args.other.length) this.args.title = this.args.other.join(' ');
|
|
50
|
+
delete this.args.other;
|
|
51
|
+
|
|
52
|
+
// special case for category, plugin, target
|
|
53
|
+
if (this.args.category) {
|
|
54
|
+
var cats = this.findObjectsFuzzy( this.categories, { id: this.args.category, title: this.args.category }, 1 );
|
|
55
|
+
events = events.filter( event => !!Tools.findObject(cats, { id: event.category }) );
|
|
56
|
+
is_filtered = true;
|
|
57
|
+
delete this.args.category;
|
|
58
|
+
}
|
|
59
|
+
if (this.args.plugin) {
|
|
60
|
+
var plugs = this.findObjectsFuzzy( this.plugins, { id: this.args.plugin, title: this.args.plugin }, 1 );
|
|
61
|
+
events = events.filter( event => !!Tools.findObject(plugs, { id: event.plugin }) );
|
|
62
|
+
is_filtered = true;
|
|
63
|
+
delete this.args.plugin;
|
|
64
|
+
}
|
|
65
|
+
if (this.args.target) {
|
|
66
|
+
var grps = this.findObjectsFuzzy( this.groups, { id: this.args.target, title: this.args.target }, 1 );
|
|
67
|
+
if (grps.length) {
|
|
68
|
+
events = events.filter( event => this.includesAny(event.targets || [], grps.map( grp => grp.id )) );
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
var srvs = this.findObjectsFuzzy( Object.values(this.servers), { id: this.args.target, title: this.args.target }, 1 );
|
|
72
|
+
events = events.filter( event => this.includesAny(event.targets || [], srvs.map( srv => srv.id )) );
|
|
73
|
+
}
|
|
74
|
+
is_filtered = true;
|
|
75
|
+
delete this.args.target;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (Tools.numKeys(this.args)) {
|
|
79
|
+
// if any args are left, consider them to be filters
|
|
80
|
+
is_filtered = true;
|
|
81
|
+
events = this.findObjectsFuzzy( events, this.args );
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (this.format.match(/json/)) {
|
|
85
|
+
// early exit for json mode
|
|
86
|
+
return this.jsonOutput(events);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// full cli table mode
|
|
90
|
+
if (is_filtered) println( "\n " + this.color('theme').bold("FILTERED EVENTS") + "\n " + gray('(' + Tools.commify(events.length) + ' of ' + Tools.commify(this.events.length) + ' total events)') );
|
|
91
|
+
else println( "\n " + this.color('theme').bold("ALL EVENTS") + "\n " + gray('(' + Tools.commify(events.length) + ' total events)') );
|
|
92
|
+
|
|
93
|
+
// split up by sorted category so we can drop that column
|
|
94
|
+
Tools.sortBy( this.categories, 'sort_order', { type: 'number', dir: 1 } ).forEach( category => {
|
|
95
|
+
var cat_events = Tools.findObjects( events, { category: category.id } );
|
|
96
|
+
if (!cat_events.length) return;
|
|
97
|
+
Tools.sortBy(cat_events, 'title');
|
|
98
|
+
|
|
99
|
+
println( "\n " + cli.emoji('📂') + ' ' + this.getNiceCategory(category.id) );
|
|
100
|
+
|
|
101
|
+
var rows = [
|
|
102
|
+
['Event ID', 'Title', 'Plugin', 'Targets', 'Triggers', 'Status', 'Modified']
|
|
103
|
+
];
|
|
104
|
+
cat_events.forEach( event => {
|
|
105
|
+
rows.push([
|
|
106
|
+
this.getNiceEventID(event.id),
|
|
107
|
+
this.getNiceEvent(event.id),
|
|
108
|
+
this.getNicePlugin(event.plugin),
|
|
109
|
+
this.getNiceTargets(event.targets || []),
|
|
110
|
+
summarize_event_timings(event),
|
|
111
|
+
this.getNiceEventStatus(event),
|
|
112
|
+
// this.getNiceDateTime(event.modified)
|
|
113
|
+
this.getTextFromSecondsShort(this.epoch - event.modified) + ' ago'
|
|
114
|
+
]);
|
|
115
|
+
} );
|
|
116
|
+
var clr = gray;
|
|
117
|
+
if (category.color && (category.color != 'plain')) clr = this.color(category.color);
|
|
118
|
+
print( "" + this.table(rows, { borderStyles: [clr] }) + "\n" );
|
|
119
|
+
} ); // foreach cat
|
|
120
|
+
|
|
121
|
+
if (!events.length) {
|
|
122
|
+
if (is_filtered) print( gray(" (No events found matching your filters.)") + "\n");
|
|
123
|
+
else print( gray(" (No events found.)") + "\n");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// suggested commands
|
|
127
|
+
this.printSuggestedCommands({
|
|
128
|
+
"Specific category": "xy events --category ID_OR_TITLE",
|
|
129
|
+
"Specific plugin": "xy events --plugin ID_OR_TITLE",
|
|
130
|
+
"Specific group": "xy events --target ID_OR_TITLE",
|
|
131
|
+
"Get event details": "xy event ID_OR_TITLE"
|
|
132
|
+
});
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
async cmd_get_event() {
|
|
136
|
+
// Only the related-job variants use pagination. Remove their page controls
|
|
137
|
+
// before resolving an event from named filters.
|
|
138
|
+
if (this.args.upcoming || this.args.completed || this.args.queued || this.args.queue) this.prepSearchArgs();
|
|
139
|
+
|
|
140
|
+
// get single event and display details
|
|
141
|
+
await this.getMultiple();
|
|
142
|
+
|
|
143
|
+
if (!this.events.length) {
|
|
144
|
+
return this.die( "No events found." );
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
var event = null;
|
|
148
|
+
if (this.args.other.length) {
|
|
149
|
+
// assume loose argument is event ID or fuzzy title
|
|
150
|
+
var str = this.args.other.join(' ');
|
|
151
|
+
event = this.findObjectFuzzy( this.events, { id: str, title: str }, 1 );
|
|
152
|
+
if (!event) return this.die("Could not find event based on your criteria: " + str);
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
delete this.args.other;
|
|
156
|
+
event = this.findObjectFuzzy( this.events, this.args );
|
|
157
|
+
if (!event) return this.die("Could not find event based on your criteria: " + JSON.stringify(this.args));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (this.format.match(/json/)) {
|
|
161
|
+
// early exit for json mode
|
|
162
|
+
return this.jsonOutput(event);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// summary table
|
|
166
|
+
this.printBoxList({
|
|
167
|
+
title: event.workflow ? "Workflow Summary" : "Event Summary",
|
|
168
|
+
rows: [
|
|
169
|
+
[ 'Event ID', gray(event.id) ],
|
|
170
|
+
[ 'Title', this.color('theme').bold(event.title) ],
|
|
171
|
+
[ 'Status', this.getNiceEnabled(event.enabled) ],
|
|
172
|
+
[ 'Category', this.getNiceCategory(event.category) + ` ${gray('(' + event.category + ')')}` ],
|
|
173
|
+
[ 'Plugin', this.getNicePlugin(event.plugin) + (event.plugin != '_workflow' ? ` ${gray('(' + event.plugin + ')')}` : '') ],
|
|
174
|
+
[ 'Targets', this.getNiceTargets(event.targets) ],
|
|
175
|
+
[ 'Algorithm', this.getNiceAlgo(event.algo) ],
|
|
176
|
+
[ 'Author', event.username ],
|
|
177
|
+
[ 'Created', this.getNiceDateTime(event.created, true, true) ],
|
|
178
|
+
[ 'Modified', this.getNiceDateTime(event.modified, true, true) ],
|
|
179
|
+
[ 'Revision', event.revision ]
|
|
180
|
+
]
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// notes
|
|
184
|
+
if (event.notes) {
|
|
185
|
+
this.printUserNotes('EVENT NOTES', event.notes);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// upcoming jobs
|
|
189
|
+
if (this.args.upcoming) {
|
|
190
|
+
await this.printUpcomingJobs({
|
|
191
|
+
events: [event]
|
|
192
|
+
});
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// completed jobs
|
|
197
|
+
if (this.args.completed) {
|
|
198
|
+
await this.printCompletedJobs({
|
|
199
|
+
query: 'event:' + event.id
|
|
200
|
+
});
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// queued jobs
|
|
205
|
+
if (this.args.queued || this.args.queue) {
|
|
206
|
+
await this.printQueuedJobs({
|
|
207
|
+
event: event.id
|
|
208
|
+
});
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// triggers
|
|
213
|
+
this.printBoxTable({
|
|
214
|
+
title: "Triggers",
|
|
215
|
+
header: ['Status', 'Description', 'Type', 'Tags'],
|
|
216
|
+
rows: (event.triggers || []).map( item => {
|
|
217
|
+
var { nice_type, nice_desc } = this.getTriggerDisplayArgs(item);
|
|
218
|
+
return [
|
|
219
|
+
this.getNiceEnabled(item.enabled),
|
|
220
|
+
item.enabled ? bold(nice_desc) : gray(nice_desc),
|
|
221
|
+
item.enabled ? nice_type : gray(nice_type),
|
|
222
|
+
this.getNiceTagList( item.tags || [] )
|
|
223
|
+
];
|
|
224
|
+
} )
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// actions
|
|
228
|
+
var actions = [].concat( event.actions || [] );
|
|
229
|
+
|
|
230
|
+
// add inherited category actions
|
|
231
|
+
var category = Tools.findObject( this.categories, { id: event.category } ) || {};
|
|
232
|
+
(category.actions || []).forEach( function(action) {
|
|
233
|
+
actions.push({ ...action, source: 'category' });
|
|
234
|
+
} );
|
|
235
|
+
|
|
236
|
+
// add universal actions (not hidden)
|
|
237
|
+
var temp_event_type = event.workflow ? 'workflow' : 'default';
|
|
238
|
+
this.config.job_universal_actions[temp_event_type].forEach( function(action) {
|
|
239
|
+
if (action.condition && !action.hidden) actions.push({ ...action, source: 'universal' });
|
|
240
|
+
} );
|
|
241
|
+
|
|
242
|
+
this.printBoxTable({
|
|
243
|
+
title: "Actions",
|
|
244
|
+
header: ['#', 'Status', 'Condition', 'Type', 'Description', 'Note'],
|
|
245
|
+
rows: actions.map( (item, idx) => {
|
|
246
|
+
var disp = this.getJobActionDisplayArgs(item);
|
|
247
|
+
return [
|
|
248
|
+
idx,
|
|
249
|
+
this.getNiceEnabled(item.enabled),
|
|
250
|
+
item.enabled ? this.color(disp.condition.color).bold(disp.condition.title) : gray(disp.condition.title),
|
|
251
|
+
item.enabled ? disp.type : gray(disp.type),
|
|
252
|
+
item.enabled ? disp.desc : gray(disp.desc),
|
|
253
|
+
item.enabled ? disp.note : gray(disp.note),
|
|
254
|
+
];
|
|
255
|
+
} )
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// limits
|
|
259
|
+
var limits = [].concat( event.limits || [] );
|
|
260
|
+
|
|
261
|
+
// add inherited category limits
|
|
262
|
+
var category = Tools.findObject( this.categories, { id: event.category } ) || {};
|
|
263
|
+
(category.limits || []).forEach( function(limit) {
|
|
264
|
+
limits.push({ ...limit, source: 'category' });
|
|
265
|
+
} );
|
|
266
|
+
|
|
267
|
+
// add universal limits (not hidden)
|
|
268
|
+
var temp_event_type = event.workflow ? 'workflow' : 'default';
|
|
269
|
+
this.config.job_universal_limits[temp_event_type].forEach( function(limit) {
|
|
270
|
+
if (limit.type && !limit.hidden) limits.push({ ...limit, source: 'universal' });
|
|
271
|
+
} );
|
|
272
|
+
|
|
273
|
+
this.printBoxTable({
|
|
274
|
+
title: "Limits",
|
|
275
|
+
header: ['#', 'Status', 'Limit', 'Description', 'Note'],
|
|
276
|
+
rows: limits.map( (item, idx) => {
|
|
277
|
+
var { nice_title, nice_desc, note } = this.getResLimitDisplayArgs(item);
|
|
278
|
+
return [
|
|
279
|
+
idx,
|
|
280
|
+
this.getNiceEnabled(item.enabled),
|
|
281
|
+
item.enabled ? bold(nice_title) : gray(nice_title),
|
|
282
|
+
item.enabled ? nice_desc : gray(nice_desc),
|
|
283
|
+
item.enabled ? note : gray(note),
|
|
284
|
+
];
|
|
285
|
+
} )
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// plugin param (values)
|
|
289
|
+
if (!event.workflow) {
|
|
290
|
+
var plugin = Tools.findObject( this.plugins, { id: event.plugin } ) || { title: "Plugin" };
|
|
291
|
+
this.printParamValues({
|
|
292
|
+
title: `${plugin.title} Parameters`,
|
|
293
|
+
fields: plugin.params || [],
|
|
294
|
+
values: event.params || {}
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// user params (field defs)
|
|
299
|
+
this.printParamFields({
|
|
300
|
+
title: "User Parameters",
|
|
301
|
+
fields: event.fields || []
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// workflow nodes (ugh)
|
|
305
|
+
if (event.workflow && event.workflow.nodes) {
|
|
306
|
+
var nodes = Tools.sortBy( event.workflow.nodes.filter( node => node.type.match(/^(event|job|controller)$/) ), 'type', { type: 'string', dir: 1 } );
|
|
307
|
+
this.printBoxTable({
|
|
308
|
+
title: "Workflow Nodes",
|
|
309
|
+
header: ['Node Title', 'ID', 'Type', 'Description', 'Connections'],
|
|
310
|
+
rows: nodes.map( node => {
|
|
311
|
+
var { title, type, desc, conns } = this.getWFNodeDisplayArgs(event, node);
|
|
312
|
+
return [ bold(title), gray(node.id), type, desc, conns ];
|
|
313
|
+
} )
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// active jobs
|
|
318
|
+
this.printActiveJobs({
|
|
319
|
+
title: "Active Jobs",
|
|
320
|
+
criteria: {
|
|
321
|
+
event: event.id
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// suggested commands
|
|
326
|
+
this.printSuggestedCommands({
|
|
327
|
+
"Queued jobs": `xy event ${event.id} --queued`,
|
|
328
|
+
"Upcoming jobs": `xy event ${event.id} --upcoming`,
|
|
329
|
+
"Completed jobs": `xy event ${event.id} --completed`,
|
|
330
|
+
"Run job manually": `xy run ${event.id}`,
|
|
331
|
+
"Update event": `xy event update ${event.id} [--KEY VALUE, ...]`,
|
|
332
|
+
"Export event": `xy event ${event.id} --export event.json`,
|
|
333
|
+
"Delete event": `xy event delete ${event.id}`
|
|
334
|
+
});
|
|
335
|
+
},
|
|
336
|
+
|
|
337
|
+
async cmd_run_event() {
|
|
338
|
+
// launch job for event
|
|
339
|
+
// launch w/params: xy run EVENT_ID --params.foo "bar"
|
|
340
|
+
// launch w/files: xy run EVENT_ID --file FILE1.txt --file FILE2.txt
|
|
341
|
+
// launch w/data: xy run EVENT_ID --input.data.foo "bar"
|
|
342
|
+
// custom actions array from file: xy run EVENT_ID --actions @my-actions.json
|
|
343
|
+
// entire request from stdin: xy run EVENT_ID --json @-
|
|
344
|
+
await this.getMultiple();
|
|
345
|
+
|
|
346
|
+
if (!this.events.length) {
|
|
347
|
+
return this.die( "No events found." );
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
var event = null;
|
|
351
|
+
if (this.args.other.length) {
|
|
352
|
+
// assume loose argument is event ID or fuzzy title
|
|
353
|
+
var str = this.args.other.join(' ');
|
|
354
|
+
event = this.findObjectFuzzy( this.events, { id: str, title: str }, 1 );
|
|
355
|
+
if (!event) return this.die("Could not find event based on your criteria: " + str);
|
|
356
|
+
}
|
|
357
|
+
else if (this.args.id) {
|
|
358
|
+
event = this.findObjectFuzzy( this.events, { id: this.args.id } );
|
|
359
|
+
if (!event) return this.die("Could not find event from ID: " + this.args.id);
|
|
360
|
+
}
|
|
361
|
+
else if (this.args.title) {
|
|
362
|
+
event = this.findObjectFuzzy( this.events, { title: this.args.title } );
|
|
363
|
+
if (!event) return this.die("Could not find event from title: " + this.args.title);
|
|
364
|
+
}
|
|
365
|
+
else this.die("Could not find event.");
|
|
366
|
+
|
|
367
|
+
delete this.args.other;
|
|
368
|
+
delete this.args.id;
|
|
369
|
+
delete this.args.title;
|
|
370
|
+
|
|
371
|
+
var do_follow = this.args.follow;
|
|
372
|
+
delete this.args.follow;
|
|
373
|
+
|
|
374
|
+
var files = Tools.alwaysArray( this.args.file || this.args.files || [] );
|
|
375
|
+
delete this.args.file;
|
|
376
|
+
delete this.args.files;
|
|
377
|
+
|
|
378
|
+
this.args.id = event.id;
|
|
379
|
+
|
|
380
|
+
// resolve dot paths in args
|
|
381
|
+
this.mergeDotArgs( this.args, this.args );
|
|
382
|
+
|
|
383
|
+
var req = this.args;
|
|
384
|
+
var opts = { text: 'Running event...' };
|
|
385
|
+
if (files.length) opts.files = files;
|
|
386
|
+
|
|
387
|
+
var data = await this.callStandardAPI('runEvent', req, opts);
|
|
388
|
+
if (this.dry) return;
|
|
389
|
+
|
|
390
|
+
this.toast('✅', 'green', "Successfully launched job: #" + data.id );
|
|
391
|
+
|
|
392
|
+
if (do_follow) {
|
|
393
|
+
this.gotMultiple = false;
|
|
394
|
+
this.args = { id: data.id };
|
|
395
|
+
await this.cmd_stream_job();
|
|
396
|
+
}
|
|
397
|
+
else if (!this.verbose) {
|
|
398
|
+
println( "\n " + green.bold("Watch job live: ") + bold("xy job " + data.id) );
|
|
399
|
+
}
|
|
400
|
+
},
|
|
401
|
+
|
|
402
|
+
async cmd_create_event() {
|
|
403
|
+
// create new event
|
|
404
|
+
// xy event create --title "Foo" --plugin testplug --params.duration 30
|
|
405
|
+
// xy event create --title "Yoo" --plugin shellplug --params.script "#!/bin/bash\necho 'Hi'"
|
|
406
|
+
// xy event create --title "Bar" --cron "30 4 * * *" --seconds 15
|
|
407
|
+
// xy event create --title "Baz" --interval "5 minutes" --catchup
|
|
408
|
+
await this.getMultiple();
|
|
409
|
+
|
|
410
|
+
// resolve dot paths in args
|
|
411
|
+
this.mergeDotArgs( this.args, this.args );
|
|
412
|
+
|
|
413
|
+
var params = Tools.mergeHashes( Tools.copyHash(this.config.new_event_template, true), this.args );
|
|
414
|
+
delete params.other;
|
|
415
|
+
|
|
416
|
+
if (!params.category) {
|
|
417
|
+
if (Tools.findObject(this.categories, { id: 'general' })) params.category = 'general';
|
|
418
|
+
else if (!this.categories.length) return this.die("You must define at least one category to add events.");
|
|
419
|
+
else params.category = this.categories[0].id;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (!params.plugin && (params.type != 'workflow')) {
|
|
423
|
+
if (Tools.findObject(this.plugins, { id: 'shellplug' })) params.plugin = 'shellplug';
|
|
424
|
+
else if (!this.plugins.length) return this.die("You must create at least one Plugin to add events.");
|
|
425
|
+
else params.plugin = this.plugins[0].id;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (!params.targets || !params.targets.length) {
|
|
429
|
+
if (Tools.findObject(this.groups, { id: 'main' })) params.targets = ['main'];
|
|
430
|
+
else if (!this.groups.length) return this.die("You must define at least one server group to add events.");
|
|
431
|
+
else params.targets = [ this.groups[0].id ];
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (!params.algo) params.algo = 'random';
|
|
435
|
+
|
|
436
|
+
this.processEventUpdates(params);
|
|
437
|
+
|
|
438
|
+
var data = await this.callStandardAPI('createEvent', params, {
|
|
439
|
+
text: 'Creating event...'
|
|
440
|
+
});
|
|
441
|
+
if (this.dry) return;
|
|
442
|
+
|
|
443
|
+
this.toast('✅', 'green', "Successfully created event: #" + data.event.id );
|
|
444
|
+
|
|
445
|
+
this.printSuggestedCommands({
|
|
446
|
+
"View event details": `xy event ${data.event.id}`,
|
|
447
|
+
"Run event manually": `xy run ${data.event.id}`,
|
|
448
|
+
"Run and follow": `xy run ${data.event.id} --follow`,
|
|
449
|
+
"Update event": `xy event update ${data.event.id} [--KEY VALUE, ...]`,
|
|
450
|
+
"List all events": "xy events"
|
|
451
|
+
});
|
|
452
|
+
},
|
|
453
|
+
|
|
454
|
+
async cmd_update_event() {
|
|
455
|
+
// update single event
|
|
456
|
+
// xy event update EVENT_ID --title "New Title" --params.duration 15
|
|
457
|
+
// xy event update "Title" --action '{ "type":"email", "enabled":true, "condition":"success", "users":["admin"] }'
|
|
458
|
+
// xy event update EVENT_ID --actions.0.condition "complete" --actions.0.enabled true
|
|
459
|
+
// xy event update EVENT_ID --field '{ "id":"foo", "title":"Foo", "type":"text", "value":"hello" }'
|
|
460
|
+
// xy event update "Title" --magic
|
|
461
|
+
await this.getMultiple();
|
|
462
|
+
|
|
463
|
+
if (!this.events.length) {
|
|
464
|
+
return this.die( "No events found." );
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
var event = null;
|
|
468
|
+
if (this.args.other.length) {
|
|
469
|
+
// assume loose argument is event ID or fuzzy title
|
|
470
|
+
var str = this.args.other.join(' ');
|
|
471
|
+
event = this.findObjectFuzzy( this.events, { id: str, title: str }, 1 );
|
|
472
|
+
if (!event) return this.die("Could not find event based on your criteria: " + str);
|
|
473
|
+
delete this.args.other;
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
delete this.args.other;
|
|
477
|
+
event = this.findObjectFuzzy( this.events, this.args );
|
|
478
|
+
if (!event) return this.die("Could not find event based on your criteria: " + JSON.stringify(this.args));
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
this.printMutationSummary({
|
|
482
|
+
title: event.workflow ? "Update Workflow" : "Update Event",
|
|
483
|
+
rows: [
|
|
484
|
+
[ 'Event ID', gray(event.id) ],
|
|
485
|
+
[ 'Title', this.color('theme').bold(event.title) ]
|
|
486
|
+
]
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
delete this.args.other;
|
|
490
|
+
if (!await this.confirmSyncUpdate('event', event.id, event.workflow ? 'workflow' : 'event')) return;
|
|
491
|
+
if (!Tools.firstKey(this.args)) return this.die("No updates specified for event.");
|
|
492
|
+
|
|
493
|
+
this.printUpdateData(this.args);
|
|
494
|
+
|
|
495
|
+
var params = this.prepareEventUpdateParams(event, this.args);
|
|
496
|
+
params.id = event.id;
|
|
497
|
+
|
|
498
|
+
var data = await this.callStandardAPI('updateEvent', params, {
|
|
499
|
+
text: 'Updating event...'
|
|
500
|
+
});
|
|
501
|
+
if (this.dry) return;
|
|
502
|
+
|
|
503
|
+
this.toast('✅', 'green', "Successfully updated event: #" + event.id );
|
|
504
|
+
},
|
|
505
|
+
|
|
506
|
+
async cmd_delete_event() {
|
|
507
|
+
// delete single event
|
|
508
|
+
// xy event delete EVENT_ID
|
|
509
|
+
// xy event delete EVENT_ID --delete_jobs
|
|
510
|
+
await this.getMultiple();
|
|
511
|
+
|
|
512
|
+
if (!this.events.length) {
|
|
513
|
+
return this.die( "No events found." );
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if (this.args.other && this.args.other.length && !this.args.id) {
|
|
517
|
+
this.args.id = this.args.other.shift();
|
|
518
|
+
}
|
|
519
|
+
if (!this.args.id) this.die("Missing required 'id' argument.");
|
|
520
|
+
|
|
521
|
+
delete this.args.other;
|
|
522
|
+
|
|
523
|
+
var event = Tools.findObject(this.events, { id: this.args.id });
|
|
524
|
+
if (!event) return this.die("Could not find event from ID: " + this.args.id);
|
|
525
|
+
|
|
526
|
+
this.printMutationSummary({
|
|
527
|
+
title: event.workflow ? "Delete Workflow" : "Delete Event",
|
|
528
|
+
rows: [
|
|
529
|
+
[ 'Event ID', gray(event.id) ],
|
|
530
|
+
[ 'Title', this.color('theme').bold(event.title) ]
|
|
531
|
+
]
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
// user must confirm, as this is destructive
|
|
535
|
+
if (this.args.confirm !== true) {
|
|
536
|
+
this.toast('⚠️', 'orange', "Please confirm the delete by adding '--confirm'.");
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
delete this.args.confirm;
|
|
540
|
+
|
|
541
|
+
var data = await this.callStandardAPI('deleteEvent', this.args, {
|
|
542
|
+
text: 'Deleting event...'
|
|
543
|
+
});
|
|
544
|
+
if (this.dry) return;
|
|
545
|
+
|
|
546
|
+
this.toast('✅', 'green', "Successfully deleted event: #" + event.id );
|
|
547
|
+
},
|
|
548
|
+
|
|
549
|
+
getNiceEventID(event) {
|
|
550
|
+
if (!event) return gray('(None)');
|
|
551
|
+
if (typeof(event) == 'string') {
|
|
552
|
+
event = Tools.findObject( this.events, { id: event } ) || { id: event, title: event };
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
if (!event.enabled) return gray(event.id);
|
|
556
|
+
|
|
557
|
+
var category = Tools.findObject( this.categories, { id: event.category } );
|
|
558
|
+
if (category && category.color && (category.color != 'plain')) {
|
|
559
|
+
return this.color(category.color).bold(event.id);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
return this.color('theme').bold(event.id);
|
|
563
|
+
},
|
|
564
|
+
|
|
565
|
+
prepareEventUpdateParams(event, input) {
|
|
566
|
+
// Build a sparse API request so properties introduced by a newer xyOps server
|
|
567
|
+
// are never echoed back through an older CLI. For dotted edits, clone only
|
|
568
|
+
// the affected saved container because xyOps shallow-merges top-level fields.
|
|
569
|
+
var params = {};
|
|
570
|
+
var dotted = {};
|
|
571
|
+
|
|
572
|
+
Object.keys(input).filter( key => !key.includes('.') ).forEach( key => {
|
|
573
|
+
params[key] = input[key];
|
|
574
|
+
});
|
|
575
|
+
Object.keys(input).filter( key => key.includes('.') ).forEach( key => {
|
|
576
|
+
var root = key.split('.')[0];
|
|
577
|
+
if (!Object.prototype.hasOwnProperty.call(params, root) && Object.prototype.hasOwnProperty.call(event, root)) {
|
|
578
|
+
params[root] = Tools.copyHash({ value: event[root] }, true).value;
|
|
579
|
+
}
|
|
580
|
+
dotted[key] = input[key];
|
|
581
|
+
});
|
|
582
|
+
this.mergeDotArgs(params, dotted);
|
|
583
|
+
|
|
584
|
+
// Singular append options and trigger shortcuts need the saved array so they
|
|
585
|
+
// preserve existing entries. Whole-array options above remain replacements.
|
|
586
|
+
var append_fields = {
|
|
587
|
+
trigger: 'triggers',
|
|
588
|
+
action: 'actions',
|
|
589
|
+
limit: 'limits',
|
|
590
|
+
field: 'fields'
|
|
591
|
+
};
|
|
592
|
+
Object.keys(append_fields).forEach( singular => {
|
|
593
|
+
var plural = append_fields[singular];
|
|
594
|
+
if ((singular in params) && !(plural in params)) {
|
|
595
|
+
params[plural] = Tools.copyHash({ value: event[plural] || [] }, true).value;
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
var trigger_shortcuts = ['cron', 'interval', 'single', 'magic', 'seconds', 'delay', 'invisible', 'ephemeral', 'catchup'];
|
|
600
|
+
if (trigger_shortcuts.some( key => key in params ) && !('triggers' in params)) {
|
|
601
|
+
params.triggers = Tools.copyHash({ value: event.triggers || [] }, true).value;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
this.processEventUpdates(params, event);
|
|
605
|
+
return params;
|
|
606
|
+
},
|
|
607
|
+
|
|
608
|
+
processEventUpdates(event, saved_event) {
|
|
609
|
+
// convert convenience param syntax into event object
|
|
610
|
+
|
|
611
|
+
// Creation retains the original empty-array defaults. Sparse updates only
|
|
612
|
+
// carry arrays which are explicitly replaced, edited or appended above.
|
|
613
|
+
if (!saved_event) {
|
|
614
|
+
if (!event.triggers) event.triggers = [];
|
|
615
|
+
if (!event.actions) event.actions = [];
|
|
616
|
+
if (!event.limits) event.limits = [];
|
|
617
|
+
if (!event.fields) event.fields = [];
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// allow convenience singular objects to append
|
|
621
|
+
if (event.trigger) { event.triggers.push(event.trigger); delete event.trigger; }
|
|
622
|
+
if (event.action) { event.actions.push(event.action); delete event.action; }
|
|
623
|
+
if (event.limit) { event.limits.push(event.limit); delete event.limit; }
|
|
624
|
+
if (event.field) { event.fields.push(event.field); delete event.field; }
|
|
625
|
+
|
|
626
|
+
// allow cron syntax to append a schedule trigger
|
|
627
|
+
if (event.cron) {
|
|
628
|
+
var trigger = this.parseCrontab(event.cron, event.title || (saved_event && saved_event.title));
|
|
629
|
+
trigger.type = 'schedule';
|
|
630
|
+
trigger.enabled = true;
|
|
631
|
+
event.triggers.push(trigger);
|
|
632
|
+
delete event.cron;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
if (event.interval) {
|
|
636
|
+
// add interval trigger
|
|
637
|
+
var trigger = {
|
|
638
|
+
type: 'interval',
|
|
639
|
+
enabled: true,
|
|
640
|
+
start: Tools.normalizeTime( Tools.timeNow(true), { sec: 0 } ),
|
|
641
|
+
duration: Tools.getSecondsFromText( event.interval ) || this.die("Could not parse seconds from interval: " + event.interval)
|
|
642
|
+
};
|
|
643
|
+
event.triggers.push(trigger);
|
|
644
|
+
delete event.interval;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
if (event.single) {
|
|
648
|
+
// add single-shot
|
|
649
|
+
var trigger = {
|
|
650
|
+
type: 'precision',
|
|
651
|
+
enabled: true,
|
|
652
|
+
epoch: Tools.parseDate(event.single) || this.die("Could not parse date from single-shot: " + event.single)
|
|
653
|
+
};
|
|
654
|
+
event.triggers.push(trigger);
|
|
655
|
+
delete event.single;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
if (event.magic) {
|
|
659
|
+
// add or refresh magic link
|
|
660
|
+
var trigger = {
|
|
661
|
+
type: 'magic',
|
|
662
|
+
enabled: true,
|
|
663
|
+
key: Tools.generateUniqueID()
|
|
664
|
+
};
|
|
665
|
+
var magic_link = this.config.base_url + "/api/app/magic/v1/" + trigger.key;
|
|
666
|
+
println( "\n " + cyan.bold("Magic Link: ") + " " + gray(magic_link) );
|
|
667
|
+
this.updateOrAdd( event.triggers, { type: 'magic' }, trigger );
|
|
668
|
+
delete event.magic;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if ('seconds' in event) {
|
|
672
|
+
// add or update precision
|
|
673
|
+
var trigger = {
|
|
674
|
+
type: 'precision',
|
|
675
|
+
enabled: true,
|
|
676
|
+
seconds: String(event.seconds).split(/\,\s*/).map( sec => parseInt(sec) )
|
|
677
|
+
};
|
|
678
|
+
this.updateOrAdd( event.triggers, { type: 'precision' }, trigger );
|
|
679
|
+
delete event.seconds;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
if (event.delay) {
|
|
683
|
+
// add or update delay
|
|
684
|
+
var trigger = {
|
|
685
|
+
type: 'delay',
|
|
686
|
+
enabled: true,
|
|
687
|
+
duration: Tools.getSecondsFromText(event.delay) || this.die("Could not parse seconds from delay: " + event.delay)
|
|
688
|
+
};
|
|
689
|
+
this.updateOrAdd( event.triggers, { type: 'delay' }, trigger );
|
|
690
|
+
delete event.delay;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
if (event.invisible) {
|
|
694
|
+
// add or update quiet (invisible)
|
|
695
|
+
var trigger = {
|
|
696
|
+
type: 'quiet',
|
|
697
|
+
enabled: true,
|
|
698
|
+
invisible: true
|
|
699
|
+
};
|
|
700
|
+
this.updateOrAdd( event.triggers, { type: 'quiet' }, trigger );
|
|
701
|
+
delete event.invisible;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
if (event.ephemeral) {
|
|
705
|
+
// add or update quiet (ephemeral)
|
|
706
|
+
var trigger = {
|
|
707
|
+
type: 'quiet',
|
|
708
|
+
enabled: true,
|
|
709
|
+
ephemeral: true
|
|
710
|
+
};
|
|
711
|
+
this.updateOrAdd( event.triggers, { type: 'quiet' }, trigger );
|
|
712
|
+
delete event.ephemeral;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
if (event.catchup) {
|
|
716
|
+
// add or update catch-up
|
|
717
|
+
var trigger = {
|
|
718
|
+
type: 'catchup',
|
|
719
|
+
enabled: true
|
|
720
|
+
};
|
|
721
|
+
this.updateOrAdd( event.triggers, { type: 'catchup' }, trigger );
|
|
722
|
+
delete event.catchup;
|
|
723
|
+
}
|
|
724
|
+
},
|
|
725
|
+
|
|
726
|
+
updateOrAdd(arr, criteria, obj) {
|
|
727
|
+
// update object if found based on criteria, otherwise add new
|
|
728
|
+
var old = Tools.findObject(arr, criteria);
|
|
729
|
+
if (old) Tools.mergeHashInto(old, obj);
|
|
730
|
+
else arr.push(obj);
|
|
731
|
+
},
|
|
732
|
+
|
|
733
|
+
getWFNodeDisplayArgs(event, node) {
|
|
734
|
+
// get display args for any workflow node
|
|
735
|
+
// title, type, desc, conns
|
|
736
|
+
var workflow = event.workflow;
|
|
737
|
+
var title = '';
|
|
738
|
+
var type = '';
|
|
739
|
+
var desc = '';
|
|
740
|
+
|
|
741
|
+
var conns = [];
|
|
742
|
+
(workflow.connections || []).forEach( function(conn) {
|
|
743
|
+
if (conn.source == node.id) {
|
|
744
|
+
var dest_node = Tools.findObject( workflow.nodes, { id: conn.dest } );
|
|
745
|
+
if (dest_node && dest_node.type.match(/^(event|job|controller)$/)) conns.push( conn.dest );
|
|
746
|
+
}
|
|
747
|
+
// else if (conn.dest == node.id) conns.push( conn.source );
|
|
748
|
+
} );
|
|
749
|
+
conns = conns.join(', ') || gray('(None)');
|
|
750
|
+
|
|
751
|
+
switch (node.type) {
|
|
752
|
+
case 'event':
|
|
753
|
+
var event = Tools.findObject( this.events, { id: node.data.event } ) || { title: red("(Event Not Found)") };
|
|
754
|
+
type = "Event";
|
|
755
|
+
title = event.title;
|
|
756
|
+
desc = node.data.replay ? `(Replay Job #${node.data.replay})` : '-';
|
|
757
|
+
break;
|
|
758
|
+
|
|
759
|
+
case 'job':
|
|
760
|
+
var plugin = Tools.findObject( this.plugins, { id: node.data.plugin } ) || { title: red("(Plugin Not Found)") };
|
|
761
|
+
type = "Job";
|
|
762
|
+
title = node.data.label || plugin.title;
|
|
763
|
+
desc = node.data.replay ? `(Replay Job #${node.data.replay})` : '-';
|
|
764
|
+
break;
|
|
765
|
+
|
|
766
|
+
case 'controller':
|
|
767
|
+
type = "Controller";
|
|
768
|
+
title = Tools.ucfirst( node.data.controller ) + " Controller";
|
|
769
|
+
desc = node.data.split || node.data.decision || '-';
|
|
770
|
+
if (node.data.controller == 'repeat') desc = 'Repeat ' + node.data.repeat + " times";
|
|
771
|
+
else if (node.data.controller == 'wait') desc = 'Wait ' + Tools.getTextFromSeconds(node.data.wait, false, false);
|
|
772
|
+
break;
|
|
773
|
+
} // switch node.type
|
|
774
|
+
|
|
775
|
+
return { title, type, desc, conns };
|
|
776
|
+
},
|
|
777
|
+
|
|
778
|
+
getNiceEventStatus(event) {
|
|
779
|
+
// get pretty event status (active jobs or last result)
|
|
780
|
+
var num_jobs = 0;
|
|
781
|
+
var last_job_id = '';
|
|
782
|
+
for (var job_id in this.activeJobs) {
|
|
783
|
+
var job = this.activeJobs[job_id];
|
|
784
|
+
if (job.event == event.id) { num_jobs++; last_job_id = job.id; }
|
|
785
|
+
}
|
|
786
|
+
var nice_status = gray('Idle');
|
|
787
|
+
var event_state = Tools.getPath( this.state, 'events/' + event.id );
|
|
788
|
+
|
|
789
|
+
if (num_jobs) {
|
|
790
|
+
nice_status = cli.emoji('🔄') + ' ' + this.color('theme').bold( num_jobs + ' Active' );
|
|
791
|
+
}
|
|
792
|
+
else if (!num_jobs && event_state && event_state.last_job) {
|
|
793
|
+
switch (event_state.last_code) {
|
|
794
|
+
case 'warning': nice_status = cli.emoji('⚠️') + ' ' + this.color('yellow').bold('Warning'); break;
|
|
795
|
+
case 'critical': nice_status = cli.emoji('☢️') + ' ' + this.color('purple').bold('Critical'); break;
|
|
796
|
+
case 'abort': nice_status = cli.emoji('🚫') + ' ' + this.color('gray').bold('Abort'); break;
|
|
797
|
+
default:
|
|
798
|
+
if (event_state.last_code) nice_status = cli.emoji('🛑') + ' ' + this.color('red').bold('Error');
|
|
799
|
+
else nice_status = cli.emoji('✅') + ' ' + this.color('green').bold('Success');
|
|
800
|
+
break;
|
|
801
|
+
}
|
|
802
|
+
if (event_state.last_completed) {
|
|
803
|
+
nice_status += ' ' + gray( this.getTextFromSecondsShort(this.epoch - event_state.last_completed) + ' ago' );
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
return nice_status;
|
|
808
|
+
},
|
|
809
|
+
|
|
810
|
+
getTriggerDisplayArgs(item) {
|
|
811
|
+
// prep trigger item for display
|
|
812
|
+
var nice_type = '';
|
|
813
|
+
var alt_type = '';
|
|
814
|
+
var nice_desc = '';
|
|
815
|
+
var short_desc = '';
|
|
816
|
+
|
|
817
|
+
var menu_item = Tools.findObject( this.config.ui.event_trigger_type_menu, { id: item.type } );
|
|
818
|
+
|
|
819
|
+
switch (item.type) {
|
|
820
|
+
case 'schedule':
|
|
821
|
+
nice_type = 'Schedule';
|
|
822
|
+
short_desc = summarize_event_timing(item);
|
|
823
|
+
nice_desc = short_desc;
|
|
824
|
+
|
|
825
|
+
// find actual sub-type based on schedule trigger params
|
|
826
|
+
var trigger = item;
|
|
827
|
+
var tmode = 'hourly';
|
|
828
|
+
if (trigger.years && trigger.years.length) tmode = 'custom';
|
|
829
|
+
else if (trigger.months && trigger.months.length && trigger.weekdays && trigger.weekdays.length) tmode = 'custom';
|
|
830
|
+
else if (trigger.days && trigger.days.length && trigger.weekdays && trigger.weekdays.length) tmode = 'custom';
|
|
831
|
+
else if (trigger.months && trigger.months.length) tmode = 'yearly';
|
|
832
|
+
else if (trigger.weekdays && trigger.weekdays.length) tmode = 'weekly';
|
|
833
|
+
else if (trigger.days && trigger.days.length) tmode = 'monthly';
|
|
834
|
+
else if (trigger.hours && trigger.hours.length) tmode = 'daily';
|
|
835
|
+
else if (trigger.minutes && trigger.minutes.length) tmode = 'hourly';
|
|
836
|
+
break;
|
|
837
|
+
|
|
838
|
+
case 'interval':
|
|
839
|
+
nice_type = 'Schedule';
|
|
840
|
+
alt_type = 'Interval';
|
|
841
|
+
short_desc = Tools.getTextFromSeconds(item.duration || 0, true, false);
|
|
842
|
+
nice_desc = Tools.getTextFromSeconds(item.duration || 0, false, false);
|
|
843
|
+
break;
|
|
844
|
+
|
|
845
|
+
case 'startup':
|
|
846
|
+
nice_type = 'Schedule';
|
|
847
|
+
alt_type = 'System';
|
|
848
|
+
nice_desc = 'Run at Startup';
|
|
849
|
+
short_desc = "Run at Startup";
|
|
850
|
+
break;
|
|
851
|
+
|
|
852
|
+
case 'single':
|
|
853
|
+
nice_type = 'Schedule';
|
|
854
|
+
alt_type = 'Single Shot';
|
|
855
|
+
short_desc = summarize_event_timing(item);
|
|
856
|
+
nice_desc = 'Single Shot: ' + short_desc;
|
|
857
|
+
break;
|
|
858
|
+
|
|
859
|
+
case 'manual':
|
|
860
|
+
nice_type = 'On-Demand';
|
|
861
|
+
nice_desc = 'Manual Run';
|
|
862
|
+
short_desc = "Manual Run";
|
|
863
|
+
break;
|
|
864
|
+
|
|
865
|
+
case 'magic':
|
|
866
|
+
nice_type = 'On-Demand';
|
|
867
|
+
nice_desc = 'Magic Link';
|
|
868
|
+
short_desc = "Magic Link";
|
|
869
|
+
break;
|
|
870
|
+
|
|
871
|
+
case 'keyboard':
|
|
872
|
+
nice_type = 'On-Demand';
|
|
873
|
+
alt_type = 'Keyboard';
|
|
874
|
+
nice_desc = 'Keyboard: [' + (item.keys || []).join('], [') + ']';
|
|
875
|
+
short_desc = '[' + (item.keys || []).join('], [') + ']';
|
|
876
|
+
break;
|
|
877
|
+
|
|
878
|
+
case 'catchup':
|
|
879
|
+
nice_type = alt_type = 'Modifier';
|
|
880
|
+
nice_desc = 'Catch-Up';
|
|
881
|
+
short_desc = "Catch-Up";
|
|
882
|
+
break;
|
|
883
|
+
|
|
884
|
+
case 'nth':
|
|
885
|
+
nice_type = alt_type = 'Modifier';
|
|
886
|
+
nice_desc = 'Run Every ' + format_ordinal(item.every);
|
|
887
|
+
short_desc = "Every " + format_ordinal(item.every);
|
|
888
|
+
break;
|
|
889
|
+
|
|
890
|
+
case 'range':
|
|
891
|
+
nice_type = 'Modifier';
|
|
892
|
+
alt_type = 'Range';
|
|
893
|
+
short_desc = (item.start && item.end && (item.end > item.start)) ? Tools.getTextFromSeconds( item.end - item.start, true, true ) : this.summarizeTimingRange(item);
|
|
894
|
+
nice_desc = 'Range: ' + this.summarizeTimingRange(item);
|
|
895
|
+
break;
|
|
896
|
+
|
|
897
|
+
case 'blackout':
|
|
898
|
+
nice_type = 'Modifier';
|
|
899
|
+
alt_type = 'Blackout';
|
|
900
|
+
short_desc = (item.start && item.end && (item.end > item.start)) ? Tools.getTextFromSeconds( item.end - item.start, true, true ) : this.summarizeTimingRange(item);
|
|
901
|
+
nice_desc = 'Blackout: ' + this.summarizeTimingRange(item);
|
|
902
|
+
break;
|
|
903
|
+
|
|
904
|
+
case 'delay':
|
|
905
|
+
nice_type = 'Modifier';
|
|
906
|
+
alt_type = 'Delay';
|
|
907
|
+
short_desc = Tools.getTextFromSeconds(item.duration || 0, false, true);
|
|
908
|
+
nice_desc = 'Delay: ' + short_desc;
|
|
909
|
+
break;
|
|
910
|
+
|
|
911
|
+
case 'precision':
|
|
912
|
+
nice_type = 'Precision';
|
|
913
|
+
alt_type = 'Precision';
|
|
914
|
+
short_desc = 'On the minute';
|
|
915
|
+
if (item.seconds && item.seconds.length) short_desc = item.seconds.map( sec => ':' + zeroPad(sec, 2) ).join(', ');
|
|
916
|
+
nice_desc = 'Seconds: ' + short_desc;
|
|
917
|
+
break;
|
|
918
|
+
|
|
919
|
+
case 'quiet':
|
|
920
|
+
nice_type = 'Modifier';
|
|
921
|
+
alt_type = 'Quiet';
|
|
922
|
+
short_desc = '';
|
|
923
|
+
nice_desc = '';
|
|
924
|
+
if (item.invisible) {
|
|
925
|
+
short_desc += 'Invisible';
|
|
926
|
+
nice_desc += 'Invisible';
|
|
927
|
+
}
|
|
928
|
+
if (item.ephemeral) {
|
|
929
|
+
if (short_desc) short_desc += ', ';
|
|
930
|
+
if (nice_desc) nice_desc += ', ';
|
|
931
|
+
short_desc += 'Ephemeral';
|
|
932
|
+
nice_desc += 'Ephemeral';
|
|
933
|
+
}
|
|
934
|
+
if (!short_desc) short_desc = '(None)';
|
|
935
|
+
if (!nice_desc) nice_desc = '(None)';
|
|
936
|
+
break;
|
|
937
|
+
|
|
938
|
+
case 'plugin':
|
|
939
|
+
nice_type = alt_type = 'Plugin';
|
|
940
|
+
nice_desc = this.getNicePlugin(item.plugin_id);
|
|
941
|
+
var plugin = Tools.findObject( this.plugins, { id: item.plugin_id } ) || { title: item.plugin_id };
|
|
942
|
+
short_desc = plugin.title;
|
|
943
|
+
break;
|
|
944
|
+
} // switch item.type
|
|
945
|
+
|
|
946
|
+
return { nice_type, alt_type, nice_desc, short_desc };
|
|
947
|
+
},
|
|
948
|
+
|
|
949
|
+
summarizeTimingRange(trigger) {
|
|
950
|
+
// summarize date/time range, or single start/end
|
|
951
|
+
var text = '';
|
|
952
|
+
var tz = trigger.timezone || app.config.tz;
|
|
953
|
+
var opts = this.getDateOptions({
|
|
954
|
+
year: 'numeric',
|
|
955
|
+
month: 'short',
|
|
956
|
+
day: 'numeric',
|
|
957
|
+
hour: 'numeric',
|
|
958
|
+
minute: '2-digit',
|
|
959
|
+
timeZone: tz
|
|
960
|
+
});
|
|
961
|
+
var formatter = new Intl.DateTimeFormat(opts.locale, opts);
|
|
962
|
+
|
|
963
|
+
if (trigger.start && trigger.end) {
|
|
964
|
+
// full range
|
|
965
|
+
text = formatter.formatRange( new Date(trigger.start * 1000), new Date(trigger.end * 1000) );
|
|
966
|
+
}
|
|
967
|
+
else if (trigger.start) {
|
|
968
|
+
// start only
|
|
969
|
+
text = "Start on " + formatter.format( new Date(trigger.start * 1000) );
|
|
970
|
+
}
|
|
971
|
+
else if (trigger.end) {
|
|
972
|
+
// end only
|
|
973
|
+
text = "End on " + formatter.format( new Date(trigger.end * 1000) );
|
|
974
|
+
}
|
|
975
|
+
else return "n/a";
|
|
976
|
+
|
|
977
|
+
// show timezone if it differs from user's current
|
|
978
|
+
var ropts = Intl.DateTimeFormat().resolvedOptions();
|
|
979
|
+
var user_tz = ropts.timeZone;
|
|
980
|
+
if (user_tz != tz) text += ' (' + tz + ')';
|
|
981
|
+
|
|
982
|
+
return text;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
}; // exports
|
|
986
|
+
|
|
987
|
+
function zeroPad(value, len) {
|
|
988
|
+
// Pad a number with zeroes to achieve a desired total length (max 10)
|
|
989
|
+
return ('0000000000' + value).slice(0 - len);
|
|
990
|
+
};
|
|
991
|
+
|
|
992
|
+
function get_pretty_int_list(arr, ranges) {
|
|
993
|
+
// compose int array to string using commas + spaces, and
|
|
994
|
+
// the english "and" to group the final two elements.
|
|
995
|
+
// also detect sequences and collapse those into dashed ranges
|
|
996
|
+
if (!arr || !arr.length) return '';
|
|
997
|
+
if (arr.length == 1) return arr[0].toString();
|
|
998
|
+
arr = Tools.copyHash(arr, true).sort( function(a, b) { return a - b; } );
|
|
999
|
+
|
|
1000
|
+
// check for ranges and collapse them
|
|
1001
|
+
if (ranges) {
|
|
1002
|
+
var groups = [];
|
|
1003
|
+
var group = [];
|
|
1004
|
+
for (var idx = 0, len = arr.length; idx < len; idx++) {
|
|
1005
|
+
var elem = arr[idx];
|
|
1006
|
+
if (!group.length || (elem == group[group.length - 1] + 1)) group.push(elem);
|
|
1007
|
+
else { groups.push(group); group = [elem]; }
|
|
1008
|
+
}
|
|
1009
|
+
if (group.length) groups.push(group);
|
|
1010
|
+
arr = [];
|
|
1011
|
+
for (var idx = 0, len = groups.length; idx < len; idx++) {
|
|
1012
|
+
var group = groups[idx];
|
|
1013
|
+
if (group.length == 1) arr.push( group[0] );
|
|
1014
|
+
else if (group.length == 2) {
|
|
1015
|
+
arr.push( group[0] );
|
|
1016
|
+
arr.push( group[1] );
|
|
1017
|
+
}
|
|
1018
|
+
else {
|
|
1019
|
+
arr.push( group[0] + ' - ' + group[group.length - 1] );
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
} // ranges
|
|
1023
|
+
|
|
1024
|
+
if (arr.length == 1) return arr[0].toString();
|
|
1025
|
+
return arr.slice(0, arr.length - 1).join(', ') + ' and ' + arr[ arr.length - 1 ];
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
function get_pretty_str_list(arr) {
|
|
1029
|
+
// compose string array to string using commas + spaces, and
|
|
1030
|
+
// the english "and" to group the final two elements
|
|
1031
|
+
if (!arr || !arr.length) return '';
|
|
1032
|
+
if (arr.length == 1) return arr[0].toString();
|
|
1033
|
+
return arr.slice(0, arr.length - 1).join(', ') + ' and ' + arr[ arr.length - 1 ];
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
function format_ordinal(num) {
|
|
1037
|
+
// keep English ordinal suffixes, but localize digits
|
|
1038
|
+
var date_opts = app.getDateOptions();
|
|
1039
|
+
var num_fmt = new Intl.NumberFormat(date_opts.locale, { useGrouping: false, numberingSystem: date_opts.numberingSystem });
|
|
1040
|
+
var suffix = 'th';
|
|
1041
|
+
if ((num % 100 < 11) || (num % 100 > 13)) {
|
|
1042
|
+
switch (num % 10) {
|
|
1043
|
+
case 1: suffix = 'st'; break;
|
|
1044
|
+
case 2: suffix = 'nd'; break;
|
|
1045
|
+
case 3: suffix = 'rd'; break;
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
return num_fmt.format(num) + suffix;
|
|
1049
|
+
};
|
|
1050
|
+
|
|
1051
|
+
function summarize_event_timings(event) {
|
|
1052
|
+
// summarize all event triggers from event into human-readable string
|
|
1053
|
+
// separate schedule items and options
|
|
1054
|
+
var triggers = event.triggers.filter( function(trigger) { return trigger.enabled; } );
|
|
1055
|
+
var schedules = triggers.filter( function(trigger) { return !!(trigger.type || '').match(/^(schedule|interval|single|startup|magic|keyboard)$/); } );
|
|
1056
|
+
var parts = (schedules.length == 1) ? [summarize_event_timing(schedules[0])] : schedules.map( summarize_event_timing );
|
|
1057
|
+
if (!parts.length) {
|
|
1058
|
+
if (Tools.findObject(triggers, { type: 'manual', enabled: true })) return "On Demand";
|
|
1059
|
+
else return "Disabled";
|
|
1060
|
+
}
|
|
1061
|
+
// var summary = (parts.length == 1) ? parts[0] : (parts.slice(0, parts.length - 1).join(', ') + ', and ' + parts[ parts.length - 1 ]);
|
|
1062
|
+
var summary = parts.join(', ');
|
|
1063
|
+
|
|
1064
|
+
var opts = [];
|
|
1065
|
+
triggers.forEach( function(trigger) {
|
|
1066
|
+
switch (trigger.type) {
|
|
1067
|
+
case 'catchup': opts.push("Catch-Up"); break;
|
|
1068
|
+
case 'nth':
|
|
1069
|
+
if (trigger.every > 1) opts.push("Every " + format_ordinal(trigger.every));
|
|
1070
|
+
break;
|
|
1071
|
+
case 'range': opts.push("Date Range"); break;
|
|
1072
|
+
case 'blackout': opts.push("Blackout"); break;
|
|
1073
|
+
case 'delay': opts.push("Delay"); break;
|
|
1074
|
+
case 'precision': opts.push("Precision"); break;
|
|
1075
|
+
case 'quiet': opts.push("Quiet"); break;
|
|
1076
|
+
case 'plugin':
|
|
1077
|
+
var plugin = Tools.findObject( app.plugins, { id: trigger.plugin_id } );
|
|
1078
|
+
if (plugin) opts.push(plugin.title);
|
|
1079
|
+
else opts.push("Plugin");
|
|
1080
|
+
break;
|
|
1081
|
+
}
|
|
1082
|
+
} );
|
|
1083
|
+
var unique_opts = [...new Set(opts)];
|
|
1084
|
+
if (unique_opts.length) summary += ' (' + unique_opts.join(', ') + ')';
|
|
1085
|
+
|
|
1086
|
+
return summary;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
function summarize_event_timing(trigger, idx) {
|
|
1090
|
+
// summarize event trigger into human-readable string
|
|
1091
|
+
if (trigger.type == 'startup') return "On Startup";
|
|
1092
|
+
if (trigger.type == 'plugin') return "Plugin";
|
|
1093
|
+
if (trigger.type == 'magic') return "Magic";
|
|
1094
|
+
if (trigger.type == 'keyboard') return "Keyboard";
|
|
1095
|
+
if (trigger.type == 'single') {
|
|
1096
|
+
var text = app.getNiceDateTime(trigger.epoch);
|
|
1097
|
+
return text;
|
|
1098
|
+
} // single shot
|
|
1099
|
+
|
|
1100
|
+
if (trigger.type == 'interval') {
|
|
1101
|
+
return "Every " + Tools.getTextFromSeconds(trigger.duration, false, false);
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
// years
|
|
1105
|
+
var year_str = '';
|
|
1106
|
+
var date_opts = app.getDateOptions({ timeZone: 'UTC' });
|
|
1107
|
+
var locale = date_opts.locale;
|
|
1108
|
+
var lang = (locale || '').split(/\-/)[0];
|
|
1109
|
+
var is_english = (lang == 'en');
|
|
1110
|
+
var numbering = date_opts.numberingSystem;
|
|
1111
|
+
|
|
1112
|
+
function build_formatter(extra) {
|
|
1113
|
+
var opts = Object.assign({}, date_opts, extra);
|
|
1114
|
+
var loc = opts.locale;
|
|
1115
|
+
delete opts.locale;
|
|
1116
|
+
return new Intl.DateTimeFormat(loc, opts);
|
|
1117
|
+
}
|
|
1118
|
+
var month_fmt = build_formatter({ month: 'long' });
|
|
1119
|
+
var wday_fmt = build_formatter({ weekday: 'long' });
|
|
1120
|
+
var hour_fmt = build_formatter({ hour: 'numeric' });
|
|
1121
|
+
var hm_fmt = build_formatter({ hour: 'numeric', minute: '2-digit' });
|
|
1122
|
+
var num_fmt = new Intl.NumberFormat(locale, { useGrouping: false, numberingSystem: numbering });
|
|
1123
|
+
var num2_fmt = new Intl.NumberFormat(locale, { useGrouping: false, numberingSystem: numbering, minimumIntegerDigits: 2 });
|
|
1124
|
+
|
|
1125
|
+
function format_number(num, min_digits) {
|
|
1126
|
+
return (min_digits ? num2_fmt : num_fmt).format(num);
|
|
1127
|
+
}
|
|
1128
|
+
function format_ord(num) {
|
|
1129
|
+
// keep English ordinal suffixes, but localize digits
|
|
1130
|
+
var suffix = 'th';
|
|
1131
|
+
if ((num % 100 < 11) || (num % 100 > 13)) {
|
|
1132
|
+
switch (num % 10) {
|
|
1133
|
+
case 1: suffix = 'st'; break;
|
|
1134
|
+
case 2: suffix = 'nd'; break;
|
|
1135
|
+
case 3: suffix = 'rd'; break;
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
return format_number(num) + suffix;
|
|
1139
|
+
}
|
|
1140
|
+
function format_month_name(num) {
|
|
1141
|
+
return month_fmt.format( new Date(Date.UTC(2020, num - 1, 1)) );
|
|
1142
|
+
}
|
|
1143
|
+
function format_weekday_name(num) {
|
|
1144
|
+
return wday_fmt.format( new Date(Date.UTC(2020, 7, 2 + num)) );
|
|
1145
|
+
}
|
|
1146
|
+
function normalize_time_label(text) {
|
|
1147
|
+
if (!is_english) return text;
|
|
1148
|
+
return text.replace(/\s+/g, '').replace(/am|pm/i, function(m_all) { return m_all.toLowerCase(); });
|
|
1149
|
+
}
|
|
1150
|
+
function format_hour_name(num) {
|
|
1151
|
+
return normalize_time_label( hour_fmt.format( new Date(Date.UTC(2020, 0, 1, num, 0)) ) );
|
|
1152
|
+
}
|
|
1153
|
+
function format_time_name(hour, minute) {
|
|
1154
|
+
return normalize_time_label( hm_fmt.format( new Date(Date.UTC(2020, 0, 1, hour, minute)) ) );
|
|
1155
|
+
}
|
|
1156
|
+
function format_weekday_label(num) {
|
|
1157
|
+
var name = format_weekday_name(num);
|
|
1158
|
+
return is_english ? (name + 's') : name;
|
|
1159
|
+
}
|
|
1160
|
+
function format_month_day(num) {
|
|
1161
|
+
if (num < 0) {
|
|
1162
|
+
if (num == -1) return 'last day';
|
|
1163
|
+
return format_ord(Math.abs(num)) + ' last day';
|
|
1164
|
+
}
|
|
1165
|
+
return format_ord(num);
|
|
1166
|
+
}
|
|
1167
|
+
function format_minute_label(num) {
|
|
1168
|
+
if (num == 0) return 'hour';
|
|
1169
|
+
if (num == 30) return 'half-hour';
|
|
1170
|
+
return ':' + format_number(num, 2);
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
if (trigger.years && trigger.years.length) {
|
|
1174
|
+
year_str = get_pretty_int_list(trigger.years, true).replace(/(\d+)/g, function(m_all, m_g1) {
|
|
1175
|
+
return format_number( parseInt(m_g1) );
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
// months
|
|
1180
|
+
var mon_str = '';
|
|
1181
|
+
if (trigger.months && trigger.months.length) {
|
|
1182
|
+
mon_str = get_pretty_int_list(trigger.months, true).replace(/(\d+)/g, function(m_all, m_g1) {
|
|
1183
|
+
return format_month_name( parseInt(m_g1) );
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
// days
|
|
1188
|
+
var mday_str = '';
|
|
1189
|
+
if (trigger.days && trigger.days.length) {
|
|
1190
|
+
mday_str = get_pretty_int_list(trigger.days, true).replace(/(\-?\d+)/g, function(m_all, m_g1) {
|
|
1191
|
+
return format_month_day( parseInt(m_g1) );
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
// weekdays
|
|
1196
|
+
var wday_str = '';
|
|
1197
|
+
if (trigger.weekdays && trigger.weekdays.length) {
|
|
1198
|
+
var wdays = Tools.copyHash(trigger.weekdays, true).sort( function(a, b) { return a - b; } );
|
|
1199
|
+
if ((wdays.length == 5) && (wdays[0] == 1) && (wdays[4] == 5)) {
|
|
1200
|
+
wday_str = 'weekdays';
|
|
1201
|
+
}
|
|
1202
|
+
else {
|
|
1203
|
+
wday_str = get_pretty_int_list(wdays, true).replace(/(\d+)/g, function(m_all, m_g1) {
|
|
1204
|
+
return format_weekday_label( parseInt(m_g1) );
|
|
1205
|
+
});
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
// hours
|
|
1210
|
+
var hour_str = '';
|
|
1211
|
+
if (trigger.hours && trigger.hours.length) {
|
|
1212
|
+
hour_str = get_pretty_int_list(trigger.hours, true).replace(/(\d+)/g, function(m_all, m_g1) {
|
|
1213
|
+
return format_hour_name( parseInt(m_g1) );
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
// minutes
|
|
1218
|
+
var min_str = '';
|
|
1219
|
+
if (trigger.minutes && trigger.minutes.length) {
|
|
1220
|
+
var mins = Tools.copyHash(trigger.minutes, true).sort( function(a, b) { return a - b; } );
|
|
1221
|
+
min_str = get_pretty_str_list( mins.map( function(min) { return format_minute_label(min); } ) );
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
// construct final string
|
|
1225
|
+
var groups = [];
|
|
1226
|
+
var mday_compressed = false;
|
|
1227
|
+
|
|
1228
|
+
if (year_str) {
|
|
1229
|
+
groups.push( 'in ' + year_str );
|
|
1230
|
+
if (mon_str) groups.push( mon_str );
|
|
1231
|
+
}
|
|
1232
|
+
else if (mon_str) {
|
|
1233
|
+
// compress single month + single day
|
|
1234
|
+
if (trigger.months && trigger.months.length == 1 && trigger.days && trigger.days.length == 1) {
|
|
1235
|
+
groups.push( 'on ' + mon_str + ' ' + mday_str );
|
|
1236
|
+
mday_compressed = true;
|
|
1237
|
+
}
|
|
1238
|
+
else {
|
|
1239
|
+
groups.push( 'in ' + mon_str );
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
if (mday_str && !mday_compressed) {
|
|
1244
|
+
if (mon_str || wday_str) groups.push( 'on the ' + mday_str );
|
|
1245
|
+
else groups.push( 'monthly on the ' + mday_str );
|
|
1246
|
+
}
|
|
1247
|
+
if (wday_str) groups.push( 'on ' + wday_str );
|
|
1248
|
+
|
|
1249
|
+
// compress single hour + single minute
|
|
1250
|
+
if (trigger.hours && trigger.hours.length == 1 && trigger.minutes && trigger.minutes.length == 1) {
|
|
1251
|
+
var new_str = format_time_name(trigger.hours[0], trigger.minutes[0]);
|
|
1252
|
+
|
|
1253
|
+
if (mday_str || wday_str) groups.push( 'at ' + new_str );
|
|
1254
|
+
else groups.push( 'daily at ' + new_str );
|
|
1255
|
+
}
|
|
1256
|
+
else {
|
|
1257
|
+
var min_added = false;
|
|
1258
|
+
if (hour_str) {
|
|
1259
|
+
if (mday_str || wday_str) groups.push( 'at ' + hour_str );
|
|
1260
|
+
else groups.push( 'daily at ' + hour_str );
|
|
1261
|
+
}
|
|
1262
|
+
else {
|
|
1263
|
+
// check for repeating minute pattern
|
|
1264
|
+
if (trigger.minutes && trigger.minutes.length) {
|
|
1265
|
+
var interval = detect_num_interval( trigger.minutes, 60 );
|
|
1266
|
+
if (interval) {
|
|
1267
|
+
var new_str = 'every ' + interval + ' minutes';
|
|
1268
|
+
if (trigger.minutes[0] > 0) {
|
|
1269
|
+
new_str += ' starting on the :' + format_number(trigger.minutes[0], 2);
|
|
1270
|
+
}
|
|
1271
|
+
groups.push( new_str );
|
|
1272
|
+
min_added = true;
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
if (!min_added) {
|
|
1277
|
+
if (min_str) groups.push( 'hourly' );
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
if (!min_added) {
|
|
1282
|
+
if (min_str) groups.push( 'on the ' + min_str );
|
|
1283
|
+
else groups.push( 'every minute' );
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
var text = (typeof(idx) != 'undefined') ? groups.join(' ') : groups.join(', ');
|
|
1288
|
+
var output = text;
|
|
1289
|
+
if (!idx) output = text.substring(0, 1).toUpperCase() + text.substring(1, text.length);
|
|
1290
|
+
var timing_tz = trigger.timezone || app.config.tz;
|
|
1291
|
+
var user_tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
1292
|
+
if (timing_tz && user_tz && (timing_tz != user_tz)) output += ' (' + timing_tz + ')';
|
|
1293
|
+
|
|
1294
|
+
return output;
|
|
1295
|
+
};
|
|
1296
|
+
|
|
1297
|
+
function detect_num_interval(arr, max) {
|
|
1298
|
+
// detect interval between array elements, return if found
|
|
1299
|
+
// all elements must have same interval between them
|
|
1300
|
+
if (arr.length < 2) return false;
|
|
1301
|
+
// if (arr[0] > 0) return false;
|
|
1302
|
+
|
|
1303
|
+
var interval = arr[1] - arr[0];
|
|
1304
|
+
for (var idx = 1, len = arr.length; idx < len; idx++) {
|
|
1305
|
+
var temp = arr[idx] - arr[idx - 1];
|
|
1306
|
+
if (temp != interval) return false;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
// if max is provided, final element + interval must equal max
|
|
1310
|
+
// if (max && (arr[arr.length - 1] + interval != max)) return false;
|
|
1311
|
+
if (max && ((arr[arr.length - 1] + interval) % max != arr[0])) return false;
|
|
1312
|
+
|
|
1313
|
+
return interval;
|
|
1314
|
+
};
|