mirror of
https://git.proxmox.com/git/proxmox-widget-toolkit
synced 2025-05-24 07:30:51 +00:00
node/Tasks: merge improvements from PBS and make it more generic
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>
This commit is contained in:
parent
5995eddcc4
commit
9e059d560c
@ -48,6 +48,10 @@
|
|||||||
color: #FF6C59;
|
color: #FF6C59;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.info-blue {
|
||||||
|
color: #3892d4;
|
||||||
|
}
|
||||||
|
|
||||||
/* reduce chart legend space usage to something more sane */
|
/* reduce chart legend space usage to something more sane */
|
||||||
.x-legend-item {
|
.x-legend-item {
|
||||||
padding: 0.4em 0.8em 0.4em 1.8em;
|
padding: 0.4em 0.8em 0.4em 1.8em;
|
||||||
|
@ -1,21 +1,146 @@
|
|||||||
Ext.define('Proxmox.node.Tasks', {
|
Ext.define('Proxmox.node.Tasks', {
|
||||||
extend: 'Ext.grid.GridPanel',
|
extend: 'Ext.grid.GridPanel',
|
||||||
|
|
||||||
alias: ['widget.proxmoxNodeTasks'],
|
alias: 'widget.proxmoxNodeTasks',
|
||||||
|
|
||||||
stateful: true,
|
stateful: true,
|
||||||
stateId: 'grid-node-tasks',
|
stateId: 'pve-grid-node-tasks',
|
||||||
|
|
||||||
loadMask: true,
|
loadMask: true,
|
||||||
sortableColumns: false,
|
sortableColumns: false,
|
||||||
vmidFilter: 0,
|
|
||||||
|
|
||||||
initComponent: function() {
|
// 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: [],
|
||||||
|
|
||||||
|
|
||||||
|
// 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 me = this;
|
||||||
|
let selection = me.getView().getSelection();
|
||||||
if (!me.nodename) {
|
if (selection.length < 1) {
|
||||||
throw "no node name specified";
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let store = Ext.create('Ext.data.BufferedStore', {
|
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.getView().updateLayout();
|
||||||
|
},
|
||||||
|
|
||||||
|
sinceChange: function(field, newval) {
|
||||||
|
let me = this;
|
||||||
|
let vm = me.getViewModel();
|
||||||
|
|
||||||
|
vm.set('since', newval);
|
||||||
|
},
|
||||||
|
|
||||||
|
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);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
pageSize: 500,
|
||||||
autoLoad: true,
|
autoLoad: true,
|
||||||
remoteFilter: true,
|
remoteFilter: true,
|
||||||
@ -24,68 +149,157 @@ Ext.define('Proxmox.node.Tasks', {
|
|||||||
type: 'proxmox',
|
type: 'proxmox',
|
||||||
startParam: 'start',
|
startParam: 'start',
|
||||||
limitParam: 'limit',
|
limitParam: 'limit',
|
||||||
url: "/api2/json/nodes/" + me.nodename + "/tasks",
|
extraParams: '{extraParams}',
|
||||||
|
url: "/api2/json/nodes/localhost/tasks",
|
||||||
|
},
|
||||||
|
listeners: {
|
||||||
|
prefetch: 'updateLayout',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
|
||||||
|
|
||||||
store.on('prefetch', function() {
|
bind: {
|
||||||
// we want to update the scrollbar on every store load
|
store: '{bufferedstore}',
|
||||||
// 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 = '';
|
dockedItems: [
|
||||||
let filter_errors = 0;
|
{
|
||||||
|
xtype: 'toolbar',
|
||||||
let updateProxyParams = function() {
|
items: [
|
||||||
let params = {
|
{
|
||||||
errors: filter_errors,
|
xtype: 'proxmoxButton',
|
||||||
};
|
|
||||||
if (userfilter) {
|
|
||||||
params.userfilter = userfilter;
|
|
||||||
}
|
|
||||||
if (me.vmidFilter) {
|
|
||||||
params.vmid = me.vmidFilter;
|
|
||||||
}
|
|
||||||
store.proxy.extraParams = params;
|
|
||||||
};
|
|
||||||
|
|
||||||
updateProxyParams();
|
|
||||||
|
|
||||||
let reload_task = Ext.create('Ext.util.DelayedTask', function() {
|
|
||||||
updateProxyParams();
|
|
||||||
store.reload();
|
|
||||||
});
|
|
||||||
|
|
||||||
let run_task_viewer = function() {
|
|
||||||
let sm = me.getSelectionModel();
|
|
||||||
let rec = sm.getSelection()[0];
|
|
||||||
if (!rec) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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'),
|
text: gettext('View'),
|
||||||
|
iconCls: 'fa fa-window-restore',
|
||||||
disabled: true,
|
disabled: true,
|
||||||
handler: run_task_viewer,
|
handler: 'showTaskLog',
|
||||||
});
|
},
|
||||||
|
{
|
||||||
|
xtype: 'button',
|
||||||
|
text: gettext('Reload'),
|
||||||
|
iconCls: 'fa fa-refresh',
|
||||||
|
handler: 'reload',
|
||||||
|
},
|
||||||
|
'->',
|
||||||
|
{
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
listeners: {
|
||||||
|
toggle: 'showFilter',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
xtype: 'toolbar',
|
||||||
|
dock: 'top',
|
||||||
|
reference: 'filtertoolbar',
|
||||||
|
layout: {
|
||||||
|
type: 'hbox',
|
||||||
|
align: 'top',
|
||||||
|
},
|
||||||
|
bind: {
|
||||||
|
hidden: '{!showFilter}',
|
||||||
|
},
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
xtype: 'container',
|
||||||
|
padding: 10,
|
||||||
|
layout: {
|
||||||
|
type: 'vbox',
|
||||||
|
align: 'stretch',
|
||||||
|
},
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
Proxmox.Utils.monStoreErrors(me, store, true);
|
|
||||||
|
|
||||||
Ext.apply(me, {
|
|
||||||
store: store,
|
|
||||||
viewConfig: {
|
viewConfig: {
|
||||||
trackOver: false,
|
trackOver: false,
|
||||||
stripeRows: false, // does not work with getRowClass()
|
stripeRows: false, // does not work with getRowClass()
|
||||||
|
emptyText: gettext('No Tasks found'),
|
||||||
|
|
||||||
getRowClass: function(record, index) {
|
getRowClass: function(record, index) {
|
||||||
let status = record.get('status');
|
let status = record.get('status');
|
||||||
@ -101,39 +315,7 @@ Ext.define('Proxmox.node.Tasks', {
|
|||||||
return '';
|
return '';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
tbar: [
|
|
||||||
view_btn,
|
|
||||||
{
|
|
||||||
text: gettext('Refresh'), // FIXME: smart-auto-refresh store
|
|
||||||
handler: () => store.reload(),
|
|
||||||
},
|
|
||||||
'->',
|
|
||||||
gettext('User name') +':',
|
|
||||||
' ',
|
|
||||||
{
|
|
||||||
xtype: 'textfield',
|
|
||||||
width: 200,
|
|
||||||
value: userfilter,
|
|
||||||
enableKeyEvents: true,
|
|
||||||
listeners: {
|
|
||||||
keyup: function(field, e) {
|
|
||||||
userfilter = field.getValue();
|
|
||||||
reload_task.delay(500);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, ' ', gettext('Only Errors') + ':', ' ',
|
|
||||||
{
|
|
||||||
xtype: 'checkbox',
|
|
||||||
hideLabel: true,
|
|
||||||
checked: filter_errors,
|
|
||||||
listeners: {
|
|
||||||
change: function(field, checked) {
|
|
||||||
filter_errors = checked ? 1 : 0;
|
|
||||||
reload_task.delay(10);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, ' ',
|
|
||||||
],
|
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
header: gettext("Start Time"),
|
header: gettext("Start Time"),
|
||||||
@ -172,11 +354,6 @@ Ext.define('Proxmox.node.Tasks', {
|
|||||||
return Proxmox.Utils.unknownText;
|
return Proxmox.Utils.unknownText;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
header: gettext("Node"),
|
|
||||||
dataIndex: 'node',
|
|
||||||
width: 120,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
header: gettext("User name"),
|
header: gettext("User name"),
|
||||||
dataIndex: 'user',
|
dataIndex: 'user',
|
||||||
@ -198,20 +375,79 @@ Ext.define('Proxmox.node.Tasks', {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
return Proxmox.Utils.format_task_status(value);
|
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;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
listeners: {
|
|
||||||
itemdblclick: run_task_viewer,
|
initComponent: function() {
|
||||||
selectionchange: function(v, selections) {
|
const me = this;
|
||||||
view_btn.setDisabled(!(selections && selections[0]));
|
|
||||||
|
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',
|
||||||
},
|
},
|
||||||
show: function() { reload_task.delay(10); },
|
defaults: {
|
||||||
destroy: function() { reload_task.cancel(); },
|
labelWidth: 80,
|
||||||
|
},
|
||||||
|
items,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// start with a userfilter
|
||||||
|
me.extraFilter = [
|
||||||
|
{
|
||||||
|
xtype: 'textfield',
|
||||||
|
fieldLabel: gettext('User name'),
|
||||||
|
changeOptions: {
|
||||||
|
buffer: 500,
|
||||||
|
},
|
||||||
|
name: 'userfilter',
|
||||||
|
},
|
||||||
|
...me.extraFilter,
|
||||||
|
];
|
||||||
|
let items = [];
|
||||||
|
for (const filterTemplate of me.extraFilter) {
|
||||||
|
let filter = Ext.clone(filterTemplate);
|
||||||
|
|
||||||
|
filter.listeners = filter.listeners || {};
|
||||||
|
filter.listeners.change = Ext.apply(filter.changeOptions || {}, {
|
||||||
|
fn: function(field, value) {
|
||||||
|
updateExtraFilters(filter.name, value);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
me.callParent();
|
items.push(filter);
|
||||||
|
if (items.length === 2) {
|
||||||
|
addFields(items);
|
||||||
|
items = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addFields(items);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user