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