From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 91E7765755 for ; Thu, 23 Jul 2020 13:03:55 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 5278028708 for ; Thu, 23 Jul 2020 13:03:55 +0200 (CEST) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [212.186.127.180]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS id 5A302286CB for ; Thu, 23 Jul 2020 13:03:53 +0200 (CEST) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 24D1743337 for ; Thu, 23 Jul 2020 13:03:53 +0200 (CEST) From: Dominik Csapak To: pbs-devel@lists.proxmox.com Date: Thu, 23 Jul 2020 13:03:49 +0200 Message-Id: <20200723110351.31882-1-d.csapak@proxmox.com> X-Mailer: git-send-email 2.20.1 MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL -0.001 Adjusted score from AWL reputation of From: address KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment KAM_LAZY_DOMAIN_SECURITY 1 Sending domain does not have any anti-forgery methods NO_DNS_FOR_FROM 0.379 Envelope sender has no MX or A DNS records RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_NONE 0.001 SPF: sender does not publish an SPF Record Subject: [pbs-devel] [PATCH proxmox-backup 1/3] ui: rework DataStore content Panel X-BeenThere: pbs-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Backup Server development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 23 Jul 2020 11:03:55 -0000 instead of having the files as a column, put the files into the tree as a third level with this, we can move the actions into an action column and remove the top buttons (except reload) clicking the download action now downloads directly, so we would not need the download window anymore clicking the browse action, opens the pxar browser like before, but expands and selects (&focus) the selected pxar file also changes the icon of 'signed' to the one to locked but color codes them (singed => greyed out, encrypted => green), similar to what browsers do/did for certificates Signed-off-by: Dominik Csapak --- www/DataStoreContent.js | 255 +++++++++++++++++++------------------- www/Utils.js | 4 +- www/css/ext6-pbs.css | 16 +++ www/window/FileBrowser.js | 11 +- 4 files changed, 156 insertions(+), 130 deletions(-) diff --git a/www/DataStoreContent.js b/www/DataStoreContent.js index a29436f..af5eca7 100644 --- a/www/DataStoreContent.js +++ b/www/DataStoreContent.js @@ -142,9 +142,18 @@ Ext.define('PBS.DataStoreContent', { let data = item.data; data.text = group + '/' + PBS.Utils.render_datetime_utc(data["backup-time"]); - data.leaf = true; + data.leaf = false; data.cls = 'no-leaf-icons'; + data.children = []; + for (const file of data.files) { + file.text = file.filename, + file['crypt-mode'] = PBS.Utils.cryptmap.indexOf(file['crypt-mode']); + file.leaf = true; + + data.children.push(file); + } + children.push(data); } @@ -181,13 +190,12 @@ Ext.define('PBS.DataStoreContent', { Proxmox.Utils.setErrorMask(view, false); }, - onPrune: function() { + onPrune: function(view, rI, cI, item, e, rec) { var view = this.getView(); - let rec = view.selModel.getSelection()[0]; if (!(rec && rec.data)) return; let data = rec.data; - if (data.leaf) return; + if (rec.parentNode.id !== 'root') return; if (!view.datastore) return; @@ -200,18 +208,17 @@ Ext.define('PBS.DataStoreContent', { win.show(); }, - onVerify: function() { + onVerify: function(view, rI, cI, item, e, rec) { var view = this.getView(); if (!view.datastore) return; - let rec = view.selModel.getSelection()[0]; if (!(rec && rec.data)) return; let data = rec.data; let params; - if (data.leaf) { + if (rec.parentNode.id !== 'root') { params = { "backup-type": data["backup-type"], "backup-id": data["backup-id"], @@ -239,75 +246,77 @@ Ext.define('PBS.DataStoreContent', { }); }, - onForget: function() { + onForget: function(view, rI, cI, item, e, rec) { + let me = this; var view = this.getView(); - let rec = view.selModel.getSelection()[0]; if (!(rec && rec.data)) return; let data = rec.data; - if (!data.leaf) return; - if (!view.datastore) return; - console.log(data); + Ext.Msg.show({ + title: gettext('Confirm'), + icon: Ext.Msg.WARNING, + message: Ext.String.format(gettext('Are you sure you want to remove snapshot {0}'), `'${data.text}'`), + buttons: Ext.Msg.YESNO, + defaultFocus: 'no', + callback: function(btn) { + if (btn !== 'yes') { + return; + } - Proxmox.Utils.API2Request({ - params: { - "backup-type": data["backup-type"], - "backup-id": data["backup-id"], - "backup-time": (data['backup-time'].getTime()/1000).toFixed(0), - }, - url: `/admin/datastore/${view.datastore}/snapshots`, - method: 'DELETE', - waitMsgTarget: view, - failure: function(response, opts) { - Ext.Msg.alert(gettext('Error'), response.htmlStatus); + Proxmox.Utils.API2Request({ + params: { + "backup-type": data["backup-type"], + "backup-id": data["backup-id"], + "backup-time": (data['backup-time'].getTime()/1000).toFixed(0), + }, + url: `/admin/datastore/${view.datastore}/snapshots`, + method: 'DELETE', + waitMsgTarget: view, + failure: function(response, opts) { + Ext.Msg.alert(gettext('Error'), response.htmlStatus); + }, + callback: me.reload.bind(me), + }); }, - callback: this.reload.bind(this), }); }, - openBackupFileDownloader: function() { + downloadFile: function(tV, rI, cI, item, e, rec) { let me = this; let view = me.getView(); - let rec = view.selModel.getSelection()[0]; if (!(rec && rec.data)) return; - let data = rec.data; - - Ext.create('PBS.window.BackupFileDownloader', { - baseurl: `/api2/json/admin/datastore/${view.datastore}`, - params: { - 'backup-id': data['backup-id'], - 'backup-type': data['backup-type'], - 'backup-time': (data['backup-time'].getTime()/1000).toFixed(0), - }, - files: data.files, - }).show(); + let data = rec.parentNode.data; + + let file = rec.data.filename; + let params = { + 'backup-id': data['backup-id'], + 'backup-type': data['backup-type'], + 'backup-time': (data['backup-time'].getTime()/1000).toFixed(0), + 'file-name': file, + }; + + let idx = file.lastIndexOf('.'); + let filename = file.slice(0, idx); + let atag = document.createElement('a'); + params['file-name'] = file; + atag.download = filename; + let url = new URL(`/api2/json/admin/datastore/${view.datastore}/download-decoded`, window.location.origin); + for (const [key, value] of Object.entries(params)) { + url.searchParams.append(key, value); + } + atag.href = url.href; + atag.click(); }, - openPxarBrowser: function() { + openPxarBrowser: function(tv, rI, Ci, item, e, rec) { let me = this; let view = me.getView(); - let rec = view.selModel.getSelection()[0]; if (!(rec && rec.data)) return; - let data = rec.data; - - let encrypted = false; - data.files.forEach(file => { - if (file.filename === 'catalog.pcat1.didx' && file['crypt-mode'] === 'encrypt') { - encrypted = true; - } - }); - - if (encrypted) { - Ext.Msg.alert( - gettext('Cannot open Catalog'), - gettext('Only unencrypted Backups can be opened on the server. Please use the client with the decryption key instead.'), - ); - return; - } + let data = rec.parentNode.data; let id = data['backup-id']; let time = data['backup-time']; @@ -320,6 +329,7 @@ Ext.define('PBS.DataStoreContent', { 'backup-id': id, 'backup-time': (time.getTime()/1000).toFixed(0), 'backup-type': type, + archive: rec.data.filename, }).show(); } }, @@ -331,6 +341,55 @@ Ext.define('PBS.DataStoreContent', { dataIndex: 'text', flex: 1 }, + { + header: gettext('Actions'), + xtype: 'actioncolumn', + dataIndex: 'text', + items: [ + { + handler: 'onVerify', + tooltip: gettext('Verify'), + getClass: (v, m, rec) => rec.data.leaf ? 'pmx-hidden' : 'fa fa-search', + isDisabled: (v, r, c, i, rec) => !!rec.data.leaf, + }, + { + handler: 'onPrune', + tooltip: gettext('Prune'), + getClass: (v, m, rec) => rec.parentNode.id ==='root' ? 'fa fa-scissors' : 'pmx-hidden', + isDisabled: (v, r, c, i, rec) => rec.parentNode.id !=='root', + }, + { + handler: 'onForget', + tooltip: gettext('Forget Snapshot'), + getClass: (v, m, rec) => !rec.data.leaf && rec.parentNode.id !== 'root' ? 'fa critical fa-trash-o' : 'pmx-hidden', + isDisabled: (v, r, c, i, rec) => rec.data.leaf || rec.parentNode.id === 'root', + }, + { + handler: 'downloadFile', + tooltip: gettext('Download'), + getClass: (v, m, rec) => rec.data.leaf && rec.data.filename ? 'fa fa-download' : 'pmx-hidden', + isDisabled: (v, r, c, i, rec) => !rec.data.leaf || !rec.data.filename || rec.data['crypt-mode'] > 2, + }, + { + handler: 'openPxarBrowser', + tooltip: gettext('Browse'), + getClass: (v, m, rec) => { + let data = rec.data; + if (data.leaf && data.filename && data.filename.endsWith('pxar.didx')) { + return 'fa fa-folder-open-o'; + } + return 'pmx-hidden'; + }, + isDisabled: (v, r, c, i, rec) => { + let data = rec.data; + return !(data.leaf && + data.filename && + data.filename.endsWith('pxar.didx') && + data['crypt-mode'] < 2); + } + }, + ] + }, { xtype: 'datecolumn', header: gettext('Backup Time'), @@ -344,6 +403,9 @@ Ext.define('PBS.DataStoreContent', { sortable: true, dataIndex: 'size', renderer: (v, meta, record) => { + if (record.data.text === 'client.log.blob' && v === undefined) { + return ''; + } if (v === undefined || v === null) { meta.tdCls = "x-grid-row-loading"; return ''; @@ -366,28 +428,17 @@ Ext.define('PBS.DataStoreContent', { { header: gettext('Encrypted'), dataIndex: 'crypt-mode', - renderer: value => PBS.Utils.cryptText[value] || Proxmox.Utils.unknownText, - }, - { - header: gettext("Files"), - sortable: false, - dataIndex: 'files', - renderer: function(files) { - return files.map((file) => { - let icon = ''; - let size = ''; - let mode = PBS.Utils.cryptmap.indexOf(file['crypt-mode']); - let iconCls = PBS.Utils.cryptIconCls[mode] || ''; - if (iconCls !== '') { - icon = ` `; - } - if (file.size) { - size = ` (${Proxmox.Utils.format_size(file.size)})`; - } - return `${icon}${file.filename}${size}`; - }).join(', '); - }, - flex: 2 + renderer: (v, meta, record) => { + if (v === -1) { + return ''; + } + let iconCls = PBS.Utils.cryptIconCls[v] || ''; + let iconTxt = ""; + if (iconCls) { + iconTxt = ` `; + } + return (iconTxt + PBS.Utils.cryptText[v]) || Proxmox.Utils.unknownText + } }, ], @@ -397,55 +448,5 @@ Ext.define('PBS.DataStoreContent', { iconCls: 'fa fa-refresh', handler: 'reload', }, - '-', - { - xtype: 'proxmoxButton', - text: gettext('Verify'), - disabled: true, - parentXType: 'pbsDataStoreContent', - enableFn: (rec) => !!rec.data && rec.data.size !== null, - handler: 'onVerify', - }, - { - xtype: 'proxmoxButton', - text: gettext('Prune'), - disabled: true, - parentXType: 'pbsDataStoreContent', - enableFn: (rec) => !rec.data.leaf, - handler: 'onPrune', - }, - { - xtype: 'proxmoxButton', - text: gettext('Forget'), - disabled: true, - parentXType: 'pbsDataStoreContent', - handler: 'onForget', - dangerous: true, - confirmMsg: function(record) { - //console.log(record); - let name = record.data.text; - return Ext.String.format(gettext('Are you sure you want to remove snapshot {0}'), `'${name}'`); - }, - enableFn: (rec) => !!rec.data.leaf && rec.data.size !== null, - }, - '-', - { - xtype: 'proxmoxButton', - text: gettext('Download Files'), - disabled: true, - parentXType: 'pbsDataStoreContent', - handler: 'openBackupFileDownloader', - enableFn: (rec) => !!rec.data.leaf && rec.data.size !== null, - }, - { - xtype: "proxmoxButton", - text: gettext('PXAR File Browser'), - disabled: true, - handler: 'openPxarBrowser', - parentXType: 'pbsDataStoreContent', - enableFn: function(record) { - return !!record.data.leaf && record.size !== null && record.data.files.some(el => el.filename.endsWith('pxar.didx')); - }, - } ], }); diff --git a/www/Utils.js b/www/Utils.js index c75a779..dfd10cf 100644 --- a/www/Utils.js +++ b/www/Utils.js @@ -30,8 +30,8 @@ Ext.define('PBS.Utils', { cryptIconCls: [ '', '', - 'certificate', - 'lock', + 'lock faded', + 'lock good', ], calculateCryptMode: function(data) { diff --git a/www/css/ext6-pbs.css b/www/css/ext6-pbs.css index 9a073d3..80a3620 100644 --- a/www/css/ext6-pbs.css +++ b/www/css/ext6-pbs.css @@ -208,3 +208,19 @@ p.logs { .pmx-button-badge.active { background-color: #464d4d; } + +.pmx-hidden { + cursor: default; +} + +.x-action-col-icon.good:before { + color: #21BF4B; +} + +.x-action-col-icon.warning:before { + color: #fc0; +} + +.x-action-col-icon.critical:before { + color: #FF6C59; +} diff --git a/www/window/FileBrowser.js b/www/window/FileBrowser.js index fb1c6b9..ee29fdd 100644 --- a/www/window/FileBrowser.js +++ b/www/window/FileBrowser.js @@ -145,7 +145,16 @@ Ext.define("PBS.window.FileBrowser", { store.load(() => { let root = store.getRoot(); root.expand(); // always expand invisible root node - if (root.childNodes.length === 1) { + if (view.archive) { + let child = root.findChild('text', view.archive); + if (child) { + child.expand(); + setTimeout(function() { + tree.setSelection(child); + tree.getView().focusRow(child); + }, 10); + } + } else if (root.childNodes.length === 1) { root.firstChild.expand(); } }); -- 2.20.1