]> git.proxmox.com Git - proxmox-widget-toolkit.git/blame - src/node/Tasks.js
bump version to 4.2.3
[proxmox-widget-toolkit.git] / src / node / Tasks.js
CommitLineData
09d64465
DM
1Ext.define('Proxmox.node.Tasks', {
2 extend: 'Ext.grid.GridPanel',
3
9e059d56
DC
4 alias: 'widget.proxmoxNodeTasks',
5
09d64465 6 stateful: true,
9e059d56
DC
7 stateId: 'pve-grid-node-tasks',
8
09d64465
DM
9 loadMask: true,
10 sortableColumns: false,
09d64465 11
6fc91fc6
TL
12 // set extra filter components, must have a 'name' property for the parameter, and must
13 // trigger a 'change' event if the value is 'undefined', it will not be sent to the api
9e059d56 14 extraFilter: [],
09d64465 15
09d64465 16
6fc91fc6
TL
17 // fixed filters which cannot be changed after instantiation, for example:
18 // { vmid: 100 }
9e059d56
DC
19 preFilter: {},
20
21 controller: {
22 xclass: 'Ext.app.ViewController',
23
24 showTaskLog: function() {
25 let me = this;
26 let selection = me.getView().getSelection();
27 if (selection.length < 1) {
28 return;
29 }
09d64465 30
9e059d56
DC
31 let rec = selection[0];
32
33 Ext.create('Proxmox.window.TaskViewer', {
34 upid: rec.data.upid,
35 endtime: rec.data.endtime,
36 }).show();
37 },
38
a86b1f26 39 updateLayout: function(store, records, success, operation) {
9e059d56 40 let me = this;
a86b1f26
DC
41 let view = me.getView().getView(); // the table view, not the whole grid
42 Proxmox.Utils.setErrorMask(view, false);
6fc91fc6
TL
43 // update the scrollbar on every store load since the total count might be different.
44 // the buffered grid plugin does this only on (user) scrolling itself and even reduces
45 // the scrollheight again when scrolling up
9e059d56 46 me.getView().updateLayout();
a86b1f26
DC
47
48 if (!success) {
49 Proxmox.Utils.setErrorMask(view, Proxmox.Utils.getResponseErrorMessage(operation.getError()));
50 }
9e059d56 51 },
09d64465 52
12a6f682
DC
53 refresh: function() {
54 let me = this;
55 let view = me.getView();
56
57 let selection = view.getSelection();
58 let store = me.getViewModel().get('bufferedstore');
59 if (selection && selection.length > 0) {
60 // deselect if selection is not there anymore
61 if (!store.contains(selection[0])) {
62 view.setSelection(undefined);
63 }
64 }
65 },
66
9e059d56
DC
67 sinceChange: function(field, newval) {
68 let me = this;
69 let vm = me.getViewModel();
09d64465 70
9e059d56
DC
71 vm.set('since', newval);
72 },
09d64465 73
9e059d56
DC
74 untilChange: function(field, newval, oldval) {
75 let me = this;
76 let vm = me.getViewModel();
77
78 vm.set('until', newval);
79 },
80
81 reload: function() {
82 let me = this;
83 let view = me.getView();
84 view.getStore().load();
85 },
86
87 showFilter: function(btn, pressed) {
88 let me = this;
89 let vm = me.getViewModel();
90 vm.set('showFilter', pressed);
91 },
abde5f2b
DC
92
93 clearFilter: function() {
94 let me = this;
95 me.lookup('filtertoolbar').query('field').forEach((field) => {
96 field.setValue(undefined);
97 });
98 },
9e059d56 99 },
09d64465 100
9e059d56
DC
101 listeners: {
102 itemdblclick: 'showTaskLog',
103 },
104
105 viewModel: {
106 data: {
107 typefilter: '',
108 statusfilter: '',
9e059d56
DC
109 showFilter: false,
110 extraFilter: {},
111 since: null,
112 until: null,
113 },
114
115 formulas: {
116 filterIcon: (get) => 'fa fa-filter' + (get('showFilter') ? ' info-blue' : ''),
117 extraParams: function(get) {
118 let me = this;
119 let params = {};
120 if (get('typefilter')) {
121 params.typefilter = get('typefilter');
122 }
123 if (get('statusfilter')) {
124 params.statusfilter = get('statusfilter');
125 }
9e059d56
DC
126
127 if (get('extraFilter')) {
128 let extraFilter = get('extraFilter');
129 for (const [name, value] of Object.entries(extraFilter)) {
130 if (value !== undefined && value !== null && value !== "") {
131 params[name] = value;
b1d446d0 132 }
09d64465 133 }
9e059d56
DC
134 }
135
136 if (get('since')) {
137 params.since = get('since').valueOf()/1000;
138 }
139
140 if (get('until')) {
141 let until = new Date(get('until').getTime()); // copy object
142 until.setDate(until.getDate() + 1); // end of the day
143 params.until = until.valueOf()/1000;
144 }
145
146 me.getView().getStore().load();
147
148 return params;
149 },
abde5f2b
DC
150 filterCount: function(get) {
151 let count = 0;
152 if (get('typefilter')) {
153 count++;
154 }
155 let status = get('statusfilter');
156 if ((Ext.isArray(status) && status.length > 0) ||
157 (!Ext.isArray(status) && status)) {
158 count++;
159 }
160 if (get('since')) {
161 count++;
162 }
163 if (get('until')) {
164 count++;
165 }
166
167 if (get('extraFilter')) {
64c1d619 168 let preFilter = get('preFilter') || {};
abde5f2b 169 let extraFilter = get('extraFilter');
64c1d619
DC
170 for (const [name, value] of Object.entries(extraFilter)) {
171 if (value !== undefined && value !== null && value !== "" &&
04070ce5
TL
172 preFilter[name] === undefined
173 ) {
abde5f2b
DC
174 count++;
175 }
176 }
177 }
178
179 return count;
180 },
181 clearFilterText: function(get) {
182 let count = get('filterCount');
183 let fieldMsg = '';
184 if (count > 1) {
185 fieldMsg = ` (${count} ${gettext('Fields')})`;
186 } else if (count > 0) {
187 fieldMsg = ` (1 ${gettext('Field')})`;
188 }
189 return gettext('Clear Filter') + fieldMsg;
190 },
9e059d56
DC
191 },
192
193 stores: {
194 bufferedstore: {
195 type: 'buffered',
196 pageSize: 500,
197 autoLoad: true,
198 remoteFilter: true,
199 model: 'proxmox-tasks',
200 proxy: {
201 type: 'proxmox',
202 startParam: 'start',
203 limitParam: 'limit',
204 extraParams: '{extraParams}',
0dbcfb8c 205 url: '{url}',
9e059d56
DC
206 },
207 listeners: {
208 prefetch: 'updateLayout',
12a6f682 209 refresh: 'refresh',
122a2079 210 },
09d64465 211 },
9e059d56
DC
212 },
213 },
214
215 bind: {
216 store: '{bufferedstore}',
217 },
218
219 dockedItems: [
220 {
221 xtype: 'toolbar',
222 items: [
f138d11f 223 {
9e059d56
DC
224 xtype: 'proxmoxButton',
225 text: gettext('View'),
226 iconCls: 'fa fa-window-restore',
227 disabled: true,
228 handler: 'showTaskLog',
229 },
230 {
231 xtype: 'button',
232 text: gettext('Reload'),
233 iconCls: 'fa fa-refresh',
234 handler: 'reload',
f138d11f
TL
235 },
236 '->',
abde5f2b
DC
237 {
238 xtype: 'button',
239 bind: {
240 text: '{clearFilterText}',
241 disabled: '{!filterCount}',
242 },
243 text: gettext('Clear Filter'),
244 enabled: false,
245 handler: 'clearFilter',
246 },
09d64465 247 {
9e059d56
DC
248 xtype: 'button',
249 enableToggle: true,
250 bind: {
251 iconCls: '{filterIcon}',
252 },
253 text: gettext('Filter'),
254 stateful: true,
255 stateId: 'task-showfilter',
256 stateEvents: ['toggle'],
257 applyState: function(state) {
258 if (state.pressed !== undefined) {
259 this.setPressed(state.pressed);
260 }
261 },
262 getState: function() {
263 return {
264 pressed: this.pressed,
265 };
122a2079 266 },
09d64465 267 listeners: {
9e059d56 268 toggle: 'showFilter',
122a2079 269 },
9e059d56 270 },
09d64465 271 ],
9e059d56
DC
272 },
273 {
274 xtype: 'toolbar',
275 dock: 'top',
276 reference: 'filtertoolbar',
277 layout: {
278 type: 'hbox',
279 align: 'top',
280 },
281 bind: {
282 hidden: '{!showFilter}',
283 },
284 items: [
09d64465 285 {
9e059d56
DC
286 xtype: 'container',
287 padding: 10,
288 layout: {
289 type: 'vbox',
290 align: 'stretch',
122a2079 291 },
9e059d56
DC
292 defaults: {
293 labelWidth: 80,
122a2079 294 },
9e059d56
DC
295 // cannot bind the values directly, as it then changes also
296 // on blur, causing wrong reloads of the store
297 items: [
298 {
299 xtype: 'datefield',
300 fieldLabel: gettext('Since'),
301 format: 'Y-m-d',
302 bind: {
303 maxValue: '{until}',
304 },
305 listeners: {
306 change: 'sinceChange',
307 },
308 },
309 {
310 xtype: 'datefield',
311 fieldLabel: gettext('Until'),
312 format: 'Y-m-d',
313 bind: {
314 minValue: '{since}',
315 },
316 listeners: {
317 change: 'untilChange',
318 },
319 },
320 ],
09d64465 321 },
54dc3ab4 322 {
9e059d56
DC
323 xtype: 'container',
324 padding: 10,
325 layout: {
326 type: 'vbox',
327 align: 'stretch',
54dc3ab4 328 },
9e059d56
DC
329 defaults: {
330 labelWidth: 80,
331 },
332 items: [
333 {
334 xtype: 'pmxTaskTypeSelector',
335 fieldLabel: gettext('Task Type'),
336 emptyText: gettext('All'),
337 bind: {
338 value: '{typefilter}',
339 },
340 },
341 {
342 xtype: 'combobox',
343 fieldLabel: gettext('Task Result'),
344 emptyText: gettext('All'),
345 multiSelect: true,
346 store: [
347 ['ok', gettext('OK')],
348 ['unknown', Proxmox.Utils.unknownText],
349 ['warning', gettext('Warnings')],
350 ['error', gettext('Errors')],
351 ],
352 bind: {
353 value: '{statusfilter}',
354 },
355 },
356 ],
54dc3ab4 357 },
9e059d56
DC
358 ],
359 },
360 ],
361
362 viewConfig: {
363 trackOver: false,
364 stripeRows: false, // does not work with getRowClass()
365 emptyText: gettext('No Tasks found'),
366
367 getRowClass: function(record, index) {
368 let status = record.get('status');
369
370 if (status) {
371 let parsed = Proxmox.Utils.parse_task_status(status);
372 if (parsed === 'error') {
373 return "proxmox-invalid-row";
374 } else if (parsed === 'warning') {
375 return "proxmox-warning-row";
376 }
377 }
378 return '';
379 },
380 },
381
382 columns: [
383 {
384 header: gettext("Start Time"),
385 dataIndex: 'starttime',
386 width: 130,
387 renderer: function(value) {
388 return Ext.Date.format(value, "M d H:i:s");
389 },
390 },
391 {
392 header: gettext("End Time"),
393 dataIndex: 'endtime',
394 width: 130,
395 renderer: function(value, metaData, record) {
396 if (!value) {
397 metaData.tdCls = "x-grid-row-loading";
398 return '';
399 }
400 return Ext.Date.format(value, "M d H:i:s");
401 },
402 },
403 {
404 header: gettext("Duration"),
405 hidden: true,
406 width: 80,
407 renderer: function(value, metaData, record) {
408 let start = record.data.starttime;
409 if (start) {
410 let end = record.data.endtime || Date.now();
411 let duration = end - start;
412 if (duration > 0) {
413 duration /= 1000;
414 }
415 return Proxmox.Utils.format_duration_human(duration);
416 }
417 return Proxmox.Utils.unknownText;
418 },
419 },
420 {
421 header: gettext("User name"),
422 dataIndex: 'user',
423 width: 150,
424 },
425 {
426 header: gettext("Description"),
427 dataIndex: 'upid',
428 flex: 1,
429 renderer: Proxmox.Utils.render_upid,
430 },
431 {
432 header: gettext("Status"),
433 dataIndex: 'status',
434 width: 200,
435 renderer: function(value, metaData, record) {
436 if (value === undefined && !record.data.endtime) {
437 metaData.tdCls = "x-grid-row-loading";
438 return '';
439 }
440
bfab8fc1 441 return Proxmox.Utils.format_task_status(value);
9e059d56
DC
442 },
443 },
444 ],
445
446 initComponent: function() {
447 const me = this;
448
0dbcfb8c
DC
449 let nodename = me.nodename || 'localhost';
450 let url = me.url || `/api2/json/nodes/${nodename}/tasks`;
451 me.getViewModel().set('url', url);
452
9e059d56
DC
453 let updateExtraFilters = function(name, value) {
454 let vm = me.getViewModel();
455 let extraFilter = Ext.clone(vm.get('extraFilter'));
456 extraFilter[name] = value;
457 vm.set('extraFilter', extraFilter);
458 };
459
460 for (const [name, value] of Object.entries(me.preFilter)) {
461 updateExtraFilters(name, value);
462 }
463
64c1d619
DC
464 me.getViewModel().set('preFilter', me.preFilter);
465
9e059d56
DC
466 me.callParent();
467
468 let addFields = function(items) {
469 me.lookup('filtertoolbar').add({
470 xtype: 'container',
471 padding: 10,
472 layout: {
473 type: 'vbox',
474 align: 'stretch',
09d64465 475 },
9e059d56
DC
476 defaults: {
477 labelWidth: 80,
09d64465 478 },
9e059d56
DC
479 items,
480 });
481 };
b1d446d0 482
9e059d56
DC
483 // start with a userfilter
484 me.extraFilter = [
485 {
486 xtype: 'textfield',
487 fieldLabel: gettext('User name'),
488 changeOptions: {
489 buffer: 500,
122a2079 490 },
9e059d56 491 name: 'userfilter',
122a2079 492 },
9e059d56
DC
493 ...me.extraFilter,
494 ];
495 let items = [];
496 for (const filterTemplate of me.extraFilter) {
497 let filter = Ext.clone(filterTemplate);
09d64465 498
9e059d56
DC
499 filter.listeners = filter.listeners || {};
500 filter.listeners.change = Ext.apply(filter.changeOptions || {}, {
501 fn: function(field, value) {
502 updateExtraFilters(filter.name, value);
503 },
504 });
505
506 items.push(filter);
507 if (items.length === 2) {
508 addFields(items);
509 items = [];
510 }
511 }
512
513 addFields(items);
122a2079 514 },
09d64465 515});