From: Dominik Csapak <d.csapak@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [PATCH widget-toolkit 1/1] node/Tasks: merge improvements from PBS and make it more generic
Date: Thu, 24 Jun 2021 09:16:16 +0200 [thread overview]
Message-ID: <20210624071618.21507-2-d.csapak@proxmox.com> (raw)
In-Reply-To: <20210624071618.21507-1-d.csapak@proxmox.com>
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 | 4 +
src/node/Tasks.js | 578 ++++++++++++++++++++++++++++++-------------
2 files changed, 411 insertions(+), 171 deletions(-)
diff --git a/src/css/ext6-pmx.css b/src/css/ext6-pmx.css
index 7d7cddf..8e1980c 100644
--- a/src/css/ext6-pmx.css
+++ b/src/css/ext6-pmx.css
@@ -48,6 +48,10 @@
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;
diff --git a/src/node/Tasks.js b/src/node/Tasks.js
index 7f20a8a..049b527 100644
--- a/src/node/Tasks.js
+++ b/src/node/Tasks.js
@@ -1,217 +1,453 @@
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);
},
});
--
2.20.1
next prev parent reply other threads:[~2021-06-24 7:16 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-06-24 7:16 [pve-devel] [PATCH widget-toolkit/manager] unify gui for task listing Dominik Csapak
2021-06-24 7:16 ` Dominik Csapak [this message]
2021-06-28 15:47 ` [pve-devel] applied: [PATCH widget-toolkit 1/1] node/Tasks: merge improvements from PBS and make it more generic Thomas Lamprecht
2021-06-24 7:16 ` [pve-devel] [PATCH manager 1/2] {qemu, lxc}/Config: adapt to new proxmoxNodeTasks Dominik Csapak
2021-06-28 15:53 ` [pve-devel] applied: " Thomas Lamprecht
2021-06-24 7:16 ` [pve-devel] [PATCH manager 2/2] ui: node/Config: add vmid filter to task lists Dominik Csapak
2021-06-28 15:53 ` [pve-devel] applied: " Thomas Lamprecht
2021-06-28 9:45 ` [pve-devel] [PATCH widget-toolkit/manager] unify gui for task listing Lorenz Stechauner
2021-06-28 15:58 ` Thomas Lamprecht
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20210624071618.21507-2-d.csapak@proxmox.com \
--to=d.csapak@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal