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