alchemy-chimera 1.3.0-alpha.3 → 1.3.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/CHANGELOG.md +15 -0
- package/CLAUDE.md +297 -0
- package/assets/scripts/chimera/chimera.js +12 -0
- package/assets/stylesheets/chimera/chimera.scss +308 -0
- package/config/routes.js +38 -0
- package/controller/00-chimera_controller.js +36 -6
- package/controller/chimera_editor_controller.js +53 -9
- package/controller/chimera_settings_controller.js +1 -0
- package/controller/chimera_static_controller.js +163 -2
- package/controller/system_task_controller.js +224 -0
- package/element/chimera_dashboard_button.js +100 -0
- package/element/chimera_run_task_button.js +93 -0
- package/element/chimera_task_monitor.js +672 -0
- package/lib/chimera_config.js +94 -0
- package/lib/toolbar_buttons.js +78 -0
- package/model/00_chimera_model.js +11 -0
- package/model/chimera_dashboard_config_model.js +44 -0
- package/package.json +4 -4
- package/view/chimera/dashboard.hwk +4 -3
- package/view/chimera/editor/task_monitor.hwk +32 -0
- package/view/chimera/sidebar.hwk +0 -0
- package/view/chimera/toolbar/customize_dashboard_button.hwk +26 -0
- package/view/chimera/toolbar/monitor_button.hwk +8 -0
- package/view/chimera/toolbar/reset_dashboard_button.hwk +26 -0
- package/view/chimera/toolbar/run_task_button.hwk +25 -0
- package/view/elements/chimera_task_monitor.hwk +80 -0
- package/view/layouts/chimera_body.hwk +46 -4
|
@@ -0,0 +1,672 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The chimera-task-monitor element
|
|
3
|
+
* Displays live progress of a running task via WebSocket linkup
|
|
4
|
+
*
|
|
5
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
6
|
+
* @since 1.3.0
|
|
7
|
+
* @version 1.3.0
|
|
8
|
+
*/
|
|
9
|
+
const TaskMonitor = Function.inherits('Alchemy.Element.App', 'Alchemy.Element.Chimera', 'TaskMonitor');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Set the custom element prefix to 'chimera'
|
|
13
|
+
*
|
|
14
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
15
|
+
* @since 1.3.0
|
|
16
|
+
* @version 1.3.0
|
|
17
|
+
*/
|
|
18
|
+
TaskMonitor.setStatic('custom_element_prefix', 'chimera');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The template file
|
|
22
|
+
*
|
|
23
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
24
|
+
* @since 1.3.0
|
|
25
|
+
* @version 1.3.0
|
|
26
|
+
*/
|
|
27
|
+
TaskMonitor.setTemplateFile('elements/chimera_task_monitor');
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The task history ID to monitor
|
|
31
|
+
*
|
|
32
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
33
|
+
* @since 1.3.0
|
|
34
|
+
* @version 1.3.0
|
|
35
|
+
*/
|
|
36
|
+
TaskMonitor.setAttribute('task-history-id');
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The history document (passed from server)
|
|
40
|
+
*
|
|
41
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
42
|
+
* @since 1.3.0
|
|
43
|
+
* @version 1.3.0
|
|
44
|
+
*/
|
|
45
|
+
TaskMonitor.setAssignedProperty('history_doc');
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The system task document (passed from server)
|
|
49
|
+
*
|
|
50
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
51
|
+
* @since 1.3.0
|
|
52
|
+
* @version 1.3.0
|
|
53
|
+
*/
|
|
54
|
+
TaskMonitor.setAssignedProperty('system_task');
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get or initialize the current task state
|
|
58
|
+
* (stored as _task_state to persist modifications)
|
|
59
|
+
*
|
|
60
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
61
|
+
* @since 1.3.0
|
|
62
|
+
* @version 1.3.0
|
|
63
|
+
*/
|
|
64
|
+
TaskMonitor.setProperty(function task_state() {
|
|
65
|
+
|
|
66
|
+
// Check if we need to reinitialize (history_doc changed)
|
|
67
|
+
let current_pk = this.history_doc?.$pk;
|
|
68
|
+
if (this._task_state && this._task_state_doc_pk !== current_pk) {
|
|
69
|
+
this._task_state = null; // Invalidate cache
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Return existing state if already initialized
|
|
73
|
+
if (this._task_state) {
|
|
74
|
+
return this._task_state;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Store which history_doc we're initializing from
|
|
78
|
+
this._task_state_doc_pk = current_pk;
|
|
79
|
+
|
|
80
|
+
// Initialize from history_doc on first access
|
|
81
|
+
this._task_state = {
|
|
82
|
+
is_running : this.history_doc?.is_running ?? false,
|
|
83
|
+
percentage : null,
|
|
84
|
+
is_paused : false,
|
|
85
|
+
can_stop : false,
|
|
86
|
+
can_pause : false,
|
|
87
|
+
had_error : this.history_doc?.had_error ?? false,
|
|
88
|
+
started_at : this.history_doc?.started_at,
|
|
89
|
+
ended_at : this.history_doc?.ended_at,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
return this._task_state;
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The element has been added to the DOM
|
|
97
|
+
*
|
|
98
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
99
|
+
* @since 1.3.0
|
|
100
|
+
* @version 1.3.0
|
|
101
|
+
*/
|
|
102
|
+
TaskMonitor.setMethod(function introduced() {
|
|
103
|
+
|
|
104
|
+
if (!Blast.isBrowser) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Set up event listeners for control buttons
|
|
109
|
+
this.onEventSelector('click', '.btn-stop', e => {
|
|
110
|
+
e.preventDefault();
|
|
111
|
+
this.stopTask();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
this.onEventSelector('click', '.btn-pause', e => {
|
|
115
|
+
e.preventDefault();
|
|
116
|
+
this.pauseTask();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
this.onEventSelector('click', '.btn-resume', e => {
|
|
120
|
+
e.preventDefault();
|
|
121
|
+
this.resumeTask();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Initialize display from history_doc (for already-completed tasks)
|
|
125
|
+
this.updateStatus();
|
|
126
|
+
this.updateProgress();
|
|
127
|
+
this.updateControls();
|
|
128
|
+
|
|
129
|
+
// Start the linkup connection
|
|
130
|
+
this.connectLinkup();
|
|
131
|
+
|
|
132
|
+
// Start elapsed time updates
|
|
133
|
+
this.startElapsedTimeUpdates();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Connect to the server via linkup
|
|
138
|
+
*
|
|
139
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
140
|
+
* @since 1.3.0
|
|
141
|
+
* @version 1.3.0
|
|
142
|
+
*/
|
|
143
|
+
TaskMonitor.setMethod(function connectLinkup() {
|
|
144
|
+
|
|
145
|
+
let task_history_id = this.task_history_id;
|
|
146
|
+
|
|
147
|
+
if (!task_history_id) {
|
|
148
|
+
this.showError(this.__('no-task-history-id'));
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Enable websockets
|
|
153
|
+
alchemy.enableWebsockets();
|
|
154
|
+
|
|
155
|
+
// Create the linkup to the taskmonitor route in the chimera section
|
|
156
|
+
// The event name must include the section prefix 'chimera@' because the route
|
|
157
|
+
// is defined on the chimera section (chimera_section.linkup(...))
|
|
158
|
+
try {
|
|
159
|
+
this.linkup = alchemy.linkup('chimera@taskmonitor', {
|
|
160
|
+
task_history_id: task_history_id
|
|
161
|
+
});
|
|
162
|
+
} catch (err) {
|
|
163
|
+
this.showError(this.__('linkup-connection-failed'));
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Handle initial state
|
|
168
|
+
this.linkup.on('state', (data) => {
|
|
169
|
+
this.handleState(data);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Handle progress updates
|
|
173
|
+
this.linkup.on('progress', (data) => {
|
|
174
|
+
this.handleProgress(data);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Handle new reports (logs)
|
|
178
|
+
this.linkup.on('report', (data) => {
|
|
179
|
+
this.handleReport(data);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Handle completion
|
|
183
|
+
this.linkup.on('complete', (data) => {
|
|
184
|
+
this.handleComplete(data);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Handle errors (both protocol errors with data.message and connection errors with err.message)
|
|
188
|
+
this.linkup.on('error', (err) => {
|
|
189
|
+
let error_message = err?.message || err;
|
|
190
|
+
this.showError(error_message);
|
|
191
|
+
|
|
192
|
+
// Also append to log for connection-level errors
|
|
193
|
+
if (err instanceof Error) {
|
|
194
|
+
this.appendLog(this.__('connection-error') + ': ' + error_message, 'error');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Stop elapsed time updates if the task is not running
|
|
198
|
+
if (!this.task_state.is_running) {
|
|
199
|
+
this.stopElapsedTimeUpdates();
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Handle connection close (server disconnect, network issues)
|
|
204
|
+
this.linkup.on('close', () => {
|
|
205
|
+
if (this.task_state.is_running) {
|
|
206
|
+
this.appendLog(this.__('connection-lost'), 'warning');
|
|
207
|
+
}
|
|
208
|
+
this.stopElapsedTimeUpdates();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Handle pause/resume/stop confirmations
|
|
212
|
+
this.linkup.on('paused', () => {
|
|
213
|
+
this.task_state.is_paused = true;
|
|
214
|
+
this.updateControls();
|
|
215
|
+
this.appendLog(this.__('task-paused'), 'info');
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
this.linkup.on('resumed', () => {
|
|
219
|
+
this.task_state.is_paused = false;
|
|
220
|
+
this.updateControls();
|
|
221
|
+
this.appendLog(this.__('task-resumed'), 'info');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
this.linkup.on('stopped', () => {
|
|
225
|
+
this.task_state.is_running = false;
|
|
226
|
+
this.updateControls();
|
|
227
|
+
this.appendLog(this.__('task-stopped-by-user'), 'warning');
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Handle initial state from server
|
|
233
|
+
*
|
|
234
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
235
|
+
* @since 1.3.0
|
|
236
|
+
* @version 1.3.0
|
|
237
|
+
*/
|
|
238
|
+
TaskMonitor.setMethod(function handleState(data) {
|
|
239
|
+
|
|
240
|
+
this.task_state.is_running = data.is_running;
|
|
241
|
+
this.task_state.percentage = data.percentage;
|
|
242
|
+
this.task_state.is_paused = data.is_paused;
|
|
243
|
+
this.task_state.can_stop = data.can_stop;
|
|
244
|
+
this.task_state.can_pause = data.can_pause;
|
|
245
|
+
this.task_state.had_error = data.had_error;
|
|
246
|
+
this.task_state.started_at = data.started_at;
|
|
247
|
+
this.task_state.ended_at = data.ended_at;
|
|
248
|
+
|
|
249
|
+
// Display existing reports
|
|
250
|
+
if (data.reports && data.reports.length) {
|
|
251
|
+
for (let report of data.reports) {
|
|
252
|
+
this.displayReport(report);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
this.updateStatus();
|
|
257
|
+
this.updateProgress();
|
|
258
|
+
this.updateControls();
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Handle progress update
|
|
263
|
+
*
|
|
264
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
265
|
+
* @since 1.3.0
|
|
266
|
+
* @version 1.3.0
|
|
267
|
+
*/
|
|
268
|
+
TaskMonitor.setMethod(function handleProgress(data) {
|
|
269
|
+
|
|
270
|
+
this.task_state.percentage = data.percentage;
|
|
271
|
+
this.task_state.is_paused = data.is_paused;
|
|
272
|
+
|
|
273
|
+
this.updateProgress();
|
|
274
|
+
this.updateControls();
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Handle a new report (log entry)
|
|
279
|
+
*
|
|
280
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
281
|
+
* @since 1.3.0
|
|
282
|
+
* @version 1.3.0
|
|
283
|
+
*/
|
|
284
|
+
TaskMonitor.setMethod(function handleReport(report) {
|
|
285
|
+
this.displayReport(report);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Display a report in the logs
|
|
290
|
+
*
|
|
291
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
292
|
+
* @since 1.3.0
|
|
293
|
+
* @version 1.3.0
|
|
294
|
+
*/
|
|
295
|
+
TaskMonitor.setMethod(function displayReport(report) {
|
|
296
|
+
|
|
297
|
+
if (!report) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
let type_class = 'log-info';
|
|
302
|
+
|
|
303
|
+
if (report.type === 'done') {
|
|
304
|
+
type_class = 'log-success';
|
|
305
|
+
} else if (report.type === 'failed' || report.error) {
|
|
306
|
+
type_class = 'log-error';
|
|
307
|
+
} else if (report.type === 'stopped') {
|
|
308
|
+
type_class = 'log-warning';
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Log the report type/status
|
|
312
|
+
if (report.type) {
|
|
313
|
+
let percentage_str = '';
|
|
314
|
+
if (report.percentage != null) {
|
|
315
|
+
percentage_str = ` (${report.percentage}%)`;
|
|
316
|
+
}
|
|
317
|
+
this.appendLog(`[${report.type}]${percentage_str}`, type_class);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Log any messages
|
|
321
|
+
if (report.logs && report.logs.length) {
|
|
322
|
+
for (let log_entry of report.logs) {
|
|
323
|
+
let message = log_entry.args ? log_entry.args.join(' ') : '';
|
|
324
|
+
this.appendLog(message, 'log-detail');
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Log any errors
|
|
329
|
+
if (report.error) {
|
|
330
|
+
let error_message = report.error.message || report.error.toString();
|
|
331
|
+
this.appendLog(this.__('error') + ': ' + error_message, 'log-error');
|
|
332
|
+
|
|
333
|
+
if (report.error.stack) {
|
|
334
|
+
this.appendLog(report.error.stack, 'log-error log-stack');
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Handle task completion
|
|
341
|
+
*
|
|
342
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
343
|
+
* @since 1.3.0
|
|
344
|
+
* @version 1.3.0
|
|
345
|
+
*/
|
|
346
|
+
TaskMonitor.setMethod(function handleComplete(data) {
|
|
347
|
+
|
|
348
|
+
this.task_state.is_running = false;
|
|
349
|
+
this.task_state.ended_at = data.ended_at;
|
|
350
|
+
this.task_state.had_error = data.had_error;
|
|
351
|
+
|
|
352
|
+
if (data.had_error && data.error_message) {
|
|
353
|
+
this.appendLog(this.__('task-failed') + ': ' + data.error_message, 'log-error');
|
|
354
|
+
} else if (!data.had_error) {
|
|
355
|
+
this.task_state.percentage = 100;
|
|
356
|
+
this.appendLog(this.__('task-completed-successfully'), 'log-success');
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
this.updateStatus();
|
|
360
|
+
this.updateProgress();
|
|
361
|
+
this.updateControls();
|
|
362
|
+
|
|
363
|
+
// Stop the elapsed time updates
|
|
364
|
+
this.stopElapsedTimeUpdates();
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Update the status display
|
|
369
|
+
*
|
|
370
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
371
|
+
* @since 1.3.0
|
|
372
|
+
* @version 1.3.0
|
|
373
|
+
*/
|
|
374
|
+
TaskMonitor.setMethod(function updateStatus() {
|
|
375
|
+
|
|
376
|
+
let status_el = this.querySelector('.task-status');
|
|
377
|
+
|
|
378
|
+
if (!status_el) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
let status_text = '';
|
|
383
|
+
let status_class = '';
|
|
384
|
+
|
|
385
|
+
if (this.task_state.is_running) {
|
|
386
|
+
if (this.task_state.is_paused) {
|
|
387
|
+
status_text = this.__('task-status-paused');
|
|
388
|
+
status_class = 'status-paused';
|
|
389
|
+
} else {
|
|
390
|
+
status_text = this.__('task-status-running');
|
|
391
|
+
status_class = 'status-running';
|
|
392
|
+
}
|
|
393
|
+
} else if (this.task_state.had_error) {
|
|
394
|
+
status_text = this.__('task-status-failed');
|
|
395
|
+
status_class = 'status-error';
|
|
396
|
+
} else if (this.task_state.ended_at) {
|
|
397
|
+
status_text = this.__('task-status-completed');
|
|
398
|
+
status_class = 'status-completed';
|
|
399
|
+
} else {
|
|
400
|
+
status_text = this.__('task-status-pending');
|
|
401
|
+
status_class = 'status-pending';
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
status_el.textContent = status_text;
|
|
405
|
+
status_el.className = 'task-status ' + status_class;
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Update the progress bar
|
|
410
|
+
*
|
|
411
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
412
|
+
* @since 1.3.0
|
|
413
|
+
* @version 1.3.0
|
|
414
|
+
*/
|
|
415
|
+
TaskMonitor.setMethod(function updateProgress() {
|
|
416
|
+
|
|
417
|
+
let progress_bar = this.querySelector('.progress-bar');
|
|
418
|
+
let progress_text = this.querySelector('.progress-text');
|
|
419
|
+
|
|
420
|
+
if (progress_bar) {
|
|
421
|
+
let percentage = this.task_state.percentage ?? 0;
|
|
422
|
+
progress_bar.style.width = percentage + '%';
|
|
423
|
+
|
|
424
|
+
if (this.task_state.had_error) {
|
|
425
|
+
progress_bar.classList.add('error');
|
|
426
|
+
} else if (percentage >= 100) {
|
|
427
|
+
progress_bar.classList.add('complete');
|
|
428
|
+
} else {
|
|
429
|
+
progress_bar.classList.remove('error', 'complete');
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (progress_text) {
|
|
434
|
+
let percentage = this.task_state.percentage;
|
|
435
|
+
if (percentage != null) {
|
|
436
|
+
progress_text.textContent = Math.round(percentage) + '%';
|
|
437
|
+
} else {
|
|
438
|
+
progress_text.textContent = '-';
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Update the control buttons visibility
|
|
445
|
+
*
|
|
446
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
447
|
+
* @since 1.3.0
|
|
448
|
+
* @version 1.3.0
|
|
449
|
+
*/
|
|
450
|
+
TaskMonitor.setMethod(function updateControls() {
|
|
451
|
+
|
|
452
|
+
let stop_btn = this.querySelector('.btn-stop');
|
|
453
|
+
let pause_btn = this.querySelector('.btn-pause');
|
|
454
|
+
let resume_btn = this.querySelector('.btn-resume');
|
|
455
|
+
|
|
456
|
+
if (stop_btn) {
|
|
457
|
+
stop_btn.disabled = !this.task_state.is_running || !this.task_state.can_stop;
|
|
458
|
+
stop_btn.style.display = this.task_state.is_running && this.task_state.can_stop ? '' : 'none';
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if (pause_btn) {
|
|
462
|
+
pause_btn.disabled = !this.task_state.is_running || this.task_state.is_paused || !this.task_state.can_pause;
|
|
463
|
+
pause_btn.style.display = this.task_state.is_running && !this.task_state.is_paused && this.task_state.can_pause ? '' : 'none';
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (resume_btn) {
|
|
467
|
+
resume_btn.disabled = !this.task_state.is_paused;
|
|
468
|
+
resume_btn.style.display = this.task_state.is_paused ? '' : 'none';
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Append a log message to the output
|
|
474
|
+
*
|
|
475
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
476
|
+
* @since 1.3.0
|
|
477
|
+
* @version 1.3.0
|
|
478
|
+
*/
|
|
479
|
+
TaskMonitor.setMethod(function appendLog(message, type_class) {
|
|
480
|
+
|
|
481
|
+
let log_output = this.querySelector('.log-output');
|
|
482
|
+
|
|
483
|
+
if (!log_output) {
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
let log_entry = document.createElement('div');
|
|
488
|
+
log_entry.className = 'log-entry ' + (type_class || '');
|
|
489
|
+
|
|
490
|
+
let timestamp = document.createElement('span');
|
|
491
|
+
timestamp.className = 'log-timestamp';
|
|
492
|
+
timestamp.textContent = new Date().format('H:i:s');
|
|
493
|
+
|
|
494
|
+
let content = document.createElement('span');
|
|
495
|
+
content.className = 'log-content';
|
|
496
|
+
content.textContent = message;
|
|
497
|
+
|
|
498
|
+
log_entry.appendChild(timestamp);
|
|
499
|
+
log_entry.appendChild(content);
|
|
500
|
+
log_output.appendChild(log_entry);
|
|
501
|
+
|
|
502
|
+
// Auto-scroll to bottom
|
|
503
|
+
log_output.scrollTop = log_output.scrollHeight;
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Show an error message
|
|
508
|
+
*
|
|
509
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
510
|
+
* @since 1.3.0
|
|
511
|
+
* @version 1.3.0
|
|
512
|
+
*/
|
|
513
|
+
TaskMonitor.setMethod(function showError(message) {
|
|
514
|
+
|
|
515
|
+
let error_el = this.querySelector('.task-error');
|
|
516
|
+
|
|
517
|
+
if (error_el) {
|
|
518
|
+
error_el.textContent = message;
|
|
519
|
+
error_el.style.display = 'block';
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
this.appendLog(this.__('error') + ': ' + message, 'log-error');
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Send stop command to server
|
|
527
|
+
*
|
|
528
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
529
|
+
* @since 1.3.0
|
|
530
|
+
* @version 1.3.0
|
|
531
|
+
*/
|
|
532
|
+
TaskMonitor.setMethod(function stopTask() {
|
|
533
|
+
if (this.linkup) {
|
|
534
|
+
this.linkup.submit('stop');
|
|
535
|
+
this.appendLog(this.__('sending-stop-command'), 'log-info');
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Send pause command to server
|
|
541
|
+
*
|
|
542
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
543
|
+
* @since 1.3.0
|
|
544
|
+
* @version 1.3.0
|
|
545
|
+
*/
|
|
546
|
+
TaskMonitor.setMethod(function pauseTask() {
|
|
547
|
+
if (this.linkup) {
|
|
548
|
+
this.linkup.submit('pause');
|
|
549
|
+
this.appendLog(this.__('sending-pause-command'), 'log-info');
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Send resume command to server
|
|
555
|
+
*
|
|
556
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
557
|
+
* @since 1.3.0
|
|
558
|
+
* @version 1.3.0
|
|
559
|
+
*/
|
|
560
|
+
TaskMonitor.setMethod(function resumeTask() {
|
|
561
|
+
if (this.linkup) {
|
|
562
|
+
this.linkup.submit('resume');
|
|
563
|
+
this.appendLog(this.__('sending-resume-command'), 'log-info');
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Start updating elapsed time
|
|
569
|
+
*
|
|
570
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
571
|
+
* @since 1.3.0
|
|
572
|
+
* @version 1.3.0
|
|
573
|
+
*/
|
|
574
|
+
TaskMonitor.setMethod(function startElapsedTimeUpdates() {
|
|
575
|
+
|
|
576
|
+
// Clear any existing interval first to prevent race conditions
|
|
577
|
+
this.stopElapsedTimeUpdates();
|
|
578
|
+
|
|
579
|
+
this.updateElapsedTime();
|
|
580
|
+
|
|
581
|
+
this._elapsed_interval = setInterval(() => {
|
|
582
|
+
this.updateElapsedTime();
|
|
583
|
+
}, 1000);
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Stop updating elapsed time
|
|
588
|
+
*
|
|
589
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
590
|
+
* @since 1.3.0
|
|
591
|
+
* @version 1.3.0
|
|
592
|
+
*/
|
|
593
|
+
TaskMonitor.setMethod(function stopElapsedTimeUpdates() {
|
|
594
|
+
if (this._elapsed_interval) {
|
|
595
|
+
clearInterval(this._elapsed_interval);
|
|
596
|
+
this._elapsed_interval = null;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// Do one final update
|
|
600
|
+
this.updateElapsedTime();
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Update the elapsed time display
|
|
605
|
+
*
|
|
606
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
607
|
+
* @since 1.3.0
|
|
608
|
+
* @version 1.3.0
|
|
609
|
+
*/
|
|
610
|
+
TaskMonitor.setMethod(function updateElapsedTime() {
|
|
611
|
+
|
|
612
|
+
let elapsed_el = this.querySelector('.elapsed-time');
|
|
613
|
+
|
|
614
|
+
if (!elapsed_el) {
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
let started_at = this.task_state.started_at || this.history_doc?.started_at;
|
|
619
|
+
let ended_at = this.task_state.ended_at || this.history_doc?.ended_at;
|
|
620
|
+
|
|
621
|
+
if (!started_at) {
|
|
622
|
+
elapsed_el.textContent = '-';
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
let start_time = new Date(started_at).getTime();
|
|
627
|
+
let end_time = ended_at ? new Date(ended_at).getTime() : Date.now();
|
|
628
|
+
let elapsed_ms = end_time - start_time;
|
|
629
|
+
|
|
630
|
+
if (elapsed_ms < 0) {
|
|
631
|
+
elapsed_el.textContent = '-';
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// Format as HH:MM:SS
|
|
636
|
+
let seconds = Math.floor(elapsed_ms / 1000);
|
|
637
|
+
let minutes = Math.floor(seconds / 60);
|
|
638
|
+
let hours = Math.floor(minutes / 60);
|
|
639
|
+
|
|
640
|
+
seconds = seconds % 60;
|
|
641
|
+
minutes = minutes % 60;
|
|
642
|
+
|
|
643
|
+
let parts = [];
|
|
644
|
+
|
|
645
|
+
if (hours > 0) {
|
|
646
|
+
parts.push(String(hours).padStart(2, '0'));
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
parts.push(String(minutes).padStart(2, '0'));
|
|
650
|
+
parts.push(String(seconds).padStart(2, '0'));
|
|
651
|
+
|
|
652
|
+
elapsed_el.textContent = parts.join(':');
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* Clean up when element is removed
|
|
657
|
+
*
|
|
658
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
659
|
+
* @since 1.3.0
|
|
660
|
+
* @version 1.3.0
|
|
661
|
+
*/
|
|
662
|
+
TaskMonitor.setMethod(function removed() {
|
|
663
|
+
|
|
664
|
+
// Destroy the linkup
|
|
665
|
+
if (this.linkup) {
|
|
666
|
+
this.linkup.destroy();
|
|
667
|
+
this.linkup = null;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// Stop elapsed time updates
|
|
671
|
+
this.stopElapsedTimeUpdates();
|
|
672
|
+
});
|