ui: rework DataStore content Panel

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 <d.csapak@proxmox.com>
This commit is contained in:
Dominik Csapak 2020-07-23 13:03:49 +02:00 committed by Dietmar Maurer
parent bccdc5fa04
commit 3e395378bc
4 changed files with 159 additions and 133 deletions

View File

@ -142,9 +142,18 @@ Ext.define('PBS.DataStoreContent', {
let data = item.data; let data = item.data;
data.text = group + '/' + PBS.Utils.render_datetime_utc(data["backup-time"]); data.text = group + '/' + PBS.Utils.render_datetime_utc(data["backup-time"]);
data.leaf = true; data.leaf = false;
data.cls = 'no-leaf-icons'; 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); children.push(data);
} }
@ -181,13 +190,12 @@ Ext.define('PBS.DataStoreContent', {
Proxmox.Utils.setErrorMask(view, false); Proxmox.Utils.setErrorMask(view, false);
}, },
onPrune: function() { onPrune: function(view, rI, cI, item, e, rec) {
var view = this.getView(); var view = this.getView();
let rec = view.selModel.getSelection()[0];
if (!(rec && rec.data)) return; if (!(rec && rec.data)) return;
let data = rec.data; let data = rec.data;
if (data.leaf) return; if (rec.parentNode.id !== 'root') return;
if (!view.datastore) return; if (!view.datastore) return;
@ -200,18 +208,17 @@ Ext.define('PBS.DataStoreContent', {
win.show(); win.show();
}, },
onVerify: function() { onVerify: function(view, rI, cI, item, e, rec) {
var view = this.getView(); var view = this.getView();
if (!view.datastore) return; if (!view.datastore) return;
let rec = view.selModel.getSelection()[0];
if (!(rec && rec.data)) return; if (!(rec && rec.data)) return;
let data = rec.data; let data = rec.data;
let params; let params;
if (data.leaf) { if (rec.parentNode.id !== 'root') {
params = { params = {
"backup-type": data["backup-type"], "backup-type": data["backup-type"],
"backup-id": data["backup-id"], "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(); var view = this.getView();
let rec = view.selModel.getSelection()[0];
if (!(rec && rec.data)) return; if (!(rec && rec.data)) return;
let data = rec.data; let data = rec.data;
if (!data.leaf) return;
if (!view.datastore) 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({ Proxmox.Utils.API2Request({
params: { params: {
"backup-type": data["backup-type"], "backup-type": data["backup-type"],
"backup-id": data["backup-id"], "backup-id": data["backup-id"],
"backup-time": (data['backup-time'].getTime()/1000).toFixed(0), "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),
});
}, },
url: `/admin/datastore/${view.datastore}/snapshots`,
method: 'DELETE',
waitMsgTarget: view,
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
},
callback: this.reload.bind(this),
}); });
}, },
openBackupFileDownloader: function() { downloadFile: function(tV, rI, cI, item, e, rec) {
let me = this; let me = this;
let view = me.getView(); let view = me.getView();
let rec = view.selModel.getSelection()[0];
if (!(rec && rec.data)) return; if (!(rec && rec.data)) return;
let data = rec.data; let data = rec.parentNode.data;
Ext.create('PBS.window.BackupFileDownloader', { let file = rec.data.filename;
baseurl: `/api2/json/admin/datastore/${view.datastore}`, let params = {
params: { 'backup-id': data['backup-id'],
'backup-id': data['backup-id'], 'backup-type': data['backup-type'],
'backup-type': data['backup-type'], 'backup-time': (data['backup-time'].getTime()/1000).toFixed(0),
'backup-time': (data['backup-time'].getTime()/1000).toFixed(0), 'file-name': file,
}, };
files: data.files,
}).show();
},
openPxarBrowser: function() { let idx = file.lastIndexOf('.');
let me = this; let filename = file.slice(0, idx);
let view = me.getView(); let atag = document.createElement('a');
params['file-name'] = file;
let rec = view.selModel.getSelection()[0]; atag.download = filename;
if (!(rec && rec.data)) return; let url = new URL(`/api2/json/admin/datastore/${view.datastore}/download-decoded`, window.location.origin);
let data = rec.data; for (const [key, value] of Object.entries(params)) {
url.searchParams.append(key, value);
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;
} }
atag.href = url.href;
atag.click();
},
openPxarBrowser: function(tv, rI, Ci, item, e, rec) {
let me = this;
let view = me.getView();
if (!(rec && rec.data)) return;
let data = rec.parentNode.data;
let id = data['backup-id']; let id = data['backup-id'];
let time = data['backup-time']; let time = data['backup-time'];
@ -320,6 +329,7 @@ Ext.define('PBS.DataStoreContent', {
'backup-id': id, 'backup-id': id,
'backup-time': (time.getTime()/1000).toFixed(0), 'backup-time': (time.getTime()/1000).toFixed(0),
'backup-type': type, 'backup-type': type,
archive: rec.data.filename,
}).show(); }).show();
} }
}, },
@ -331,6 +341,55 @@ Ext.define('PBS.DataStoreContent', {
dataIndex: 'text', dataIndex: 'text',
flex: 1 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', xtype: 'datecolumn',
header: gettext('Backup Time'), header: gettext('Backup Time'),
@ -344,6 +403,9 @@ Ext.define('PBS.DataStoreContent', {
sortable: true, sortable: true,
dataIndex: 'size', dataIndex: 'size',
renderer: (v, meta, record) => { renderer: (v, meta, record) => {
if (record.data.text === 'client.log.blob' && v === undefined) {
return '';
}
if (v === undefined || v === null) { if (v === undefined || v === null) {
meta.tdCls = "x-grid-row-loading"; meta.tdCls = "x-grid-row-loading";
return ''; return '';
@ -366,28 +428,17 @@ Ext.define('PBS.DataStoreContent', {
{ {
header: gettext('Encrypted'), header: gettext('Encrypted'),
dataIndex: 'crypt-mode', dataIndex: 'crypt-mode',
renderer: value => PBS.Utils.cryptText[value] || Proxmox.Utils.unknownText, renderer: (v, meta, record) => {
}, if (v === -1) {
{ return '';
header: gettext("Files"), }
sortable: false, let iconCls = PBS.Utils.cryptIconCls[v] || '';
dataIndex: 'files', let iconTxt = "";
renderer: function(files) { if (iconCls) {
return files.map((file) => { iconTxt = `<i class="fa fa-fw fa-${iconCls}"></i> `;
let icon = ''; }
let size = ''; return (iconTxt + PBS.Utils.cryptText[v]) || Proxmox.Utils.unknownText
let mode = PBS.Utils.cryptmap.indexOf(file['crypt-mode']); }
let iconCls = PBS.Utils.cryptIconCls[mode] || '';
if (iconCls !== '') {
icon = `<i class="fa fa-${iconCls}"></i> `;
}
if (file.size) {
size = ` (${Proxmox.Utils.format_size(file.size)})`;
}
return `${icon}${file.filename}${size}`;
}).join(', ');
},
flex: 2
}, },
], ],
@ -397,55 +448,5 @@ Ext.define('PBS.DataStoreContent', {
iconCls: 'fa fa-refresh', iconCls: 'fa fa-refresh',
handler: 'reload', 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'));
},
}
], ],
}); });

View File

@ -30,8 +30,8 @@ Ext.define('PBS.Utils', {
cryptIconCls: [ cryptIconCls: [
'', '',
'', '',
'certificate', 'lock faded',
'lock', 'lock good',
], ],
calculateCryptMode: function(data) { calculateCryptMode: function(data) {

View File

@ -208,3 +208,19 @@ p.logs {
.pmx-button-badge.active { .pmx-button-badge.active {
background-color: #464d4d; 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;
}

View File

@ -145,7 +145,16 @@ Ext.define("PBS.window.FileBrowser", {
store.load(() => { store.load(() => {
let root = store.getRoot(); let root = store.getRoot();
root.expand(); // always expand invisible root node 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(); root.firstChild.expand();
} }
}); });