public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Matthias Heiserer <m.heiserer@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [PATCH manager 1/5 v2] Storage GUI: Rewrite backup content view as TreePanel.
Date: Fri, 18 Mar 2022 14:52:22 +0100	[thread overview]
Message-ID: <20220318135226.2360890-2-m.heiserer@proxmox.com> (raw)
In-Reply-To: <20220318135226.2360890-1-m.heiserer@proxmox.com>

Should be easier to read/use than the current flat list.
Backups are grouped by ID and type, so in case there are backups
with ID 100 for both CT and VM, this would create two separate
groups in the UI.
Date and size of group are taken from the latest backup.
Notes, Protection, Encrypted, and Verify State stay as default
value empty, empty, No, and None, respectively.

Code adapted from the existing backup view and the pbs
datastore content, where appropriate.

Signed-off-by: Matthias Heiserer <m.heiserer@proxmox.com>
---
Changes from v1: 

-add BackupNow button to create backups when not used for storage

-Display combined size of backups for groups, except for PBS where
deduplication happens

-remove timeout for API call

-inform window/restore if storage is PBS

-use get_backup_type helper

-some bug fixes, e.g. text was used instead of volid

-remove groupField, doesn't do anything for TreeStore

-only sort by text, ie. `(qemu|lxc)/<VMID>`

-use render_storage_content helper for displaying backup group

-remove content from store as it's never used. everything is
 content-type backup because we get the data from API
 
-group by backuptype rather than format, so that e.g tar and tar.zst
 are in the same group.
 
-code cleanup: reorder statements, remove one-character variable,
 remove debug log, rename this to me, change filtering, rename
 notPBS to isPBS
 
-add text field to model. fix date field in model

-remember expanded nodes and selection

-rename backup model

-use filterer only with correct store

 
 www/manager6/storage/BackupView.js | 702 +++++++++++++++++++++--------
 1 file changed, 511 insertions(+), 191 deletions(-)

