* [pve-devel] [PATCH manager 0/7] backup edit window improvements
@ 2023-03-06 14:23 Dominik Csapak
2023-03-06 14:23 ` [pve-devel] [PATCH manager 1/7] ui: VMSelector: columns customizable Dominik Csapak
` (7 more replies)
0 siblings, 8 replies; 10+ messages in thread
From: Dominik Csapak @ 2023-03-06 14:23 UTC (permalink / raw)
To: pve-devel
two bugfixes (the VMSelector changes are necessary for those)
and a refactor of the edit window (i put that patch at the end, because even
with simplifications, it's still ~30 lines more, and i was not sure if that's
worth it)
Dominik Csapak (7):
ui: VMSelector: columns customizable
ui: VMSelector: improve {set,get}Value handling with a loading store
ui: VMSelector: change from filter to load parameters
ui: VMSelector: correctly change invalid class on en/disable
fix #4490: ui: add column filters in Backup Job edit window
fix #4239: ui: show selected but non-existing vmids in backup edit
ui: BackupEdit: refactor edit window into declarative style
www/manager6/dc/Backup.js | 788 +++++++++++++++-----------------
www/manager6/form/VMSelector.js | 75 ++-
2 files changed, 423 insertions(+), 440 deletions(-)
--
2.30.2
^ permalink raw reply [flat|nested] 10+ messages in thread
* [pve-devel] [PATCH manager 1/7] ui: VMSelector: columns customizable
2023-03-06 14:23 [pve-devel] [PATCH manager 0/7] backup edit window improvements Dominik Csapak
@ 2023-03-06 14:23 ` Dominik Csapak
2023-03-06 14:23 ` [pve-devel] [PATCH manager 2/7] ui: VMSelector: improve {set, get}Value handling with a loading store Dominik Csapak
` (6 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Dominik Csapak @ 2023-03-06 14:23 UTC (permalink / raw)
To: pve-devel
we will reuse this component but don't want to show all columns
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
www/manager6/form/VMSelector.js | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/www/manager6/form/VMSelector.js b/www/manager6/form/VMSelector.js
index 78cd90134..d80a21ec0 100644
--- a/www/manager6/form/VMSelector.js
+++ b/www/manager6/form/VMSelector.js
@@ -22,7 +22,8 @@ Ext.define('PVE.form.VMSelector', {
value: /lxc|qemu/,
}],
},
- columns: [
+
+ columnsDeclaration: [
{
header: 'ID',
dataIndex: 'vmid',
@@ -94,6 +95,9 @@ Ext.define('PVE.form.VMSelector', {
},
],
+ // should be a list of 'dataIndex' values, if 'undefined' all declared columns will be included
+ columnSelection: undefined,
+
selModel: {
selType: 'checkboxmodel',
mode: 'SIMPLE',
@@ -155,6 +159,12 @@ Ext.define('PVE.form.VMSelector', {
initComponent: function() {
let me = this;
+ let columns = me.columnsDeclaration.filter((column) =>
+ me.columnSelection ? me.columnSelection.indexOf(column.dataIndex) !== -1 : true,
+ ).map((x) => x);
+
+ me.columns = columns;
+
me.callParent();
if (me.nodename) {
--
2.30.2
^ permalink raw reply [flat|nested] 10+ messages in thread
* [pve-devel] [PATCH manager 2/7] ui: VMSelector: improve {set, get}Value handling with a loading store
2023-03-06 14:23 [pve-devel] [PATCH manager 0/7] backup edit window improvements Dominik Csapak
2023-03-06 14:23 ` [pve-devel] [PATCH manager 1/7] ui: VMSelector: columns customizable Dominik Csapak
@ 2023-03-06 14:23 ` Dominik Csapak
2023-03-06 14:23 ` [pve-devel] [PATCH manager 3/7] ui: VMSelector: change from filter to load parameters Dominik Csapak
` (5 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Dominik Csapak @ 2023-03-06 14:23 UTC (permalink / raw)
To: pve-devel
when we do {set,get}Value during a store load, the store might be empty
or incomplete, so defer the selection after the load and cache the value
for getValue invocations until the store is loaded
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
www/manager6/form/VMSelector.js | 30 ++++++++++++++++++++++++++----
1 file changed, 26 insertions(+), 4 deletions(-)
diff --git a/www/manager6/form/VMSelector.js b/www/manager6/form/VMSelector.js
index d80a21ec0..bb670f930 100644
--- a/www/manager6/form/VMSelector.js
+++ b/www/manager6/form/VMSelector.js
@@ -117,6 +117,9 @@ Ext.define('PVE.form.VMSelector', {
getValue: function() {
var me = this;
+ if (me.savedValue !== undefined) {
+ return me.savedValue;
+ }
var sm = me.getSelectionModel();
var selection = sm.getSelection();
var values = [];
@@ -130,6 +133,20 @@ Ext.define('PVE.form.VMSelector', {
return values;
},
+ setValueSelection: function(value) {
+ let me = this;
+
+ let store = me.getStore();
+ let selection = value.map(item => store.findRecord('vmid', item, 0, false, true, true)).filter(r => r);
+
+ let sm = me.getSelectionModel();
+ if (selection.length) {
+ sm.select(selection);
+ } else {
+ sm.deselectAll();
+ }
+ },
+
setValue: function(value) {
let me = this;
if (!Ext.isArray(value)) {
@@ -137,10 +154,15 @@ Ext.define('PVE.form.VMSelector', {
}
let store = me.getStore();
- let selection = value.map(item => store.findRecord('vmid', item, 0, false, true, true)).filter(r => r);
-
- me.getSelectionModel().select(selection);
-
+ if (!store.isLoaded()) {
+ me.savedValue = value;
+ store.on('load', function() {
+ me.setValueSelection(value);
+ delete me.savedValue;
+ }, { single: true });
+ } else {
+ me.setValueSelection(value);
+ }
return me.mixins.field.setValue.call(me, value);
},
--
2.30.2
^ permalink raw reply [flat|nested] 10+ messages in thread
* [pve-devel] [PATCH manager 3/7] ui: VMSelector: change from filter to load parameters
2023-03-06 14:23 [pve-devel] [PATCH manager 0/7] backup edit window improvements Dominik Csapak
2023-03-06 14:23 ` [pve-devel] [PATCH manager 1/7] ui: VMSelector: columns customizable Dominik Csapak
2023-03-06 14:23 ` [pve-devel] [PATCH manager 2/7] ui: VMSelector: improve {set, get}Value handling with a loading store Dominik Csapak
@ 2023-03-06 14:23 ` Dominik Csapak
2023-03-06 14:23 ` [pve-devel] [PATCH manager 4/7] ui: VMSelector: correctly change invalid class on en/disable Dominik Csapak
` (4 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Dominik Csapak @ 2023-03-06 14:23 UTC (permalink / raw)
To: pve-devel
so that we can modify the filters without having to consider filtering
for the type. Note that 'vm' for the 'type' parameter also returns
containers.
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
www/manager6/form/VMSelector.js | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/www/manager6/form/VMSelector.js b/www/manager6/form/VMSelector.js
index bb670f930..fb54e4835 100644
--- a/www/manager6/form/VMSelector.js
+++ b/www/manager6/form/VMSelector.js
@@ -15,12 +15,7 @@ Ext.define('PVE.form.VMSelector', {
store: {
model: 'PVEResources',
- autoLoad: true,
sorters: 'vmid',
- filters: [{
- property: 'type',
- value: /lxc|qemu/,
- }],
},
columnsDeclaration: [
@@ -189,6 +184,8 @@ Ext.define('PVE.form.VMSelector', {
me.callParent();
+ me.getStore().load({ params: { type: 'vm' } });
+
if (me.nodename) {
me.store.filters.add({
property: 'node',
--
2.30.2
^ permalink raw reply [flat|nested] 10+ messages in thread
* [pve-devel] [PATCH manager 4/7] ui: VMSelector: correctly change invalid class on en/disable
2023-03-06 14:23 [pve-devel] [PATCH manager 0/7] backup edit window improvements Dominik Csapak
` (2 preceding siblings ...)
2023-03-06 14:23 ` [pve-devel] [PATCH manager 3/7] ui: VMSelector: change from filter to load parameters Dominik Csapak
@ 2023-03-06 14:23 ` Dominik Csapak
2023-03-11 17:34 ` Thomas Lamprecht
2023-03-06 14:23 ` [pve-devel] [PATCH manager 5/7] fix #4490: ui: add column filters in Backup Job edit window Dominik Csapak
` (3 subsequent siblings)
7 siblings, 1 reply; 10+ messages in thread
From: Dominik Csapak @ 2023-03-06 14:23 UTC (permalink / raw)
To: pve-devel
since we manually handle the invalid class, we have to manually trigger
that on setDisabled
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
www/manager6/form/VMSelector.js | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/www/manager6/form/VMSelector.js b/www/manager6/form/VMSelector.js
index fb54e4835..399c4e658 100644
--- a/www/manager6/form/VMSelector.js
+++ b/www/manager6/form/VMSelector.js
@@ -140,6 +140,8 @@ Ext.define('PVE.form.VMSelector', {
} else {
sm.deselectAll();
}
+ // to correctly trigger invalid class
+ me.getErrors();
},
setValue: function(value) {
@@ -163,7 +165,7 @@ Ext.define('PVE.form.VMSelector', {
getErrors: function(value) {
let me = this;
- if (me.allowBlank === false &&
+ if (!me.isDisabled() && me.allowBlank === false &&
me.getSelectionModel().getCount() === 0) {
me.addBodyCls(['x-form-trigger-wrap-default', 'x-form-trigger-wrap-invalid']);
return [gettext('No VM selected')];
@@ -173,6 +175,13 @@ Ext.define('PVE.form.VMSelector', {
return [];
},
+ setDisabled: function(disabled) {
+ let me = this;
+ let res = me.callParent([disabled]);
+ me.getErrors();
+ return res;
+ },
+
initComponent: function() {
let me = this;
--
2.30.2
^ permalink raw reply [flat|nested] 10+ messages in thread
* [pve-devel] [PATCH manager 5/7] fix #4490: ui: add column filters in Backup Job edit window
2023-03-06 14:23 [pve-devel] [PATCH manager 0/7] backup edit window improvements Dominik Csapak
` (3 preceding siblings ...)
2023-03-06 14:23 ` [pve-devel] [PATCH manager 4/7] ui: VMSelector: correctly change invalid class on en/disable Dominik Csapak
@ 2023-03-06 14:23 ` Dominik Csapak
2023-03-06 14:23 ` [pve-devel] [PATCH manager 6/7] fix #4239: ui: show selected but non-existing vmids in backup edit Dominik Csapak
` (2 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Dominik Csapak @ 2023-03-06 14:23 UTC (permalink / raw)
To: pve-devel
by replacing the manual vm grid implementation and reusing the
VMSelector we already have. Since this is a full-fledged form field, we
can drop the complicated selection tracking / reselecting that we did
by saving into a hidden field.
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
www/manager6/dc/Backup.js | 130 ++++----------------------------------
1 file changed, 14 insertions(+), 116 deletions(-)
diff --git a/www/manager6/dc/Backup.js b/www/manager6/dc/Backup.js
index 9d3059841..d5e8bf20c 100644
--- a/www/manager6/dc/Backup.js
+++ b/www/manager6/dc/Backup.js
@@ -18,10 +18,6 @@ Ext.define('PVE.dc.BackupEdit', {
method = 'PUT';
}
- let vmidField = Ext.create('Ext.form.field.Hidden', {
- name: 'vmid',
- });
-
// 'value' can be assigned a string or an array
let selModeField = Ext.create('Proxmox.form.KVComboBox', {
xtype: 'proxmoxKVComboBox',
@@ -36,19 +32,6 @@ Ext.define('PVE.dc.BackupEdit', {
value: '',
});
- let sm = Ext.create('Ext.selection.CheckboxModel', {
- mode: 'SIMPLE',
- listeners: {
- selectionchange: function(model, selected) {
- let sel = selected.map(record => record.data.vmid);
- // to avoid endless recursion suspend the vmidField change
- // event temporary as it calls us again
- vmidField.suspendEvent('change');
- vmidField.setValue(sel);
- vmidField.resumeEvent('change');
- },
- },
- });
let storagesel = Ext.create('PVE.form.StorageSelector', {
fieldLabel: gettext('Storage'),
@@ -72,66 +55,27 @@ Ext.define('PVE.dc.BackupEdit', {
},
});
- let store = new Ext.data.Store({
- model: 'PVEResources',
- sorters: {
- property: 'vmid',
- direction: 'ASC',
- },
- });
-
- let vmgrid = Ext.createWidget('grid', {
- store: store,
- border: true,
+ let vmgrid = Ext.createWidget('vmselector', {
height: 300,
- selModel: sm,
+ name: 'vmid',
disabled: true,
- columns: [
- {
- header: 'ID',
- dataIndex: 'vmid',
- width: 60,
- },
- {
- header: gettext('Node'),
- dataIndex: 'node',
- },
- {
- header: gettext('Status'),
- dataIndex: 'uptime',
- renderer: function(value) {
- if (value) {
- return Proxmox.Utils.runningText;
- } else {
- return Proxmox.Utils.stoppedText;
- }
- },
- },
- {
- header: gettext('Name'),
- dataIndex: 'name',
- flex: 1,
- },
- {
- header: gettext('Type'),
- dataIndex: 'type',
- },
- ],
+ allowBlank: false,
+ columnSelection: ['vmid', 'node', 'status', 'name', 'type'],
});
let selectPoolMembers = function(poolid) {
if (!poolid) {
return;
}
- sm.deselectAll(true);
- store.filter([
+ vmgrid.selModel.deselectAll(true);
+ vmgrid.getStore().filter([
{
id: 'poolFilter',
property: 'pool',
value: poolid,
},
]);
- sm.selectAll(true);
+ vmgrid.selModel.selectAll(true);
};
let selPool = Ext.create('PVE.form.PoolSelector', {
@@ -157,12 +101,13 @@ Ext.define('PVE.dc.BackupEdit', {
change: function(f, value) {
storagesel.setNodename(value);
let mode = selModeField.getValue();
+ let store = vmgrid.getStore();
store.clearFilter();
store.filterBy(function(rec) {
return !value || rec.get('node') === value;
});
if (mode === 'all') {
- sm.selectAll(true);
+ vmgrid.selModel.selectAll(true);
}
if (mode === 'pool') {
selectPoolMembers(selPool.value);
@@ -218,7 +163,6 @@ Ext.define('PVE.dc.BackupEdit', {
defaultValue: 1,
checked: true,
},
- vmidField,
];
let ipanel = Ext.create('Proxmox.panel.InputPanel', {
@@ -282,37 +226,17 @@ Ext.define('PVE.dc.BackupEdit', {
},
});
- let update_vmid_selection = function(list, mode) {
- if (mode !== 'all' && mode !== 'pool') {
- sm.deselectAll(true);
- if (list) {
- Ext.Array.each(list.split(','), function(vmid) {
- var rec = store.findRecord('vmid', vmid, 0, false, true, true);
- if (rec) {
- sm.select(rec, true);
- }
- });
- }
- }
- };
-
- vmidField.on('change', function(f, value) {
- let mode = selModeField.getValue();
- update_vmid_selection(value, mode);
- });
-
selModeField.on('change', function(f, value, oldValue) {
if (oldValue === 'pool') {
- store.removeFilter('poolFilter');
+ vmgrid.getStore().removeFilter('poolFilter');
}
- if (oldValue === 'all') {
- sm.deselectAll(true);
- vmidField.setValue('');
+ if (oldValue === 'all' || oldValue === 'pool') {
+ vmgrid.selModel.deselectAll(true);
}
if (value === 'all') {
- sm.selectAll(true);
+ vmgrid.selModel.selectAll(true);
vmgrid.setDisabled(true);
} else {
vmgrid.setDisabled(false);
@@ -320,7 +244,7 @@ Ext.define('PVE.dc.BackupEdit', {
if (value === 'pool') {
vmgrid.setDisabled(true);
- vmidField.setValue('');
+ vmgrid.selModel.deselectAll(true);
selPool.setVisible(true);
selPool.setDisabled(false);
selPool.allowBlank = false;
@@ -330,32 +254,8 @@ Ext.define('PVE.dc.BackupEdit', {
selPool.setDisabled(true);
selPool.allowBlank = true;
}
- let list = vmidField.getValue();
- update_vmid_selection(list, value);
});
- let reload = function() {
- store.load({
- params: {
- type: 'vm',
- },
- callback: function() {
- let node = nodesel.getValue();
- store.clearFilter();
- store.filterBy(rec => !node || node.length === 0 || rec.get('node') === node);
- let list = vmidField.getValue();
- let mode = selModeField.getValue();
- if (mode === 'all') {
- sm.selectAll(true);
- } else if (mode === 'pool') {
- selectPoolMembers(selPool.value);
- } else {
- update_vmid_selection(list, mode);
- }
- },
- });
- };
-
Ext.applyIf(me, {
subject: gettext("Backup Job"),
url: url,
@@ -481,8 +381,6 @@ Ext.define('PVE.dc.BackupEdit', {
},
});
}
-
- reload();
},
});
--
2.30.2
^ permalink raw reply [flat|nested] 10+ messages in thread
* [pve-devel] [PATCH manager 6/7] fix #4239: ui: show selected but non-existing vmids in backup edit
2023-03-06 14:23 [pve-devel] [PATCH manager 0/7] backup edit window improvements Dominik Csapak
` (4 preceding siblings ...)
2023-03-06 14:23 ` [pve-devel] [PATCH manager 5/7] fix #4490: ui: add column filters in Backup Job edit window Dominik Csapak
@ 2023-03-06 14:23 ` Dominik Csapak
2023-03-06 14:23 ` [pve-devel] [PATCH manager 7/7] ui: BackupEdit: refactor edit window into declarative style Dominik Csapak
2023-03-14 10:37 ` [pve-devel] applied: [PATCH manager 0/7] backup edit window improvements Thomas Lamprecht
7 siblings, 0 replies; 10+ messages in thread
From: Dominik Csapak @ 2023-03-06 14:23 UTC (permalink / raw)
To: pve-devel
by adding records manually when using 'setValue' on a vmselector.
It'll show up normally but have an 'unknown' nodename, and no type/status/etc.
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
www/manager6/form/VMSelector.js | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)
diff --git a/www/manager6/form/VMSelector.js b/www/manager6/form/VMSelector.js
index 399c4e658..e9eb5dbaa 100644
--- a/www/manager6/form/VMSelector.js
+++ b/www/manager6/form/VMSelector.js
@@ -132,7 +132,22 @@ Ext.define('PVE.form.VMSelector', {
let me = this;
let store = me.getStore();
- let selection = value.map(item => store.findRecord('vmid', item, 0, false, true, true)).filter(r => r);
+ let notFound = [];
+ let selection = value.map(item => {
+ let found = store.findRecord('vmid', item, 0, false, true, true);
+ if (!found) {
+ notFound.push(item);
+ }
+ return found;
+ }).filter(r => r);
+
+ for (const vmid of notFound) {
+ let rec = store.add({
+ vmid,
+ node: 'unknown',
+ });
+ selection.push(rec[0]);
+ }
let sm = me.getSelectionModel();
if (selection.length) {
--
2.30.2
^ permalink raw reply [flat|nested] 10+ messages in thread
* [pve-devel] [PATCH manager 7/7] ui: BackupEdit: refactor edit window into declarative style
2023-03-06 14:23 [pve-devel] [PATCH manager 0/7] backup edit window improvements Dominik Csapak
` (5 preceding siblings ...)
2023-03-06 14:23 ` [pve-devel] [PATCH manager 6/7] fix #4239: ui: show selected but non-existing vmids in backup edit Dominik Csapak
@ 2023-03-06 14:23 ` Dominik Csapak
2023-03-14 10:37 ` [pve-devel] applied: [PATCH manager 0/7] backup edit window improvements Thomas Lamprecht
7 siblings, 0 replies; 10+ messages in thread
From: Dominik Csapak @ 2023-03-06 14:23 UTC (permalink / raw)
To: pve-devel
simplifies some things, e.g. en/disabling the grid and pool selector
while refactoring, cleanup up some smaller things like nested if/else
paths, unnecessary splitting of the deprecated 'dow' parameter, etc.
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
www/manager6/dc/Backup.js | 690 ++++++++++++++++++++------------------
1 file changed, 361 insertions(+), 329 deletions(-)
diff --git a/www/manager6/dc/Backup.js b/www/manager6/dc/Backup.js
index d5e8bf20c..d0046177a 100644
--- a/www/manager6/dc/Backup.js
+++ b/www/manager6/dc/Backup.js
@@ -2,72 +2,107 @@ Ext.define('PVE.dc.BackupEdit', {
extend: 'Proxmox.window.Edit',
alias: ['widget.pveDcBackupEdit'],
+ mixins: ['Proxmox.Mixin.CBind'],
+
defaultFocus: undefined,
- initComponent: function() {
- let me = this;
+ subject: gettext("Backup Job"),
+ bodyPadding: 0,
- me.isCreate = !me.jobid;
+ url: '/api2/extjs/cluster/backup',
+ method: 'POST',
+ isCreate: true,
- let url, method;
- if (me.isCreate) {
- url = '/api2/extjs/cluster/backup';
- method = 'POST';
- } else {
- url = '/api2/extjs/cluster/backup/' + me.jobid;
- method = 'PUT';
+ cbindData: function() {
+ let me = this;
+ if (me.jobid) {
+ me.isCreate = false;
+ me.method = 'PUT';
+ me.url += `/${me.jobid}`;
}
+ return {};
+ },
- // 'value' can be assigned a string or an array
- let selModeField = Ext.create('Proxmox.form.KVComboBox', {
- xtype: 'proxmoxKVComboBox',
- comboItems: [
- ['include', gettext('Include selected VMs')],
- ['all', gettext('All')],
- ['exclude', gettext('Exclude selected VMs')],
- ['pool', gettext('Pool based')],
- ],
- fieldLabel: gettext('Selection mode'),
- name: 'selMode',
- value: '',
- });
+ controller: {
+ xclass: 'Ext.app.ViewController',
+ onGetValues: function(values) {
+ let me = this;
+ let isCreate = me.getView().isCreate;
+ if (!values.node) {
+ if (!isCreate) {
+ Proxmox.Utils.assemble_field_data(values, { 'delete': 'node' });
+ }
+ delete values.node;
+ }
- let storagesel = Ext.create('PVE.form.StorageSelector', {
- fieldLabel: gettext('Storage'),
- clusterView: true,
- storageContent: 'backup',
- allowBlank: false,
- name: 'storage',
- listeners: {
- change: function(f, v) {
- let store = f.getStore();
- let rec = store.findRecord('storage', v, 0, false, true, true);
- let compressionSelector = me.down('pveCompressionSelector');
-
- if (rec && rec.data && rec.data.type === 'pbs') {
- compressionSelector.setValue('zstd');
- compressionSelector.setDisabled(true);
- } else if (!compressionSelector.getEditable()) {
- compressionSelector.setDisabled(false);
- }
- },
- },
- });
+ if (!values.id && isCreate) {
+ values.id = 'backup-' + Ext.data.identifier.Uuid.Global.generate().slice(0, 13);
+ }
- let vmgrid = Ext.createWidget('vmselector', {
- height: 300,
- name: 'vmid',
- disabled: true,
- allowBlank: false,
- columnSelection: ['vmid', 'node', 'status', 'name', 'type'],
- });
+ let selMode = values.selMode;
+ delete values.selMode;
+
+ if (selMode === 'all') {
+ values.all = 1;
+ values.exclude = '';
+ delete values.vmid;
+ } else if (selMode === 'exclude') {
+ values.all = 1;
+ values.exclude = values.vmid;
+ delete values.vmid;
+ } else if (selMode === 'pool') {
+ delete values.vmid;
+ }
+
+ if (selMode !== 'pool') {
+ delete values.pool;
+ }
+ return values;
+ },
+
+ nodeChange: function(f, value) {
+ let me = this;
+ me.lookup('storageSelector').setNodename(value);
+ let vmgrid = me.lookup('vmgrid');
+ let store = vmgrid.getStore();
+
+ store.clearFilter();
+ store.filterBy(function(rec) {
+ return !value || rec.get('node') === value;
+ });
+
+ let mode = me.lookup('modeSelector').getValue();
+ if (mode === 'all') {
+ vmgrid.selModel.selectAll(true);
+ }
+ if (mode === 'pool') {
+ me.selectPoolMembers();
+ }
+ },
+
+ storageChange: function(f, v) {
+ let me = this;
+ let rec = f.getStore().findRecord('storage', v, 0, false, true, true);
+ let compressionSelector = me.lookup('compressionSelector');
+
+ if (rec?.data?.type === 'pbs') {
+ compressionSelector.setValue('zstd');
+ compressionSelector.setDisabled(true);
+ } else if (!compressionSelector.getEditable()) {
+ compressionSelector.setDisabled(false);
+ }
+ },
+
+ selectPoolMembers: function() {
+ let me = this;
+ let vmgrid = me.lookup('vmgrid');
+ let poolid = me.lookup('poolSelector').getValue();
- let selectPoolMembers = function(poolid) {
+ vmgrid.getSelectionModel().deselectAll(true);
if (!poolid) {
return;
}
- vmgrid.selModel.deselectAll(true);
vmgrid.getStore().filter([
{
id: 'poolFilter',
@@ -76,312 +111,309 @@ Ext.define('PVE.dc.BackupEdit', {
},
]);
vmgrid.selModel.selectAll(true);
- };
+ },
- let selPool = Ext.create('PVE.form.PoolSelector', {
- fieldLabel: gettext('Pool to backup'),
- hidden: true,
- allowBlank: true,
- name: 'pool',
- listeners: {
- change: function(selpool, newValue, oldValue) {
- selectPoolMembers(newValue);
- },
- },
- });
+ modeChange: function(f, value, oldValue) {
+ let me = this;
+ let vmgrid = me.lookup('vmgrid');
+ vmgrid.getStore().removeFilter('poolFilter');
- let nodesel = Ext.create('PVE.form.NodeSelector', {
- name: 'node',
- fieldLabel: gettext('Node'),
- allowBlank: true,
- editable: true,
- autoSelect: false,
- emptyText: '-- ' + gettext('All') + ' --',
- listeners: {
- change: function(f, value) {
- storagesel.setNodename(value);
- let mode = selModeField.getValue();
- let store = vmgrid.getStore();
- store.clearFilter();
- store.filterBy(function(rec) {
- return !value || rec.get('node') === value;
- });
- if (mode === 'all') {
- vmgrid.selModel.selectAll(true);
- }
- if (mode === 'pool') {
- selectPoolMembers(selPool.value);
- }
- },
- },
- });
+ if (oldValue === 'all' && value !== 'all') {
+ vmgrid.getSelectionModel().deselectAll(true);
+ }
- let column1 = [
- nodesel,
- storagesel,
- {
- xtype: 'pveCalendarEvent',
- fieldLabel: gettext('Schedule'),
- allowBlank: false,
- name: 'schedule',
- },
- selModeField,
- selPool,
- ];
-
- let column2 = [
- {
- xtype: 'textfield',
- fieldLabel: gettext('Send email to'),
- name: 'mailto',
- },
- {
- xtype: 'pveEmailNotificationSelector',
- fieldLabel: gettext('Email'),
- name: 'mailnotification',
- deleteEmpty: !me.isCreate,
- value: me.isCreate ? 'always' : '',
- },
- {
- xtype: 'pveCompressionSelector',
- fieldLabel: gettext('Compression'),
- name: 'compress',
- deleteEmpty: !me.isCreate,
- value: 'zstd',
- },
- {
- xtype: 'pveBackupModeSelector',
- fieldLabel: gettext('Mode'),
- value: 'snapshot',
- name: 'mode',
- },
- {
- xtype: 'proxmoxcheckbox',
- fieldLabel: gettext('Enable'),
- name: 'enabled',
- uncheckedValue: 0,
- defaultValue: 1,
- checked: true,
- },
- ];
+ if (value === 'all') {
+ vmgrid.getSelectionModel().selectAll(true);
+ }
- let ipanel = Ext.create('Proxmox.panel.InputPanel', {
- onlineHelp: 'chapter_vzdump',
- column1: column1,
- column2: column2,
- columnB: [
- {
- xtype: 'proxmoxtextfield',
- name: 'comment',
- fieldLabel: gettext('Job Comment'),
- deleteEmpty: !me.isCreate,
- autoEl: {
- tag: 'div',
- 'data-qtip': gettext('Description of the job'),
- },
- },
- vmgrid,
- ],
- advancedColumn1: [
- {
- xtype: 'proxmoxcheckbox',
- fieldLabel: gettext('Repeat missed'),
- name: 'repeat-missed',
- uncheckedValue: 0,
- defaultValue: 0,
- deleteDefaultValue: !me.isCreate,
- },
- ],
- onGetValues: function(values) {
- if (!values.node) {
- if (!me.isCreate) {
- Proxmox.Utils.assemble_field_data(values, { 'delete': 'node' });
- }
- delete values.node;
- }
+ if (value === 'pool') {
+ me.selectPoolMembers();
+ }
+ },
- if (!values.id && me.isCreate) {
- values.id = 'backup-' + Ext.data.identifier.Uuid.Global.generate().slice(0, 13);
- }
+ init: function(view) {
+ let me = this;
+ if (view.isCreate) {
+ me.lookup('modeSelector').setValue('include');
+ } else {
+ view.load({
+ success: function(response, _options) {
+ let data = response.result.data;
- let selMode = values.selMode;
- delete values.selMode;
-
- if (selMode === 'all') {
- values.all = 1;
- values.exclude = '';
- delete values.vmid;
- } else if (selMode === 'exclude') {
- values.all = 1;
- values.exclude = values.vmid;
- delete values.vmid;
- } else if (selMode === 'pool') {
- delete values.vmid;
- }
+ if (data.exclude) {
+ data.vmid = data.exclude;
+ data.selMode = 'exclude';
+ } else if (data.all) {
+ data.vmid = '';
+ data.selMode = 'all';
+ } else if (data.pool) {
+ data.selMode = 'pool';
+ data.selPool = data.pool;
+ } else {
+ data.selMode = 'include';
+ }
- if (selMode !== 'pool') {
- delete values.pool;
- }
- return values;
- },
- });
+ me.getViewModel().set('selMode', data.selMode);
- selModeField.on('change', function(f, value, oldValue) {
- if (oldValue === 'pool') {
- vmgrid.getStore().removeFilter('poolFilter');
- }
+ if (data['prune-backups']) {
+ Object.assign(data, data['prune-backups']);
+ delete data['prune-backups'];
+ } else if (data.maxfiles !== undefined) {
+ if (data.maxfiles > 0) {
+ data['keep-last'] = data.maxfiles;
+ } else {
+ data['keep-all'] = 1;
+ }
+ delete data.maxfiles;
+ }
- if (oldValue === 'all' || oldValue === 'pool') {
- vmgrid.selModel.deselectAll(true);
- }
+ if (data['notes-template']) {
+ data['notes-template'] =
+ PVE.Utils.unEscapeNotesTemplate(data['notes-template']);
+ }
- if (value === 'all') {
- vmgrid.selModel.selectAll(true);
- vmgrid.setDisabled(true);
- } else {
- vmgrid.setDisabled(false);
+ view.setValues(data);
+ },
+ });
}
+ },
+ },
- if (value === 'pool') {
- vmgrid.setDisabled(true);
- vmgrid.selModel.deselectAll(true);
- selPool.setVisible(true);
- selPool.setDisabled(false);
- selPool.allowBlank = false;
- selectPoolMembers(selPool.value);
- } else {
- selPool.setVisible(false);
- selPool.setDisabled(true);
- selPool.allowBlank = true;
- }
- });
+ viewModel: {
+ data: {
+ selMode: 'include',
+ },
- Ext.applyIf(me, {
- subject: gettext("Backup Job"),
- url: url,
- method: method,
- bodyPadding: 0,
+ formulas: {
+ poolMode: (get) => get('selMode') === 'pool',
+ disableVMSelection: (get) => get('selMode') !== 'include' && get('selMode') !== 'exclude',
+ },
+ },
+
+ items: [
+ {
+ xtype: 'tabpanel',
+ region: 'center',
+ layout: 'fit',
+ bodyPadding: 10,
items: [
{
- xtype: 'tabpanel',
+ xtype: 'container',
+ title: gettext('General'),
region: 'center',
- layout: 'fit',
- bodyPadding: 10,
+ layout: {
+ type: 'vbox',
+ align: 'stretch',
+ },
items: [
- {
- xtype: 'container',
- title: gettext('General'),
- region: 'center',
- layout: {
- type: 'vbox',
- align: 'stretch',
- },
- items: [
- ipanel,
- ],
- },
- {
- xtype: 'pveBackupJobPrunePanel',
- title: gettext('Retention'),
- isCreate: me.isCreate,
- keepAllDefaultForCreate: false,
- showPBSHint: false,
- fallbackHintHtml: gettext('Without any keep option, the storage\'s configuration or node\'s vzdump.conf is used as fallback'),
- },
{
xtype: 'inputpanel',
- title: gettext('Note Template'),
- region: 'center',
- layout: {
- type: 'vbox',
- align: 'stretch',
- },
- onGetValues: function(values) {
- if (values['notes-template']) {
- values['notes-template'] = PVE.Utils.escapeNotesTemplate(
- values['notes-template']);
- }
- return values;
- },
- items: [
+ onlineHelp: 'chapter_vzdump',
+ column1: [
+ {
+ xtype: 'pveNodeSelector',
+ name: 'node',
+ fieldLabel: gettext('Node'),
+ allowBlank: true,
+ editable: true,
+ autoSelect: false,
+ emptyText: '-- ' + gettext('All') + ' --',
+ listeners: {
+ change: 'nodeChange',
+ },
+ },
+ {
+ xtype: 'pveStorageSelector',
+ reference: 'storageSelector',
+ fieldLabel: gettext('Storage'),
+ clusterView: true,
+ storageContent: 'backup',
+ allowBlank: false,
+ name: 'storage',
+ listeners: {
+ change: 'storageChange',
+ },
+ },
+ {
+ xtype: 'pveCalendarEvent',
+ fieldLabel: gettext('Schedule'),
+ allowBlank: false,
+ name: 'schedule',
+ },
+ {
+ xtype: 'proxmoxKVComboBox',
+ reference: 'modeSelector',
+ comboItems: [
+ ['include', gettext('Include selected VMs')],
+ ['all', gettext('All')],
+ ['exclude', gettext('Exclude selected VMs')],
+ ['pool', gettext('Pool based')],
+ ],
+ fieldLabel: gettext('Selection mode'),
+ name: 'selMode',
+ value: '',
+ bind: {
+ value: '{selMode}',
+ },
+ listeners: {
+ change: 'modeChange',
+ },
+ },
+ {
+ xtype: 'pvePoolSelector',
+ reference: 'poolSelector',
+ fieldLabel: gettext('Pool to backup'),
+ hidden: true,
+ allowBlank: false,
+ name: 'pool',
+ listeners: {
+ change: 'selectPoolMembers',
+ },
+ bind: {
+ hidden: '{!poolMode}',
+ disabled: '{!poolMode}',
+ },
+ },
+ ],
+ column2: [
+ {
+ xtype: 'textfield',
+ fieldLabel: gettext('Send email to'),
+ name: 'mailto',
+ },
+ {
+ xtype: 'pveEmailNotificationSelector',
+ fieldLabel: gettext('Email'),
+ name: 'mailnotification',
+ cbind: {
+ value: (get) => get('isCreate') ? 'always' : '',
+ deleteEmpty: '{!isCreate}',
+ },
+ },
+ {
+ xtype: 'pveCompressionSelector',
+ reference: 'compressionSelector',
+ fieldLabel: gettext('Compression'),
+ name: 'compress',
+ cbind: {
+ deleteEmpty: '{!isCreate}',
+ },
+ value: 'zstd',
+ },
+ {
+ xtype: 'pveBackupModeSelector',
+ fieldLabel: gettext('Mode'),
+ value: 'snapshot',
+ name: 'mode',
+ },
+ {
+ xtype: 'proxmoxcheckbox',
+ fieldLabel: gettext('Enable'),
+ name: 'enabled',
+ uncheckedValue: 0,
+ defaultValue: 1,
+ checked: true,
+ },
+ ],
+ columnB: [
+ {
+ xtype: 'proxmoxtextfield',
+ name: 'comment',
+ fieldLabel: gettext('Job Comment'),
+ cbind: {
+ deleteEmpty: '{!isCreate}',
+ },
+ autoEl: {
+ tag: 'div',
+ 'data-qtip': gettext('Description of the job'),
+ },
+ },
{
- xtype: 'textarea',
- name: 'notes-template',
- fieldLabel: gettext('Backup Notes'),
- height: 100,
- maxLength: 512,
- deleteEmpty: !me.isCreate,
- value: me.isCreate ? '{{guestname}}' : undefined,
+ xtype: 'vmselector',
+ reference: 'vmgrid',
+ height: 300,
+ name: 'vmid',
+ disabled: true,
+ allowBlank: false,
+ columnSelection: ['vmid', 'node', 'status', 'name', 'type'],
+ bind: {
+ disabled: '{disableVMSelection}',
+ },
},
+ ],
+ advancedColumn1: [
{
- xtype: 'box',
- style: {
- margin: '8px 0px',
- 'line-height': '1.5em',
+ xtype: 'proxmoxcheckbox',
+ fieldLabel: gettext('Repeat missed'),
+ name: 'repeat-missed',
+ uncheckedValue: 0,
+ defaultValue: 0,
+ cbind: {
+ deleteDefaultValue: '{!isCreate}',
},
- html: gettext('The notes are added to each backup created by this job.')
- + '<br>'
- + Ext.String.format(
- gettext('Possible template variables are: {0}'),
- PVE.Utils.notesTemplateVars.map(v => `<code>{{${v}}}</code>`).join(', '),
- ),
},
],
+ onGetValues: function(values) {
+ return this.up('window').getController().onGetValues(values);
+ },
},
],
},
- ],
-
- });
-
- me.callParent();
-
- if (me.isCreate) {
- selModeField.setValue('include');
- } else {
- me.load({
- success: function(response, options) {
- let data = response.result.data;
-
- data.dow = (data.dow || '').split(',');
-
- if (data.all || data.exclude) {
- if (data.exclude) {
- data.vmid = data.exclude;
- data.selMode = 'exclude';
- } else {
- data.vmid = '';
- data.selMode = 'all';
- }
- } else if (data.pool) {
- data.selMode = 'pool';
- data.selPool = data.pool;
- } else {
- data.selMode = 'include';
- }
-
- if (data['prune-backups']) {
- Object.assign(data, data['prune-backups']);
- delete data['prune-backups'];
- } else if (data.maxfiles !== undefined) {
- if (data.maxfiles > 0) {
- data['keep-last'] = data.maxfiles;
- } else {
- data['keep-all'] = 1;
+ {
+ xtype: 'pveBackupJobPrunePanel',
+ title: gettext('Retention'),
+ cbind: {
+ isCreate: '{isCreate}',
+ },
+ keepAllDefaultForCreate: false,
+ showPBSHint: false,
+ fallbackHintHtml: gettext('Without any keep option, the storage\'s configuration or node\'s vzdump.conf is used as fallback'),
+ },
+ {
+ xtype: 'inputpanel',
+ title: gettext('Note Template'),
+ region: 'center',
+ layout: {
+ type: 'vbox',
+ align: 'stretch',
+ },
+ onGetValues: function(values) {
+ if (values['notes-template']) {
+ values['notes-template'] =
+ PVE.Utils.escapeNotesTemplate(values['notes-template']);
}
- delete data.maxfiles;
- }
-
- if (data['notes-template']) {
- data['notes-template'] = PVE.Utils.unEscapeNotesTemplate(
- data['notes-template']);
- }
-
- me.setValues(data);
+ return values;
+ },
+ items: [
+ {
+ xtype: 'textarea',
+ name: 'notes-template',
+ fieldLabel: gettext('Backup Notes'),
+ height: 100,
+ maxLength: 512,
+ cbind: {
+ deleteEmpty: '{!isCreate}',
+ value: (get) => get('isCreate') ? '{{guestname}}' : undefined,
+ },
+ },
+ {
+ xtype: 'box',
+ style: {
+ margin: '8px 0px',
+ 'line-height': '1.5em',
+ },
+ html: gettext('The notes are added to each backup created by this job.')
+ + '<br>'
+ + Ext.String.format(
+ gettext('Possible template variables are: {0}'),
+ PVE.Utils.notesTemplateVars.map(v => `<code>{{${v}}}</code>`).join(', '),
+ ),
+ },
+ ],
},
- });
- }
- },
+ ],
+ },
+ ],
});
Ext.define('PVE.dc.BackupView', {
--
2.30.2
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [pve-devel] [PATCH manager 4/7] ui: VMSelector: correctly change invalid class on en/disable
2023-03-06 14:23 ` [pve-devel] [PATCH manager 4/7] ui: VMSelector: correctly change invalid class on en/disable Dominik Csapak
@ 2023-03-11 17:34 ` Thomas Lamprecht
0 siblings, 0 replies; 10+ messages in thread
From: Thomas Lamprecht @ 2023-03-11 17:34 UTC (permalink / raw)
To: Proxmox VE development discussion, Dominik Csapak
Am 06/03/2023 um 15:23 schrieb Dominik Csapak:
> since we manually handle the invalid class, we have to manually trigger
> that on setDisabled
>
> Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
> ---
> www/manager6/form/VMSelector.js | 11 ++++++++++-
> 1 file changed, 10 insertions(+), 1 deletion(-)
>
> diff --git a/www/manager6/form/VMSelector.js b/www/manager6/form/VMSelector.js
> index fb54e4835..399c4e658 100644
> --- a/www/manager6/form/VMSelector.js
> +++ b/www/manager6/form/VMSelector.js
> @@ -140,6 +140,8 @@ Ext.define('PVE.form.VMSelector', {
> } else {
> sm.deselectAll();
> }
> + // to correctly trigger invalid class
> + me.getErrors();
> },
>
> setValue: function(value) {
> @@ -163,7 +165,7 @@ Ext.define('PVE.form.VMSelector', {
>
> getErrors: function(value) {
> let me = this;
> - if (me.allowBlank === false &&
> + if (!me.isDisabled() && me.allowBlank === false &&
while at it you could have changed the odd `allowBlank === false` to just `!allowBlank` ;-)
^ permalink raw reply [flat|nested] 10+ messages in thread
* [pve-devel] applied: [PATCH manager 0/7] backup edit window improvements
2023-03-06 14:23 [pve-devel] [PATCH manager 0/7] backup edit window improvements Dominik Csapak
` (6 preceding siblings ...)
2023-03-06 14:23 ` [pve-devel] [PATCH manager 7/7] ui: BackupEdit: refactor edit window into declarative style Dominik Csapak
@ 2023-03-14 10:37 ` Thomas Lamprecht
7 siblings, 0 replies; 10+ messages in thread
From: Thomas Lamprecht @ 2023-03-14 10:37 UTC (permalink / raw)
To: Proxmox VE development discussion, Dominik Csapak
Am 06/03/2023 um 15:23 schrieb Dominik Csapak:
> two bugfixes (the VMSelector changes are necessary for those)
> and a refactor of the edit window (i put that patch at the end, because even
> with simplifications, it's still ~30 lines more, and i was not sure if that's
> worth it)
>
> Dominik Csapak (7):
> ui: VMSelector: columns customizable
> ui: VMSelector: improve {set,get}Value handling with a loading store
> ui: VMSelector: change from filter to load parameters
> ui: VMSelector: correctly change invalid class on en/disable
> fix #4490: ui: add column filters in Backup Job edit window
> fix #4239: ui: show selected but non-existing vmids in backup edit
> ui: BackupEdit: refactor edit window into declarative style
>
> www/manager6/dc/Backup.js | 788 +++++++++++++++-----------------
> www/manager6/form/VMSelector.js | 75 ++-
> 2 files changed, 423 insertions(+), 440 deletions(-)
>
applied series, albeit I wanted to look a bit more closely onto it but my finger
slipped, anyhow, the core idea is good and any regression we can fix, so thanks!
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2023-03-14 10:37 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-03-06 14:23 [pve-devel] [PATCH manager 0/7] backup edit window improvements Dominik Csapak
2023-03-06 14:23 ` [pve-devel] [PATCH manager 1/7] ui: VMSelector: columns customizable Dominik Csapak
2023-03-06 14:23 ` [pve-devel] [PATCH manager 2/7] ui: VMSelector: improve {set, get}Value handling with a loading store Dominik Csapak
2023-03-06 14:23 ` [pve-devel] [PATCH manager 3/7] ui: VMSelector: change from filter to load parameters Dominik Csapak
2023-03-06 14:23 ` [pve-devel] [PATCH manager 4/7] ui: VMSelector: correctly change invalid class on en/disable Dominik Csapak
2023-03-11 17:34 ` Thomas Lamprecht
2023-03-06 14:23 ` [pve-devel] [PATCH manager 5/7] fix #4490: ui: add column filters in Backup Job edit window Dominik Csapak
2023-03-06 14:23 ` [pve-devel] [PATCH manager 6/7] fix #4239: ui: show selected but non-existing vmids in backup edit Dominik Csapak
2023-03-06 14:23 ` [pve-devel] [PATCH manager 7/7] ui: BackupEdit: refactor edit window into declarative style Dominik Csapak
2023-03-14 10:37 ` [pve-devel] applied: [PATCH manager 0/7] backup edit window improvements Thomas Lamprecht
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox