]> git.proxmox.com Git - proxmox-widget-toolkit.git/commitdiff
node/Tasks: merge improvements from PBS and make it more generic
authorDominik Csapak <d.csapak@proxmox.com>
Thu, 24 Jun 2021 07:16:16 +0000 (09:16 +0200)
committerThomas Lamprecht <t.lamprecht@proxmox.com>
Mon, 28 Jun 2021 14:06:49 +0000 (16:06 +0200)
this copies most of the task grid from pbs, but adds handling so that
users can add aribtrary filter fields

the filter fields always present are:
* since
* until
* task type
* task status

other filters fields can be added by giving an 'extraFilter' array
which must contain widget definitions that emit a 'change' event.
this is then used to update the filters for the api call

also you can add a 'preFilter' object, that sets the filter parameter
only once at the beginning

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
src/css/ext6-pmx.css
src/node/Tasks.js

index 7d7cddf6b270b9c8285b60a4869d045c080223ca..8e1980c41248ea3e3b9c4d1759edc7c5cf17cf82 100644 (file)
     color: #FF6C59;
 }
 
+.info-blue {
+    color: #3892d4;
+}
+
 /* reduce chart legend space usage to something more sane */
 .x-legend-item {
        padding: 0.4em 0.8em 0.4em 1.8em;
index 7f20a8a1418962bdc31bc80dd328362b1a95f4f9..049b5277a4aab7b5ad821cdf670c89b6b8e99485 100644 (file)
 Ext.define('Proxmox.node.Tasks', {
     extend: 'Ext.grid.GridPanel',
 
-    alias: ['widget.proxmoxNodeTasks'],
+    alias: 'widget.proxmoxNodeTasks',
+
     stateful: true,
-    stateId: 'grid-node-tasks',
+    stateId: 'pve-grid-node-tasks',
+
     loadMask: true,
     sortableColumns: false,
-    vmidFilter: 0,
 
-    initComponent: function() {
-       let me = this;
+    // set extra filter components,
+    // must have a 'name' property for the parameter,
+    // and must trigger a 'change' event
+    // if the value is 'undefined', it will not be sent to the api
+    extraFilter: [],
 
-       if (!me.nodename) {
-           throw "no node name specified";
-       }
 
-       let store = Ext.create('Ext.data.BufferedStore', {
-           pageSize: 500,
-           autoLoad: true,
-           remoteFilter: true,
-           model: 'proxmox-tasks',
-           proxy: {
-                type: 'proxmox',
-               startParam: 'start',
-               limitParam: 'limit',
-                url: "/api2/json/nodes/" + me.nodename + "/tasks",
-           },
-       });
+    // filters that should only be set once and is not changable
+    // example:
+    // {
+    //    vmid: 100,
+    // }
+    preFilter: {},
+
+    controller: {
+       xclass: 'Ext.app.ViewController',
+
+       showTaskLog: function() {
+           let me = this;
+           let selection = me.getView().getSelection();
+           if (selection.length < 1) {
+               return;
+           }
 
-       store.on('prefetch', function() {
+           let rec = selection[0];
+
+           Ext.create('Proxmox.window.TaskViewer', {
+               upid: rec.data.upid,
+               endtime: rec.data.endtime,
+           }).show();
+       },
+
+       updateLayout: function() {
+           let me = this;
            // we want to update the scrollbar on every store load
            // since the total count might be different
            // the buffered grid plugin does this only on scrolling itself
            // and even reduces the scrollheight again when scrolling up
-           me.updateLayout();
-       });
-
-       let userfilter = '';
-       let filter_errors = 0;
-
-       let updateProxyParams = function() {
-           let params = {
-               errors: filter_errors,
-           };
-           if (userfilter) {
-               params.userfilter = userfilter;
-           }
-           if (me.vmidFilter) {
-               params.vmid = me.vmidFilter;
-           }
-           store.proxy.extraParams = params;
-       };
+           me.getView().updateLayout();
+       },
 
-       updateProxyParams();
+       sinceChange: function(field, newval) {
+           let me = this;
+           let vm = me.getViewModel();
 
-       let reload_task = Ext.create('Ext.util.DelayedTask', function() {
-           updateProxyParams();
-           store.reload();
-       });
+           vm.set('since', newval);
+       },
 
-       let run_task_viewer = function() {
-           let sm = me.getSelectionModel();
-           let rec = sm.getSelection()[0];
-           if (!rec) {
-               return;
-           }
+       untilChange: function(field, newval, oldval) {
+           let me = this;
+           let vm = me.getViewModel();
+
+           vm.set('until', newval);
+       },
+
+       reload: function() {
+           let me = this;
+           let view = me.getView();
+           view.getStore().load();
+       },
+
+       showFilter: function(btn, pressed) {
+           let me = this;
+           let vm = me.getViewModel();
+           vm.set('showFilter', pressed);
+       },
+
+       init: function(view) {
+           let me = this;
+           Proxmox.Utils.monStoreErrors(view, view.getStore(), true);
+       },
+    },
 
-           let win = Ext.create('Proxmox.window.TaskViewer', {
-               upid: rec.data.upid,
-               endtime: rec.data.endtime,
-           });
-           win.show();
-       };
 
-       let view_btn = new Ext.Button({
-           text: gettext('View'),
-           disabled: true,
-           handler: run_task_viewer,
-       });
-
-       Proxmox.Utils.monStoreErrors(me, store, true);
-
-       Ext.apply(me, {
-           store: store,
-           viewConfig: {
-               trackOver: false,
-               stripeRows: false, // does not work with getRowClass()
-
-               getRowClass: function(record, index) {
-                   let status = record.get('status');
-
-                   if (status) {
-                       let parsed = Proxmox.Utils.parse_task_status(status);
-                       if (parsed === 'error') {
-                           return "proxmox-invalid-row";
-                       } else if (parsed === 'warning') {
-                           return "proxmox-warning-row";
+    listeners: {
+       itemdblclick: 'showTaskLog',
+    },
+
+    viewModel: {
+       data: {
+           typefilter: '',
+           statusfilter: '',
+           datastore: '',
+           showFilter: false,
+           extraFilter: {},
+           since: null,
+           until: null,
+       },
+
+       formulas: {
+           filterIcon: (get) => 'fa fa-filter' + (get('showFilter') ? ' info-blue' : ''),
+           extraParams: function(get) {
+               let me = this;
+               let params = {};
+               if (get('typefilter')) {
+                   params.typefilter = get('typefilter');
+               }
+               if (get('statusfilter')) {
+                   params.statusfilter = get('statusfilter');
+               }
+               if (get('datastore')) {
+                   params.store = get('datastore');
+               }
+
+               if (get('extraFilter')) {
+                   let extraFilter = get('extraFilter');
+                   for (const [name, value] of Object.entries(extraFilter)) {
+                       if (value !== undefined && value !== null && value !== "") {
+                           params[name] = value;
                        }
                    }
-                   return '';
+               }
+
+               if (get('since')) {
+                   params.since = get('since').valueOf()/1000;
+               }
+
+               if (get('until')) {
+                   let until = new Date(get('until').getTime()); // copy object
+                   until.setDate(until.getDate() + 1); // end of the day
+                   params.until = until.valueOf()/1000;
+               }
+
+               me.getView().getStore().load();
+
+               return params;
+           },
+       },
+
+       stores: {
+           bufferedstore: {
+               type: 'buffered',
+               pageSize: 500,
+               autoLoad: true,
+               remoteFilter: true,
+               model: 'proxmox-tasks',
+               proxy: {
+                   type: 'proxmox',
+                   startParam: 'start',
+                   limitParam: 'limit',
+                   extraParams: '{extraParams}',
+                   url: "/api2/json/nodes/localhost/tasks",
+               },
+               listeners: {
+                   prefetch: 'updateLayout',
                },
            },
-           tbar: [
-               view_btn,
+       },
+    },
+
+    bind: {
+       store: '{bufferedstore}',
+    },
+
+    dockedItems: [
+       {
+           xtype: 'toolbar',
+           items: [
                {
-                   text: gettext('Refresh'), // FIXME: smart-auto-refresh store
-                   handler: () => store.reload(),
+                   xtype: 'proxmoxButton',
+                   text: gettext('View'),
+                   iconCls: 'fa fa-window-restore',
+                   disabled: true,
+                   handler: 'showTaskLog',
+               },
+               {
+                   xtype: 'button',
+                   text: gettext('Reload'),
+                   iconCls: 'fa fa-refresh',
+                   handler: 'reload',
                },
                '->',
-               gettext('User name') +':',
-               ' ',
                {
-                   xtype: 'textfield',
-                   width: 200,
-                   value: userfilter,
-                   enableKeyEvents: true,
-                   listeners: {
-                       keyup: function(field, e) {
-                           userfilter = field.getValue();
-                           reload_task.delay(500);
-                       },
+                   xtype: 'button',
+                   enableToggle: true,
+                   bind: {
+                       iconCls: '{filterIcon}',
+                   },
+                   text: gettext('Filter'),
+                   stateful: true,
+                   stateId: 'task-showfilter',
+                   stateEvents: ['toggle'],
+                   applyState: function(state) {
+                       if (state.pressed !== undefined) {
+                           this.setPressed(state.pressed);
+                       }
+                   },
+                   getState: function() {
+                       return {
+                           pressed: this.pressed,
+                       };
                    },
-               }, ' ', gettext('Only Errors') + ':', ' ',
-               {
-                   xtype: 'checkbox',
-                   hideLabel: true,
-                   checked: filter_errors,
                    listeners: {
-                       change: function(field, checked) {
-                           filter_errors = checked ? 1 : 0;
-                           reload_task.delay(10);
-                       },
+                       toggle: 'showFilter',
                    },
-               }, ' ',
+               },
            ],
-           columns: [
+       },
+       {
+           xtype: 'toolbar',
+           dock: 'top',
+           reference: 'filtertoolbar',
+           layout: {
+               type: 'hbox',
+               align: 'top',
+           },
+           bind: {
+               hidden: '{!showFilter}',
+           },
+           items: [
                {
-                   header: gettext("Start Time"),
-                   dataIndex: 'starttime',
-                   width: 130,
-                   renderer: function(value) {
-                       return Ext.Date.format(value, "M d H:i:s");
+                   xtype: 'container',
+                   padding: 10,
+                   layout: {
+                       type: 'vbox',
+                       align: 'stretch',
                    },
-               },
-               {
-                   header: gettext("End Time"),
-                   dataIndex: 'endtime',
-                   width: 130,
-                   renderer: function(value, metaData, record) {
-                       if (!value) {
-                           metaData.tdCls = "x-grid-row-loading";
-                           return '';
-                       }
-                       return Ext.Date.format(value, "M d H:i:s");
+                   defaults: {
+                       labelWidth: 80,
                    },
+                   // cannot bind the values directly, as it then changes also
+                   // on blur, causing wrong reloads of the store
+                   items: [
+                       {
+                           xtype: 'datefield',
+                           fieldLabel: gettext('Since'),
+                           format: 'Y-m-d',
+                           bind: {
+                               maxValue: '{until}',
+                           },
+                           listeners: {
+                               change: 'sinceChange',
+                           },
+                       },
+                       {
+                           xtype: 'datefield',
+                           fieldLabel: gettext('Until'),
+                           format: 'Y-m-d',
+                           bind: {
+                               minValue: '{since}',
+                           },
+                           listeners: {
+                               change: 'untilChange',
+                           },
+                       },
+                   ],
                },
                {
-                   header: gettext("Duration"),
-                   hidden: true,
-                   width: 80,
-                   renderer: function(value, metaData, record) {
-                       let start = record.data.starttime;
-                       if (start) {
-                           let end = record.data.endtime || Date.now();
-                           let duration = end - start;
-                           if (duration > 0) {
-                               duration /= 1000;
-                           }
-                           return Proxmox.Utils.format_duration_human(duration);
-                       }
-                       return Proxmox.Utils.unknownText;
+                   xtype: 'container',
+                   padding: 10,
+                   layout: {
+                       type: 'vbox',
+                       align: 'stretch',
                    },
+                   defaults: {
+                       labelWidth: 80,
+                   },
+                   items: [
+                       {
+                           xtype: 'pmxTaskTypeSelector',
+                           fieldLabel: gettext('Task Type'),
+                           emptyText: gettext('All'),
+                           bind: {
+                               value: '{typefilter}',
+                           },
+                       },
+                       {
+                           xtype: 'combobox',
+                           fieldLabel: gettext('Task Result'),
+                           emptyText: gettext('All'),
+                           multiSelect: true,
+                           store: [
+                               ['ok', gettext('OK')],
+                               ['unknown', Proxmox.Utils.unknownText],
+                               ['warning', gettext('Warnings')],
+                               ['error', gettext('Errors')],
+                           ],
+                           bind: {
+                               value: '{statusfilter}',
+                           },
+                       },
+                   ],
                },
-               {
-                   header: gettext("Node"),
-                   dataIndex: 'node',
-                   width: 120,
-               },
-               {
-                   header: gettext("User name"),
-                   dataIndex: 'user',
-                   width: 150,
+           ],
+       },
+    ],
+
+    viewConfig: {
+       trackOver: false,
+       stripeRows: false, // does not work with getRowClass()
+       emptyText: gettext('No Tasks found'),
+
+       getRowClass: function(record, index) {
+           let status = record.get('status');
+
+           if (status) {
+               let parsed = Proxmox.Utils.parse_task_status(status);
+               if (parsed === 'error') {
+                   return "proxmox-invalid-row";
+               } else if (parsed === 'warning') {
+                   return "proxmox-warning-row";
+               }
+           }
+           return '';
+       },
+    },
+
+    columns: [
+       {
+           header: gettext("Start Time"),
+           dataIndex: 'starttime',
+           width: 130,
+           renderer: function(value) {
+               return Ext.Date.format(value, "M d H:i:s");
+           },
+       },
+       {
+           header: gettext("End Time"),
+           dataIndex: 'endtime',
+           width: 130,
+           renderer: function(value, metaData, record) {
+               if (!value) {
+                   metaData.tdCls = "x-grid-row-loading";
+                   return '';
+               }
+               return Ext.Date.format(value, "M d H:i:s");
+           },
+       },
+       {
+           header: gettext("Duration"),
+           hidden: true,
+           width: 80,
+           renderer: function(value, metaData, record) {
+               let start = record.data.starttime;
+               if (start) {
+                   let end = record.data.endtime || Date.now();
+                   let duration = end - start;
+                   if (duration > 0) {
+                       duration /= 1000;
+                   }
+                   return Proxmox.Utils.format_duration_human(duration);
+               }
+               return Proxmox.Utils.unknownText;
+           },
+       },
+       {
+           header: gettext("User name"),
+           dataIndex: 'user',
+           width: 150,
+       },
+       {
+           header: gettext("Description"),
+           dataIndex: 'upid',
+           flex: 1,
+           renderer: Proxmox.Utils.render_upid,
+       },
+       {
+           header: gettext("Status"),
+           dataIndex: 'status',
+           width: 200,
+           renderer: function(value, metaData, record) {
+               if (value === undefined && !record.data.endtime) {
+                   metaData.tdCls = "x-grid-row-loading";
+                   return '';
+               }
+
+               let parsed = Proxmox.Utils.parse_task_status(value);
+               switch (parsed) {
+                   case 'unknown': return Proxmox.Utils.unknownText;
+                   case 'error': return Proxmox.Utils.errorText + ': ' + value;
+                   case 'ok': // fall-through
+                   case 'warning': // fall-through
+                   default: return value;
+               }
+           },
+       },
+    ],
+
+    initComponent: function() {
+       const me = this;
+
+       let updateExtraFilters = function(name, value) {
+           let vm = me.getViewModel();
+           let extraFilter = Ext.clone(vm.get('extraFilter'));
+           extraFilter[name] = value;
+           vm.set('extraFilter', extraFilter);
+       };
+
+       for (const [name, value] of Object.entries(me.preFilter)) {
+           updateExtraFilters(name, value);
+       }
+
+       me.callParent();
+
+       let addFields = function(items) {
+           me.lookup('filtertoolbar').add({
+               xtype: 'container',
+               padding: 10,
+               layout: {
+                   type: 'vbox',
+                   align: 'stretch',
                },
-               {
-                   header: gettext("Description"),
-                   dataIndex: 'upid',
-                   flex: 1,
-                   renderer: Proxmox.Utils.render_upid,
+               defaults: {
+                   labelWidth: 80,
                },
-               {
-                   header: gettext("Status"),
-                   dataIndex: 'status',
-                   width: 200,
-                   renderer: function(value, metaData, record) {
-                       if (value === undefined && !record.data.endtime) {
-                           metaData.tdCls = "x-grid-row-loading";
-                           return '';
-                       }
+               items,
+           });
+       };
 
-                       return Proxmox.Utils.format_task_status(value);
-                   },
+       // start with a userfilter
+       me.extraFilter = [
+           {
+               xtype: 'textfield',
+               fieldLabel: gettext('User name'),
+               changeOptions: {
+                   buffer: 500,
                },
-           ],
-           listeners: {
-               itemdblclick: run_task_viewer,
-               selectionchange: function(v, selections) {
-                   view_btn.setDisabled(!(selections && selections[0]));
-               },
-               show: function() { reload_task.delay(10); },
-               destroy: function() { reload_task.cancel(); },
+               name: 'userfilter',
            },
-       });
+           ...me.extraFilter,
+       ];
+       let items = [];
+       for (const filterTemplate of me.extraFilter) {
+           let filter = Ext.clone(filterTemplate);
 
-       me.callParent();
+           filter.listeners = filter.listeners || {};
+           filter.listeners.change = Ext.apply(filter.changeOptions || {}, {
+               fn: function(field, value) {
+                   updateExtraFilters(filter.name, value);
+               },
+           });
+
+           items.push(filter);
+           if (items.length === 2) {
+               addFields(items);
+               items = [];
+           }
+       }
+
+       addFields(items);
     },
 });