diff --git a/www/manager6/storage/BackupView.js b/www/manager6/storage/BackupView.js
index 2328c0fc..cbb1624b 100644
--- a/www/manager6/storage/BackupView.js
+++ b/www/manager6/storage/BackupView.js
@@ -1,223 +1,543 @@
-Ext.define('PVE.storage.BackupView', {
-    extend: 'PVE.storage.ContentView',
+Ext.define('PVE.storage.BackupModel', {
+    extend: 'Ext.data.Model',
+    fields: [
+	{
+	    name: 'ctime',
+	    type: 'date',
+	    dateFormat: 'timestamp',
+	},
+	'format',
+	'volid',
+	'vmid',
+	'size',
+	'protected',
+	'notes',
+	'text',
+    ],
+});
 
+
+Ext.define('PVE.storage.BackupView', {
+    extend: 'Ext.tree.Panel',
     alias: 'widget.pveStorageBackupView',
+    mixins: ['Proxmox.Mixin.CBind'],
+    rootVisible: false,
 
-    showColumns: ['name', 'notes', 'protected', 'date', 'format', 'size'],
+    title: gettext('Content'),
 
-    initComponent: function() {
-	var me = this;
+    cbindData: function(initialCfg) {
+	this.isPBS = initialCfg.pluginType === 'pbs';
+	return {};
+    },
+    isStorage: false,
 
-	var nodename = me.nodename = me.pveSelNode.data.node;
-	if (!nodename) {
-	    throw "no node name specified";
-	}
+    controller: {
+	xclass: 'Ext.app.ViewController',
 
-	var storage = me.storage = me.pveSelNode.data.storage;
-	if (!storage) {
-	    throw "no storage ID specified";
-	}
+	groupnameHelper: function(item) {
+	    if (item.vmid) {
+		return PVE.Utils.get_backup_type(item.volid, item.format) + `/${item.vmid}`;
+	    } else {
+		return 'Other';
+	    }
+	},
 
-	me.content = 'backup';
+	init: function(view) {
+	    let me = this;
+	    me.storage = view?.pveSelNode?.data?.storage;
+	    me.nodename = view.nodename || view.pveSelNode.data.node;
+	    me.vmid = view.pveSelNode.data.vmid;
+	    me.vmtype = view.pveSelNode.data.type;
 
-	var sm = me.sm = Ext.create('Ext.selection.RowModel', {});
+	    me.store = Ext.create('Ext.data.Store', {
+		model: 'PVE.storage.BackupModel',
+	    });
+	    me.store.on('load', me.onLoad, me);
+	    view.getStore().setConfig('filterer', 'bottomup');
+	    view.getStore().setSorters(['text']);
 
-	var reload = function() {
-	    me.store.load();
-	};
+	    if (me.vmid) {
+		me.getView().getStore().filter({
+		    property: 'vmid',
+		    value: me.vmid,
+		    exactMatch: true,
+		});
+	    } else {
+		me.lookup('storagesel').setVisible(false);
+		me.lookup('backupNowButton').setVisible(false);
+	    }
+	    Proxmox.Utils.monStoreErrors(view, me.store);
+	},
 
-	let pruneButton = Ext.create('Proxmox.button.Button', {
-	    text: gettext('Prune group'),
-	    disabled: true,
-	    selModel: sm,
-	    setBackupGroup: function(backup) {
-		if (backup) {
-		    let name = backup.text;
-		    let vmid = backup.vmid;
-		    let format = backup.format;
+	onLoad: function(store, records, success, operation) {
+	    let me = this;
+	    let view = me.getView();
+	    let selection = view.getSelection()?.[0];
+	    selection = selection?.parentNode?.data?.text +selection?.data?.volid;
 
-		    let vmtype;
-		    if (name.startsWith('vzdump-lxc-') || format === "pbs-ct") {
-			vmtype = 'lxc';
-		    } else if (name.startsWith('vzdump-qemu-') || format === "pbs-vm") {
-			vmtype = 'qemu';
+	    let expanded = {};
+	    view.getRootNode().cascadeBy({
+		before: item => {
+		    if (item.isExpanded() && !item.data.leaf) {
+			let id = item.data.text;
+			expanded[id] = true;
+			return true;
 		    }
+		    return false;
+		},
+		after: Ext.emptyFn,
+	    });
+	    let groups = me.getRecordGroups(records, expanded);
 
-		    if (vmid && vmtype) {
-			this.setText(gettext('Prune group') + ` ${vmtype}/${vmid}`);
-			this.vmid = vmid;
-			this.vmtype = vmtype;
-			this.setDisabled(false);
-			return;
-		    }
+	    for (const item of records.map(i => i.data)) {
+		item.text = item.volid;
+		item.leaf = true;
+		item.iconCls = 'fa-file-o';
+		groups[me.groupnameHelper(item)].children.push(item);
+		groups[me.groupnameHelper(item)].size += item.size;
+	    }
+
+	    for (let [_name, group] of Object.entries(groups)) {
+		let children = group.children;
+		let latest = children.reduce((l, r) => l.ctime > r.ctime ? l : r);
+		group.ctime = latest.ctime;
+		if (view.isPBS) {
+		    group.size = latest.size;
 		}
-		this.setText(gettext('Prune group'));
-		this.vmid = null;
-		this.vmtype = null;
-		this.setDisabled(true);
-	    },
-	    handler: function(b, e, rec) {
-		let win = Ext.create('PVE.window.Prune', {
-		    nodename: nodename,
-		    storage: storage,
-		    backup_id: this.vmid,
-		    backup_type: this.vmtype,
+		let num_verified = children.reduce((l, r) => l + r.verification === 'ok', 0);
+		group.verified = num_verified / children.length;
+	    }
+
+	    let children = [];
+	    Object.entries(groups).forEach(e => children.push(e[1]));
+	    view.setRootNode({
+		expanded: true,
+		children: children,
+	    });
+
+	    if (selection) {
+		let rootnode = view.getRootNode();
+		let selected;
+		rootnode.cascade(node => {
+		    if (selected) {return false;} // skip if already found
+		    let id = node.parentNode?.data?.text + node.data?.volid;
+		    if (id === selection) {
+			selected = node;
+			return false;
+		    }
+		    return true;
 		});
-		win.show();
-		win.on('destroy', reload);
-	    },
-	});
+		view.setSelection(selected);
+		view.getView().focusRow(selected);
+	    }
+	    Proxmox.Utils.setErrorMask(view, false);
+	},
 
-	me.on('selectionchange', function(model, srecords, eOpts) {
-	    if (srecords.length === 1) {
-		pruneButton.setBackupGroup(srecords[0].data);
-	    } else {
-		pruneButton.setBackupGroup(null);
+	reload: function() {
+	    let me = this;
+	    let view = me.getView();
+	    if (!view.store || !me.store) {
+		console.warn('cannot reload, no store(s)');
+		return;
 	    }
-	});
-
-	let isPBS = me.pluginType === 'pbs';
-
-	me.tbar = [
-	    {
-		xtype: 'proxmoxButton',
-		text: gettext('Restore'),
-		selModel: sm,
-		disabled: true,
-		handler: function(b, e, rec) {
-		    var vmtype;
-		    if (PVE.Utils.volume_is_qemu_backup(rec.data.volid, rec.data.format)) {
-			vmtype = 'qemu';
-		    } else if (PVE.Utils.volume_is_lxc_backup(rec.data.volid, rec.data.format)) {
-			vmtype = 'lxc';
-		    } else {
-			return;
-		    }
 
-		    var win = Ext.create('PVE.window.Restore', {
-			nodename: nodename,
-			volid: rec.data.volid,
-			volidText: PVE.Utils.render_storage_content(rec.data.volid, {}, rec),
-			vmtype: vmtype,
-			isPBS: isPBS,
-		    });
-		    win.show();
-		    win.on('destroy', reload);
-		},
-	    },
-	];
-	if (isPBS) {
-	    me.tbar.push({
-		xtype: 'proxmoxButton',
-		text: gettext('File Restore'),
-		disabled: true,
-		selModel: sm,
-		handler: function(b, e, rec) {
-		    let isVMArchive = PVE.Utils.volume_is_qemu_backup(rec.data.volid, rec.data.format);
-		    Ext.create('Proxmox.window.FileBrowser', {
-			title: gettext('File Restore') + " - " + rec.data.text,
-			listURL: `/api2/json/nodes/localhost/storage/${me.storage}/file-restore/list`,
-			downloadURL: `/api2/json/nodes/localhost/storage/${me.storage}/file-restore/download`,
-			extraParams: {
-			    volume: rec.data.volid,
-			},
-			archive: isVMArchive ? 'all' : undefined,
-			autoShow: true,
-		    });
+	    if (!me.storage) {
+		Proxmox.Utils.setErrorMask(view, true);
+		return;
+	    }
+
+	    let url = `/api2/json/nodes/${me.nodename}/storage/${me.storage}/content`;
+	    me.store.setProxy({
+		type: 'proxmox',
+		url: url,
+		extraParams: {
+		    content: 'backup',
 		},
 	    });
-	}
-	me.tbar.push(
-	    {
-		xtype: 'proxmoxButton',
-		text: gettext('Show Configuration'),
-		disabled: true,
-		selModel: sm,
-		handler: function(b, e, rec) {
-		    var win = Ext.create('PVE.window.BackupConfig', {
-			volume: rec.data.volid,
-			pveSelNode: me.pveSelNode,
-		    });
-
-		    win.show();
+
+	    me.store.load();
+	    Proxmox.Utils.monStoreErrors(view, me.store);
+	},
+
+	getRecordGroups: function(records, expanded) {
+	    let groups = {};
+	    for (const item of records) {
+		const groupName = this.groupnameHelper(item.data);
+		groups[groupName] = {
+		    vmid: item.data.vmid,
+		    leaf: false,
+		    children: [],
+		    expanded: !!expanded[groupName],
+		    text: groupName,
+		    ctime: 0,
+		    format: item.data.format,
+		    volid: item.data.volid, // to preserve backup type information
+		    size: 0,
+		    iconCls: PVE.Utils.get_backup_type_icon_cls(item.data.volid, item.data.format),
+		};
+	    }
+	    return groups;
+	},
+
+	restoreHandler: function(button, event, rec) {
+	    let me = this;
+	    let vmtype = PVE.Utils.get_backup_type(rec.data.volid, rec.data.format);
+	    let win = Ext.create('PVE.window.Restore', {
+		nodename: me.nodename,
+		volid: rec.data.volid,
+		volidText: PVE.Utils.render_storage_content(rec.data.volid, {}, rec),
+		vmtype: vmtype,
+		isPBS: me.view.isPBS,
+	    });
+	    win.on('destroy', () => me.reload());
+	    win.show();
+	},
+
+	restoreFilesHandler: function(button, event, rec) {
+	    let me = this;
+	    let isVMArchive = PVE.Utils.volume_is_qemu_backup(rec.data.volid, rec.data.format);
+	    Ext.create('Proxmox.window.FileBrowser', {
+		title: gettext('File Restore') + " - " + rec.data.text,
+		listURL: `/api2/json/nodes/localhost/storage/${me.storage}/file-restore/list`,
+		downloadURL: `/api2/json/nodes/localhost/storage/${me.storage}/file-restore/download`,
+		extraParams: {
+		    volume: rec.data.volid,
 		},
-	    },
-	    {
-		xtype: 'proxmoxButton',
-		text: gettext('Edit Notes'),
-		disabled: true,
-		selModel: sm,
-		handler: function(b, e, rec) {
-		    let volid = rec.data.volid;
-		    Ext.create('Proxmox.window.Edit', {
-			autoLoad: true,
-			width: 600,
-			height: 400,
-			resizable: true,
-			title: gettext('Notes'),
-			url: `/api2/extjs/nodes/${nodename}/storage/${me.storage}/content/${volid}`,
+		archive: isVMArchive ? 'all' : undefined,
+		autoShow: true,
+	    });
+	},
+
+	showConfigurationHandler: function(button, event, rec) {
+	    let win = Ext.create('PVE.window.BackupConfig', {
+		volume: rec.data.volid,
+		pveSelNode: this.view.pveSelNode,
+	    });
+	    win.show();
+	},
+
+	editNotesHandler: function(button, event, rec) {
+	    let me = this;
+	    let volid = rec.data.volid;
+	    Ext.create('Proxmox.window.Edit', {
+		autoLoad: true,
+		width: 600,
+		height: 400,
+		resizable: true,
+		title: gettext('Notes'),
+		url: `/api2/extjs/nodes/${me.nodename}/storage/${me.storage}/content/${volid}`,
+		layout: 'fit',
+		items: [
+		    {
+			xtype: 'textarea',
 			layout: 'fit',
-			items: [
-			    {
-				xtype: 'textarea',
-				layout: 'fit',
-				name: 'notes',
-				height: '100%',
-			    },
-			],
-			listeners: {
-			    destroy: () => reload(),
-			},
-		    }).show();
+			name: 'notes',
+			height: '100%',
+		    },
+		],
+		listeners: {
+		    destroy: () => me.reload(),
 		},
-	    },
-	    {
-		xtype: 'proxmoxButton',
-		text: gettext('Change Protection'),
-		disabled: true,
-		handler: function(button, event, record) {
-		    const volid = record.data.volid;
-		    Proxmox.Utils.API2Request({
-			url: `/api2/extjs/nodes/${nodename}/storage/${me.storage}/content/${volid}`,
-			method: 'PUT',
-			waitMsgTarget: me,
-			params: { 'protected': record.data.protected ? 0 : 1 },
-			failure: (response) => Ext.Msg.alert('Error', response.htmlStatus),
-			success: (response) => reload(),
-		    });
+	    }).show();
+	},
+
+	changeProtectionHandler: function(button, event, rec) {
+	    let me = this;
+	    const volid = rec.data.volid;
+	    Proxmox.Utils.API2Request({
+		url: `/api2/extjs/nodes/${me.nodename}/storage/${me.storage}/content/${volid}`,
+		method: 'PUT',
+		waitMsgTarget: button,
+		params: { 'protected': rec.data.protected ? 0 : 1 },
+		failure: (response) => Ext.Msg.alert('Error', response.htmlStatus),
+		success: (_) => me.reload(),
+	    });
+	},
+
+	pruneGroupHandler: function(button, event, rec) {
+	    let me = this;
+	    let vmtype = PVE.Utils.get_backup_type(rec.data.volid, rec.data.format);
+	    Ext.create('PVE.window.Prune', {
+		nodename: me.nodename,
+		storage: me.storage,
+		backup_id: rec.data.vmid,
+		backup_type: vmtype,
+		rec: rec,
+		listeners: {
+		    destroy: () => me.reload(),
 		},
-	    },
-	    '-',
-	    pruneButton,
-	);
-
-	if (isPBS) {
-	    me.extraColumns = {
-		encrypted: {
-		    header: gettext('Encrypted'),
-		    dataIndex: 'encrypted',
-		    renderer: PVE.Utils.render_backup_encryption,
+	    }).show();
+	},
+
+	removeHandler: function(button, event, rec) {
+	    let me = this;
+	    const volid = rec.data.volid;
+	    Proxmox.Utils.API2Request({
+		url: `/nodes/${me.nodename}/storage/${me.storage}/content//${volid}`,
+		method: 'DELETE',
+		callback: () => me.reload(),
+		failure: response => Ext.Msg.alert(gettext('Error'), response.htmlStatus),
+	    });
+	},
+
+	searchKeyupFn: function(field) {
+	    let me = this;
+	    me.getView().getStore().getFilters().removeByKey('volid');
+	    me.getView().getStore().filter([
+		{
+		    property: 'volid',
+		    value: field.getValue(),
+		    anyMatch: true,
+		    caseSensitive: false,
+		    id: 'volid',
 		},
-		verification: {
-		    header: gettext('Verify State'),
-		    dataIndex: 'verification',
-		    renderer: PVE.Utils.render_backup_verification,
+	    ]);
+	},
+
+	searchClearHandler: function(field) {
+	    field.triggers.clear.setVisible(false);
+	    field.setValue(this.originalValue);
+	    this.getView().getStore().clearFilter();
+	},
+
+	searchChangeFn: function(field, newValue, oldValue) {
+	    if (newValue !== field.originalValue) {
+		field.triggers.clear.setVisible(true);
+	    }
+	},
+
+	storageSelectorBoxReady: function(selector, width, height, eOpts) {
+	    selector.setNodename(this.nodename);
+	},
+
+	storageSelectorChange: function(self, newValue, oldValue, eOpts) {
+	    let me = this;
+	    me.storage = newValue;
+	    me.getView().getSelectionModel().deselectAll();
+	    me.reload();
+	},
+
+	backupNowHandler: function(button, event) {
+	    let me = this;
+	    Ext.create('PVE.window.Backup', {
+		nodename: me.nodename,
+		vmid: me.vmid,
+		vmtype: me.vmtype,
+		storage: me.storage,
+		listeners: {
+		    close: () => me.reload(),
 		},
-	    };
-	}
+	    }).show();
+	},
+    },
+
+    columns: [
+	{
+	    xtype: 'treecolumn',
+	    header: gettext("Backup Group"),
+	    dataIndex: 'text',
+	    renderer: function(value, _metadata, record) {
+		if (record.phantom) { return value; }
+		return PVE.Utils.render_storage_content(...arguments);
+	    },
+	    flex: 2,
+	},
+	{
+	    header: gettext('Notes'),
+	    flex: 1,
+	    renderer: Ext.htmlEncode,
+	    dataIndex: 'notes',
+	},
+	{
+	    header: `<i class="fa fa-shield"></i>`,
+	    tooltip: gettext('Protected'),
+	    width: 30,
+	    renderer: v => v ? `<i data-qtip="${gettext('Protected')}" class="fa fa-shield"></i>` : '',
+	    sorter: (a, b) => (b.data.protected || 0) - (a.data.protected || 0),
+	    dataIndex: 'protected',
+	},
+	{
+	    header: gettext('Date'),
+	    width: 150,
+	    dataIndex: 'ctime',
+	    xtype: 'datecolumn',
+	    format: 'Y-m-d H:i:s',
+	},
+	{
+	    header: gettext('Format'),
+	    width: 100,
+	    dataIndex: 'format',
+	},
+	{
+	    header: gettext('Size'),
+	    width: 100,
+	    renderer: Proxmox.Utils.format_size,
+	    dataIndex: 'size',
+	},
+	{
+	    header: gettext('Encrypted'),
+	    dataIndex: 'encrypted',
+	    renderer: PVE.Utils.render_backup_encryption,
+	    cbind: {
+		hidden: '{!isPBS}',
+	    },
+	},
+	{
+	    header: gettext('Verify State'),
+	    dataIndex: 'verification',
+	    renderer: PVE.Utils.render_backup_verification,
+	    cbind: {
+		hidden: '{!isPBS}',
+	    },
+	},
+    ],
+
+    tbar: [
+	{
+	    xtype: 'button',
+	    text: gettext('Backup now'),
+	    handler: 'backupNowHandler',
+	    reference: 'backupNowButton',
+	},
+	{
+	    xtype: 'proxmoxButton',
+	    text: gettext('Restore'),
+	    handler: 'restoreHandler',
+	    parentXType: "treepanel",
+	    disabled: true,
+	    enableFn: record => record.phantom === false,
+	},
+	{
+	    xtype: 'proxmoxButton',
+	    text: gettext('File Restore'),
+	    handler: 'restoreFilesHandler',
+	    cbind: {
+		hidden: '{!isPBS}',
+	    },
+	    parentXType: "treepanel",
+	    disabled: true,
+	    enableFn: record => record.phantom === false,
+	},
+	{
+	    xtype: 'proxmoxButton',
+	    text: gettext('Show Configuration'),
+	    handler: 'showConfigurationHandler',
+	    parentXType: "treepanel",
+	    disabled: true,
+	    enableFn: record => record.phantom === false,
+	},
+	{
+	    xtype: 'proxmoxButton',
+	    text: gettext('Edit Notes'),
+	    handler: 'editNotesHandler',
+	    parentXType: "treepanel",
+	    disabled: true,
+	    enableFn: record => record.phantom === false,
+	},
+	{
+	    xtype: 'proxmoxButton',
+	    text: gettext('Change Protection'),
+	    handler: 'changeProtectionHandler',
+	    parentXType: "treepanel",
+	    disabled: true,
+	    enableFn: record => record.phantom === false,
+	},
+	'-',
+	{
+	    xtype: 'proxmoxButton',
+	    text: gettext('Prune group'),
+	    setBackupGroup: function(backup) {
+		let me = this;
+		if (backup) {
+		    let volid = backup.volid;
+		    let vmid = backup.vmid;
+		    let format = backup.format;
 
-	me.callParent();
+		    let vmtype = PVE.Utils.get_backup_type(volid, format);
+		    if (vmid && vmtype) {
+			me.setText(gettext('Prune group') + ` ${vmtype}/${vmid}`);
+			me.vmid = vmid;
+			me.vmtype = vmtype;
+			me.setDisabled(false);
+			return;
+		    }
+		}
+		me.setText(gettext('Prune group'));
+		me.vmid = null;
+		me.vmtype = null;
+		me.setDisabled(true);
+	    },
+	    handler: 'pruneGroupHandler',
+	    parentXType: "treepanel",
+	    disabled: true,
+	    reference: 'pruneButton',
+	    enableFn: () => true,
+	},
+	{
+	    xtype: 'proxmoxButton',
+	    text: gettext('Remove'),
+	    handler: 'removeHandler',
+	    parentXType: 'treepanel',
+	    disabled: true,
+	    enableFn: record => record.phantom === false && !record?.data?.protected,
+	    confirmMsg: function(rec) {
+		let name = rec.data.text;
+		return Ext.String.format(gettext('Are you sure you want to remove entry {0}'), `'${name}'`);
+	    },
+	},
+	'->',
+	{
+	    xtype: 'pveStorageSelector',
+	    fieldLabel: gettext('Storage'),
+	    storageContent: 'backup',
+	    reference: 'storagesel',
+	    listeners: {
+		change: 'storageSelectorChange',
+		boxready: 'storageSelectorBoxReady',
+	    },
 
-	me.store.getSorters().clear();
-	me.store.setSorters([
-	    {
-		property: 'vmid',
-		direction: 'ASC',
+	},
+	gettext('Search') + ':',
+	' ',
+	{
+	    xtype: 'textfield',
+	    width: 200,
+	    enableKeyEvents: true,
+	    emptyText: gettext('Name, Format'),
+	    listeners: {
+		keyup: {
+		    buffer: 500,
+		    fn: 'searchKeyupFn',
+		},
+		change: 'searchChangeFn',
 	    },
-	    {
-		property: 'vdate',
-		direction: 'DESC',
+	    triggers: {
+		clear: {
+		    cls: 'pmx-clear-trigger',
+		    weight: -1,
+		    hidden: true,
+		    handler: 'searchClearHandler',
+		},
 	    },
-	]);
+	},
+	],
+
+    listeners: {
+	activate: function() {
+	    let me = this;
+	    // only load on first activate to not load every tab switch
+	    if (!me.firstLoad) {
+		me.getController().reload();
+		me.firstLoad = true;
+	    }
+	},
+	selectionchange: function(model, srecords, eOpts) {
+	    let pruneButton = this.getController().lookup('pruneButton');
+	    if (srecords.length === 1) {
+		pruneButton.setBackupGroup(srecords[0].data);
+	    } else {
+		pruneButton.setBackupGroup(null);
+	    }
+	},
     },
 });
-- 
2.30.2





  reply	other threads:[~2022-03-18 13:52 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-03-18 13:52 [pve-devel] [PATCH manager 0/5 v2] BackupView " Matthias Heiserer
2022-03-18 13:52 ` Matthias Heiserer [this message]
2022-03-22  8:42   ` [pve-devel] [PATCH manager 1/5 v2] Storage GUI: Rewrite backup content view " Fabian Ebner
2022-03-22 12:38     ` Thomas Lamprecht
2022-03-30 12:59     ` Matthias Heiserer
2022-03-31  6:36       ` Fabian Ebner
2022-03-31  6:56         ` Thomas Lamprecht
2022-03-18 13:52 ` [pve-devel] [PATCH manager 2/5 v2] GUI: Utils: Helpers for backup type and icon Matthias Heiserer
2022-03-18 14:51   ` Thomas Lamprecht
2022-03-18 13:52 ` [pve-devel] [PATCH manager 3/5 v2] Backup GUI: Use the new storage/BackupView instead of grid/BackupView Matthias Heiserer
2022-03-22  8:47   ` Fabian Ebner
2022-03-18 13:52 ` [pve-devel] [PATCH manager 4/5 v2] Remove grid/backupview as it got replaced by storage/backupview Matthias Heiserer
2022-03-22  8:47   ` Fabian Ebner
2022-03-18 13:52 ` [pve-devel] [PATCH manager 5/5 v2] Storage ContentView: Remove dead code Matthias Heiserer
2022-03-22  8:48   ` Fabian Ebner

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=20220318135226.2360890-2-m.heiserer@proxmox.com \
    --to=m.heiserer@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
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal