* [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 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.