]> git.proxmox.com Git - proxmox-widget-toolkit.git/blob - src/node/Tasks.js
dfe93dc3687ec4eb00f21761032c5af41672d1e4
[proxmox-widget-toolkit.git] / src / node / Tasks.js
1 Ext.define('Proxmox.node.Tasks', {
2 extend: 'Ext.grid.GridPanel',
3
4 alias: 'widget.proxmoxNodeTasks',
5
6 stateful: true,
7 stateId: 'pve-grid-node-tasks',
8
9 loadMask: true,
10 sortableColumns: false,
11
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
14 extraFilter: [],
15
16
17 // fixed filters which cannot be changed after instantiation, for example:
18 // { vmid: 100 }
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 }
30
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
39 updateLayout: function(store, records, success, operation) {
40 let me = this;
41 let view = me.getView().getView(); // the table view, not the whole grid
42 Proxmox.Utils.setErrorMask(view, false);
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
46 me.getView().updateLayout();
47
48 if (!success) {
49 Proxmox.Utils.setErrorMask(view, Proxmox.Utils.getResponseErrorMessage(operation.getError()));
50 }
51 },
52
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
67 sinceChange: function(field, newval) {
68 let me = this;
69 let vm = me.getViewModel();
70
71 vm.set('since', newval);
72 },
73
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 },
92 },
93
94 listeners: {
95 itemdblclick: 'showTaskLog',
96 },
97
98 viewModel: {
99 data: {
100 typefilter: '',
101 statusfilter: '',
102 showFilter: false,
103 extraFilter: {},
104 since: null,
105 until: null,
106 },
107
108 formulas: {
109 filterIcon: (get) => 'fa fa-filter' + (get('showFilter') ? ' info-blue' : ''),
110 extraParams: function(get) {
111 let me = this;
112 let params = {};
113 if (get('typefilter')) {
114 params.typefilter = get('typefilter');
115 }
116 if (get('statusfilter')) {
117 params.statusfilter = get('statusfilter');
118 }
119
120 if (get('extraFilter')) {
121 let extraFilter = get('extraFilter');
122 for (const [name, value] of Object.entries(extraFilter)) {
123 if (value !== undefined && value !== null && value !== "") {
124 params[name] = value;
125 }
126 }
127 }
128
129 if (get('since')) {
130 params.since = get('since').valueOf()/1000;
131 }
132
133 if (get('until')) {
134 let until = new Date(get('until').getTime()); // copy object
135 until.setDate(until.getDate() + 1); // end of the day
136 params.until = until.valueOf()/1000;
137 }
138
139 me.getView().getStore().load();
140
141 return params;
142 },
143 },
144
145 stores: {
146 bufferedstore: {
147 type: 'buffered',
148 pageSize: 500,
149 autoLoad: true,
150 remoteFilter: true,
151 model: 'proxmox-tasks',
152 proxy: {
153 type: 'proxmox',
154 startParam: 'start',
155 limitParam: 'limit',
156 extraParams: '{extraParams}',
157 url: "/api2/json/nodes/localhost/tasks",
158 },
159 listeners: {
160 prefetch: 'updateLayout',
161 refresh: 'refresh',
162 },
163 },
164 },
165 },
166
167 bind: {
168 store: '{bufferedstore}',
169 },
170
171 dockedItems: [
172 {
173 xtype: 'toolbar',
174 items: [
175 {
176 xtype: 'proxmoxButton',
177 text: gettext('View'),
178 iconCls: 'fa fa-window-restore',
179 disabled: true,
180 handler: 'showTaskLog',
181 },
182 {
183 xtype: 'button',
184 text: gettext('Reload'),
185 iconCls: 'fa fa-refresh',
186 handler: 'reload',
187 },
188 '->',
189 {
190 xtype: 'button',
191 enableToggle: true,
192 bind: {
193 iconCls: '{filterIcon}',
194 },
195 text: gettext('Filter'),
196 stateful: true,
197 stateId: 'task-showfilter',
198 stateEvents: ['toggle'],
199 applyState: function(state) {
200 if (state.pressed !== undefined) {
201 this.setPressed(state.pressed);
202 }
203 },
204 getState: function() {
205 return {
206 pressed: this.pressed,
207 };
208 },
209 listeners: {
210 toggle: 'showFilter',
211 },
212 },
213 ],
214 },
215 {
216 xtype: 'toolbar',
217 dock: 'top',
218 reference: 'filtertoolbar',
219 layout: {
220 type: 'hbox',
221 align: 'top',
222 },
223 bind: {
224 hidden: '{!showFilter}',
225 },
226 items: [
227 {
228 xtype: 'container',
229 padding: 10,
230 layout: {
231 type: 'vbox',
232 align: 'stretch',
233 },
234 defaults: {
235 labelWidth: 80,
236 },
237 // cannot bind the values directly, as it then changes also
238 // on blur, causing wrong reloads of the store
239 items: [
240 {
241 xtype: 'datefield',
242 fieldLabel: gettext('Since'),
243 format: 'Y-m-d',
244 bind: {
245 maxValue: '{until}',
246 },
247 listeners: {
248 change: 'sinceChange',
249 },
250 },
251 {
252 xtype: 'datefield',
253 fieldLabel: gettext('Until'),
254 format: 'Y-m-d',
255 bind: {
256 minValue: '{since}',
257 },
258 listeners: {
259 change: 'untilChange',
260 },
261 },
262 ],
263 },
264 {
265 xtype: 'container',
266 padding: 10,
267 layout: {
268 type: 'vbox',
269 align: 'stretch',
270 },
271 defaults: {
272 labelWidth: 80,
273 },
274 items: [
275 {
276 xtype: 'pmxTaskTypeSelector',
277 fieldLabel: gettext('Task Type'),
278 emptyText: gettext('All'),
279 bind: {
280 value: '{typefilter}',
281 },
282 },
283 {
284 xtype: 'combobox',
285 fieldLabel: gettext('Task Result'),
286 emptyText: gettext('All'),
287 multiSelect: true,
288 store: [
289 ['ok', gettext('OK')],
290 ['unknown', Proxmox.Utils.unknownText],
291 ['warning', gettext('Warnings')],
292 ['error', gettext('Errors')],
293 ],
294 bind: {
295 value: '{statusfilter}',
296 },
297 },
298 ],
299 },
300 ],
301 },
302 ],
303
304 viewConfig: {
305 trackOver: false,
306 stripeRows: false, // does not work with getRowClass()
307 emptyText: gettext('No Tasks found'),
308
309 getRowClass: function(record, index) {
310 let status = record.get('status');
311
312 if (status) {
313 let parsed = Proxmox.Utils.parse_task_status(status);
314 if (parsed === 'error') {
315 return "proxmox-invalid-row";
316 } else if (parsed === 'warning') {
317 return "proxmox-warning-row";
318 }
319 }
320 return '';
321 },
322 },
323
324 columns: [
325 {
326 header: gettext("Start Time"),
327 dataIndex: 'starttime',
328 width: 130,
329 renderer: function(value) {
330 return Ext.Date.format(value, "M d H:i:s");
331 },
332 },
333 {
334 header: gettext("End Time"),
335 dataIndex: 'endtime',
336 width: 130,
337 renderer: function(value, metaData, record) {
338 if (!value) {
339 metaData.tdCls = "x-grid-row-loading";
340 return '';
341 }
342 return Ext.Date.format(value, "M d H:i:s");
343 },
344 },
345 {
346 header: gettext("Duration"),
347 hidden: true,
348 width: 80,
349 renderer: function(value, metaData, record) {
350 let start = record.data.starttime;
351 if (start) {
352 let end = record.data.endtime || Date.now();
353 let duration = end - start;
354 if (duration > 0) {
355 duration /= 1000;
356 }
357 return Proxmox.Utils.format_duration_human(duration);
358 }
359 return Proxmox.Utils.unknownText;
360 },
361 },
362 {
363 header: gettext("User name"),
364 dataIndex: 'user',
365 width: 150,
366 },
367 {
368 header: gettext("Description"),
369 dataIndex: 'upid',
370 flex: 1,
371 renderer: Proxmox.Utils.render_upid,
372 },
373 {
374 header: gettext("Status"),
375 dataIndex: 'status',
376 width: 200,
377 renderer: function(value, metaData, record) {
378 if (value === undefined && !record.data.endtime) {
379 metaData.tdCls = "x-grid-row-loading";
380 return '';
381 }
382
383 let parsed = Proxmox.Utils.parse_task_status(value);
384 switch (parsed) {
385 case 'unknown': return Proxmox.Utils.unknownText;
386 case 'error': return Proxmox.Utils.errorText + ': ' + value;
387 case 'ok': // fall-through
388 case 'warning': // fall-through
389 default: return value;
390 }
391 },
392 },
393 ],
394
395 initComponent: function() {
396 const me = this;
397
398 let updateExtraFilters = function(name, value) {
399 let vm = me.getViewModel();
400 let extraFilter = Ext.clone(vm.get('extraFilter'));
401 extraFilter[name] = value;
402 vm.set('extraFilter', extraFilter);
403 };
404
405 for (const [name, value] of Object.entries(me.preFilter)) {
406 updateExtraFilters(name, value);
407 }
408
409 me.callParent();
410
411 let addFields = function(items) {
412 me.lookup('filtertoolbar').add({
413 xtype: 'container',
414 padding: 10,
415 layout: {
416 type: 'vbox',
417 align: 'stretch',
418 },
419 defaults: {
420 labelWidth: 80,
421 },
422 items,
423 });
424 };
425
426 // start with a userfilter
427 me.extraFilter = [
428 {
429 xtype: 'textfield',
430 fieldLabel: gettext('User name'),
431 changeOptions: {
432 buffer: 500,
433 },
434 name: 'userfilter',
435 },
436 ...me.extraFilter,
437 ];
438 let items = [];
439 for (const filterTemplate of me.extraFilter) {
440 let filter = Ext.clone(filterTemplate);
441
442 filter.listeners = filter.listeners || {};
443 filter.listeners.change = Ext.apply(filter.changeOptions || {}, {
444 fn: function(field, value) {
445 updateExtraFilters(filter.name, value);
446 },
447 });
448
449 items.push(filter);
450 if (items.length === 2) {
451 addFields(items);
452 items = [];
453 }
454 }
455
456 addFields(items);
457 },
458 });