public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
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





  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
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal