From: Shannon Sterz <s.sterz@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [PATCH manager 12/21] ui: node: add panel to manage backups of a host
Date: Fri, 28 Aug 2026 15:30:21 +0200 [thread overview]
Message-ID: <20260828133030.351140-13-s.sterz@proxmox.com> (raw)
In-Reply-To: <20260828133030.351140-1-s.sterz@proxmox.com>
allows creating, editing the notes of, changing the protection status
of, file restoring and removing backups of a single pve node.
Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
---
www/manager6/Makefile | 1 +
www/manager6/node/Config.js | 7 +
www/manager6/node/HostBackup.js | 292 ++++++++++++++++++++++++++++++++
3 files changed, 300 insertions(+)
create mode 100644 www/manager6/node/HostBackup.js
diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index d2ea786be..37f29747d 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -244,6 +244,7 @@ JSSRC= \
node/CmdMenu.js \
node/Config.js \
node/Directory.js \
+ node/HostBackup.js \
node/LVM.js \
node/LVMThin.js \
node/StatusView.js \
diff --git a/www/manager6/node/Config.js b/www/manager6/node/Config.js
index 217ee284c..385b9c6fd 100644
--- a/www/manager6/node/Config.js
+++ b/www/manager6/node/Config.js
@@ -169,6 +169,13 @@ Ext.define('PVE.node.Config', {
if (caps.nodes['Sys.Audit']) {
me.items.push(
+ {
+ xtype: 'pveHostBackup',
+ title: gettext('Host Backup'),
+ iconCls: 'fa fa-floppy-o',
+ itemId: 'nodebackup',
+ nodename: nodename,
+ },
{
xtype: 'proxmoxNodeServiceView',
title: gettext('System'),
diff --git a/www/manager6/node/HostBackup.js b/www/manager6/node/HostBackup.js
new file mode 100644
index 000000000..dcecd873d
--- /dev/null
+++ b/www/manager6/node/HostBackup.js
@@ -0,0 +1,292 @@
+Ext.define('PVE.node.AddBackupPanel', {
+ extend: 'Proxmox.window.Edit',
+ mixins: ['Proxmox.Mixin.CBind'],
+
+ subject: gettext('Host Backup'),
+
+ method: 'POST',
+ showTaskViewer: true,
+ nodename: undefined,
+
+ config: {
+ entry: null,
+ },
+
+ items: [
+ {
+ fieldLabel: gettext('Storage'),
+ name: 'storage',
+ xtype: 'pveStorageSelector',
+ reference: 'backupStorageSelector',
+ storageContent: 'backup',
+ allowBlank: false,
+ cbind: {
+ nodename: '{nodename}',
+ },
+ },
+ ],
+
+ initComponent: function () {
+ let me = this;
+ me.url = `/nodes/${me.nodename}/host-backup`;
+ me.callParent();
+
+ me.lookup('backupStorageSelector')
+ .getStore()
+ .addFilter({ filterFn: (item) => item.data.type === 'pbs' });
+
+ me.setValues(me.getEntry());
+ },
+});
+
+Ext.define('PVE.node.HostBackup', {
+ extend: 'Ext.grid.Panel',
+ alias: ['widget.pveHostBackup'],
+ mixins: ['Proxmox.Mixin.CBind'],
+
+ // onlineHelp: 'chapter_vzdump',
+
+ nodename: undefined,
+ stateful: true,
+ stateId: 'grid-node-backup',
+
+ store: {
+ model: 'pve-storage-content',
+ proxy: {
+ type: 'proxmox',
+ },
+ sorters: [
+ {
+ property: 'vdate',
+ direction: 'DESC',
+ },
+ ],
+ },
+
+ tbar: [
+ {
+ text: gettext('Backup Now'),
+ xtype: 'button',
+ handler: 'createBackupHandler',
+ },
+ '-',
+ {
+ text: gettext('File Restore'),
+ xtype: 'proxmoxButton',
+ handler: 'fileRestoreHandler',
+ disabled: true,
+ },
+ {
+ text: gettext('Edit Notes'),
+ xtype: 'proxmoxButton',
+ handler: 'editNotesHandler',
+ disabled: true,
+ },
+ {
+ text: gettext('Change Protection'),
+ xtype: 'proxmoxButton',
+ handler: 'changeProtectionHandler',
+ disabled: true,
+ },
+ '-',
+ {
+ text: gettext('Remove'),
+ xtype: 'proxmoxStdRemoveButton',
+ dangerous: true,
+ customConfirmationMessage: gettext(
+ 'Are you sure you want to remove {0}. This will permanently erase it.',
+ ),
+ getRecordName: (rec) => rec.data.volid,
+ callback: function () {
+ this.up('grid').getStore().load();
+ },
+ getUrl: function (rec) {
+ let grid = this.up('grid');
+ let nodename = grid.nodename;
+ let storage = grid.lookup('backupStorageSelector').getValue();
+ let volid = rec.data.volid;
+ return `/nodes/${nodename}/storage/${storage}/content/${volid}`;
+ },
+ },
+ '->',
+ {
+ fieldLabel: gettext('Storage'),
+ xtype: 'pveStorageSelector',
+ reference: 'backupStorageSelector',
+ labelAlign: 'right',
+ storageContent: 'backup',
+ allowBlank: false,
+ listeners: {
+ change: 'changeStorageHandler',
+ },
+ cbind: {
+ nodename: '{nodename}',
+ },
+ },
+ ],
+
+ listeners: {
+ itemdblclick: 'dblclickHandler',
+ },
+
+ columns: [
+ {
+ header: gettext('Name'),
+ flex: 2,
+ sortable: true,
+ renderer: PVE.Utils.render_storage_content,
+ dataIndex: 'volid',
+ },
+ {
+ header: gettext('Notes'),
+ dataIndex: 'notes',
+ flex: 1,
+ renderer: Ext.htmlEncode,
+ },
+ {
+ 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: 'vdate',
+ },
+ {
+ 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,
+ },
+ {
+ // TRANSLATORS: The state of the verification task
+ header: gettext('Verify State'),
+ dataIndex: 'verification',
+ renderer: PVE.Utils.render_backup_verification,
+ },
+ ],
+
+ controller: {
+ dblclickHandler: (element, rec) => {
+ let storage = element.up('grid').lookup('backupStorageSelector').getValue();
+
+ Ext.create('Proxmox.window.FileBrowser', {
+ title: gettext('File Restore') + ' - ' + rec.data.text,
+ autoShow: true,
+ listURL: `/api2/json/nodes/localhost/storage/${storage}/file-restore/list`,
+ downloadURL: `/api2/json/nodes/localhost/storage/${storage}/file-restore/download`,
+ extraParams: {
+ volume: rec.data.volid,
+ },
+ });
+ },
+ createBackupHandler: (button, _event) => {
+ let grid = button.up('grid');
+
+ Ext.create('PVE.node.AddBackupPanel', {
+ autoShow: true,
+ isCreate: true,
+ nodename: grid.nodename,
+ listeners: {
+ close: () => grid.getStore().load(),
+ },
+ });
+ },
+ fileRestoreHandler: function (button, _event, rec) {
+ if (!rec) {
+ console.warn('No host backup selected!');
+ } else {
+ this.dblclickHandler(button, rec);
+ }
+ },
+ editNotesHandler: (button, _event, rec) => {
+ if (!rec) {
+ console.warn('No host backup selected!');
+ return;
+ }
+
+ let grid = button.up('grid');
+ let nodename = grid.nodename;
+ let storage = grid.lookup('backupStorageSelector').getValue();
+ let volid = rec.data.volid;
+
+ Ext.create('Proxmox.window.Edit', {
+ title: gettext('Notes'),
+ autoShow: true,
+ width: 600,
+ height: 400,
+ layout: 'fit',
+ autoLoad: true,
+ resizable: true,
+ url: `/api2/extjs/nodes/${nodename}/storage/${storage}/content/${volid}`,
+ items: [
+ {
+ xtype: 'textarea',
+ layout: 'fit',
+ name: 'notes',
+ height: '100%',
+ },
+ ],
+ listeners: {
+ close: () => grid.getStore().load(),
+ },
+ });
+ },
+ changeProtectionHandler: (button, _event, rec) => {
+ if (!rec) {
+ console.warn('No host backup selected!');
+ return;
+ }
+
+ let grid = button.up('grid');
+ let nodename = grid.nodename;
+ let storage = grid.lookup('backupStorageSelector').getValue();
+ let volid = rec.data.volid;
+
+ Proxmox.Utils.API2Request({
+ url: `/api2/extjs/nodes/${nodename}/storage/${storage}/content/${volid}`,
+ method: 'PUT',
+ waitMsgTarget: grid,
+ params: {
+ protected: rec.data.protected ? 0 : 1,
+ },
+ failure: (response) => Ext.Msg.alert('Error', response.htmlStatus),
+ success: () => grid.getStore().load(),
+ });
+ },
+ changeStorageHandler: (selector, value) => {
+ let store = selector.up('grid').getStore();
+ let url = `/api2/json/nodes/${selector.nodename}/host-backup?storage=${value}`;
+
+ store.getProxy().setUrl(url);
+ store.load();
+ },
+ },
+
+ initComponent: function () {
+ let me = this;
+ me.callParent();
+ Proxmox.Utils.monStoreErrors(me.view, me.store, true);
+
+ // filter out non-pbs storages; the store only exist after
+ // `initComponent` of the selector
+ me.lookup('backupStorageSelector')
+ .getStore()
+ .addFilter({ filterFn: (item) => item.data.type === 'pbs' });
+ },
+});
--
2.47.3
next prev parent reply other threads:[~2026-08-28 13:32 UTC|newest]
Thread overview: 22+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-28 13:30 [RFC cluster/common/container/docs/installer/manager 00/21] add rudimentary host backup mechanism Shannon Sterz
2026-08-28 13:30 ` [PATCH cluster 01/21] pmxcfs: status: fix formatting of parameters in checked_mkdir() Shannon Sterz
2026-08-28 13:30 ` [PATCH cluster 02/21] pmxcfs: correctly log message when directory can't be created Shannon Sterz
2026-08-28 13:30 ` [PATCH cluster 03/21] pmxcfs: add live backup capability Shannon Sterz
2026-08-28 13:30 ` [PATCH cluster 04/21] pmxcfs: add ability to query backup progress Shannon Sterz
2026-08-28 13:30 ` [PATCH common 05/21] systemd: move parse_os_release() helper to PVE::Systemd Shannon Sterz
2026-08-28 13:30 ` [PATCH container 06/21] setup: use parse_os_release from PVE::Systemd Shannon Sterz
2026-08-28 13:30 ` [PATCH manager 07/21] jobs/api: add basic host backup job logic Shannon Sterz
2026-08-28 13:30 ` [PATCH manager 08/21] api: cluster: add endpoints for manage host backup jobs Shannon Sterz
2026-08-28 13:30 ` [PATCH manager 09/21] api: node: add endpoints for listing backups for a node Shannon Sterz
2026-08-28 13:30 ` [PATCH manager 10/21] api: host backup: include global, disk and network options for restore Shannon Sterz
2026-08-28 13:30 ` [PATCH manager 11/21] api: host backup: add warnings in case zfs snapdir is disabled Shannon Sterz
2026-08-28 13:30 ` Shannon Sterz [this message]
2026-08-28 13:30 ` [PATCH manager 13/21] ui: dc: add panel for managing host backup jobs Shannon Sterz
2026-08-28 13:30 ` [PATCH installer 14/21] bump proxmox-installer-types to 0.2 Shannon Sterz
2026-08-28 13:30 ` [PATCH installer 15/21] make tidy and clean up whitespace in unconfigured.sh Shannon Sterz
2026-08-28 13:30 ` [PATCH installer 16/21] installer-common: add option to verify TLS connections via callback Shannon Sterz
2026-08-28 13:30 ` [PATCH installer 17/21] low-level-installer: add support for restoring backups Shannon Sterz
2026-08-28 13:30 ` [PATCH installer 18/21] installer-common/tui-installer: implement restore tui Shannon Sterz
2026-08-28 13:30 ` [PATCH installer 19/21] unconfigured: add restore mode to unconfigured.sh Shannon Sterz
2026-08-28 13:30 ` [PATCH installer 20/21] tui-installer: unmount a potentially mounted backup on abort Shannon Sterz
2026-08-28 13:30 ` [PATCH docs 21/21] examples: add example hook script for host backup jobs Shannon Sterz
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=20260828133030.351140-13-s.sterz@proxmox.com \
--to=s.sterz@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