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