From: David Riley <d.riley@proxmox.com>
To: pve-devel@lists.proxmox.com
Cc: David Riley <d.riley@proxmox.com>
Subject: [PATCH pve-manager v2 1/1] fix: #4490 ui: backup job window: add search for virtual guest grid
Date: Fri, 27 Mar 2026 11:38:01 +0100 [thread overview]
Message-ID: <20260327103801.104735-2-d.riley@proxmox.com> (raw)
In-Reply-To: <20260327103801.104735-1-d.riley@proxmox.com>
Add:
- Search field for filtering the virtual guest grid by Name or VMID.
- Review toggle, when set the grid only shows selected virtual guests.
- Selection count, counter below the grid showing the number of
selected virtual guests.
Signed-off-by: David Riley <d.riley@proxmox.com>
---
www/manager6/dc/Backup.js | 167 +++++++++++++++++++++++++++++++++++++-
1 file changed, 166 insertions(+), 1 deletion(-)
diff --git a/www/manager6/dc/Backup.js b/www/manager6/dc/Backup.js
index 956a7cdf..1990eb55 100644
--- a/www/manager6/dc/Backup.js
+++ b/www/manager6/dc/Backup.js
@@ -64,11 +64,22 @@ Ext.define('PVE.dc.BackupEdit', {
let vmgrid = me.lookup('vmgrid');
let store = vmgrid.getStore();
+ me.resetSearch();
+
store.clearFilter();
store.filterBy(function (rec) {
return !value || rec.get('node') === value;
});
+ if (value) {
+ let selModel = vmgrid.getSelectionModel();
+ let selections = selModel.getSelection();
+ let hiddenSelections = selections.filter((rec) => rec.get('node') !== value);
+ if (hiddenSelections.length > 0) {
+ selModel.deselect(hiddenSelections, true);
+ }
+ }
+
let mode = me.lookup('modeSelector').getValue();
if (mode === 'all') {
vmgrid.selModel.selectAll(true);
@@ -76,6 +87,8 @@ Ext.define('PVE.dc.BackupEdit', {
if (mode === 'pool') {
me.selectPoolMembers();
}
+
+ me.updateSelectionCount();
},
storageChange: function (f, v) {
@@ -114,6 +127,8 @@ Ext.define('PVE.dc.BackupEdit', {
},
]);
vmgrid.selModel.selectAll(true);
+
+ me.updateSelectionCount();
},
modeChange: function (f, value, oldValue) {
@@ -121,7 +136,12 @@ Ext.define('PVE.dc.BackupEdit', {
let vmgrid = me.lookup('vmgrid');
vmgrid.getStore().removeFilter('poolFilter');
- if (oldValue === 'all' && value !== 'all') {
+ me.resetSearch();
+
+ if (
+ (oldValue === 'all' && value !== 'all') ||
+ (oldValue === 'pool' && (value === 'include' || value === 'exclude'))
+ ) {
vmgrid.getSelectionModel().deselectAll(true);
}
@@ -132,6 +152,8 @@ Ext.define('PVE.dc.BackupEdit', {
if (value === 'pool') {
me.selectPoolMembers();
}
+
+ me.updateSelectionCount();
},
compressionChange: function (f, value, oldValue) {
@@ -181,9 +203,99 @@ Ext.define('PVE.dc.BackupEdit', {
return data;
},
+ searchFn: function (record) {
+ let me = this;
+ let searchQuery = me.searchValue;
+
+ if (!searchQuery) {
+ return true;
+ }
+
+ let name = (record.get('name') ?? '').toLowerCase();
+ let vmid = (record.get('vmid') ?? '').toString();
+
+ return name.includes(searchQuery) || vmid.includes(searchQuery);
+ },
+
+ searchChange: function (_, value) {
+ let me = this;
+ let search = (value ?? '').toLowerCase();
+ let vmgrid = me.lookup('vmgrid');
+ let store = vmgrid.getStore();
+
+ me.searchValue = search;
+
+ if (!search) {
+ store.removeFilter(me.searchFilter);
+ } else {
+ store.addFilter(me.searchFilter);
+ }
+ },
+
+ resetSearch: function () {
+ let me = this;
+
+ me.searchValue = '';
+ me.lookup('searchTextField').setValue('');
+ me.lookup('vmgrid').getStore().removeFilter(me.searchFilter);
+ },
+
+ selectionChange: function (_, selected) {
+ let me = this;
+ let store = me.lookup('vmgrid').getStore();
+
+ if (store.getFilters().contains(me.reviewFilter)) {
+ store.removeFilter(me.reviewFilter);
+ store.addFilter(me.reviewFilter);
+ }
+ me.updateSelectionCount(selected);
+ },
+
+ updateSelectionCount: function (selected) {
+ let me = this;
+ let selection = selected || me.lookup('vmgrid').getSelectionModel().getSelection();
+ let count = selection.length;
+
+ let label = me.lookup('selectionCount');
+ let text = Ext.String.format(gettext('Selected ({0})'), count);
+ label.setText(text);
+ },
+
+ reviewFn: function (record) {
+ let me = this;
+ let vmgrid = me.lookup('vmgrid');
+ let selModel = vmgrid.getSelectionModel();
+ return selModel.isSelected(record);
+ },
+
+ reviewModeChange: function (_, value) {
+ let me = this;
+ let store = me.lookup('vmgrid').getStore();
+
+ me.resetSearch();
+ if (value) {
+ store.addFilter(me.reviewFilter);
+ } else {
+ store.removeFilter(me.reviewFilter);
+ }
+ },
+
init: function (view) {
let me = this;
+ me.searchValue = '';
+ me.searchFilter = new Ext.util.Filter({
+ id: 'search',
+ scope: me,
+ filterFn: me.searchFn,
+ });
+
+ me.reviewFilter = new Ext.util.Filter({
+ id: 'review',
+ scope: me,
+ filterFn: me.reviewFn,
+ });
+
if (view.isCreate) {
me.lookup('modeSelector').setValue('include');
} else {
@@ -250,6 +362,9 @@ Ext.define('PVE.dc.BackupEdit', {
fieldLabel: gettext('Schedule'),
allowBlank: false,
name: 'schedule',
+ listeners: {
+ change: 'resetSearch',
+ },
},
{
xtype: 'proxmoxKVComboBox',
@@ -334,6 +449,7 @@ Ext.define('PVE.dc.BackupEdit', {
xtype: 'vmselector',
reference: 'vmgrid',
height: 300,
+ padding: '0 0 2 0',
name: 'vmid',
disabled: true,
allowBlank: false,
@@ -341,6 +457,55 @@ Ext.define('PVE.dc.BackupEdit', {
bind: {
disabled: '{disableVMSelection}',
},
+ listeners: {
+ selectionChange: 'selectionChange',
+ },
+ getValue: function () {
+ let me = this;
+ let selModel = me.getSelectionModel();
+ let selection = selModel.getSelection();
+ return selection.map((rec) => rec.get('vmid')).join(',');
+ },
+ tbar: {
+ xtype: 'toolbar',
+ items: [
+ {
+ xtype: 'proxmoxtextfield',
+ reference: 'searchTextField',
+ fieldLabel: gettext('Search'),
+ emptyText: 'Name, VMID',
+ flex: 1,
+ margin: '2 4 4 0',
+ labelWidth: 92,
+ enableKeyEvents: true,
+ submitValue: false,
+ listeners: {
+ buffer: 250,
+ change: 'searchChange',
+ },
+ },
+ ],
+ },
+ bbar: {
+ xtype: 'toolbar',
+ padding: '4 0',
+ items: [
+ {
+ xtype: 'tbtext',
+ reference: 'selectionCount',
+ text: Ext.String.format(gettext('Selected ({0})'), 0),
+ },
+ '->',
+ {
+ xtype: 'proxmoxcheckbox',
+ boxLabel: gettext('Review'),
+ submitValue: false,
+ listeners: {
+ change: 'reviewModeChange',
+ },
+ },
+ ],
+ },
},
],
onGetValues: function (values) {
--
2.47.3
prev parent reply other threads:[~2026-03-27 10:38 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-03-27 10:38 [PATCH pve-manager v2 0/1] " David Riley
2026-03-27 10:38 ` David Riley [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260327103801.104735-2-d.riley@proxmox.com \
--to=d.riley@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox