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:
parent
bccdc5fa04
commit
3e395378bc
|
@ -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'));
|
|
||||||
},
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
|
@ -30,8 +30,8 @@ Ext.define('PBS.Utils', {
|
||||||
cryptIconCls: [
|
cryptIconCls: [
|
||||||
'',
|
'',
|
||||||
'',
|
'',
|
||||||
'certificate',
|
'lock faded',
|
||||||
'lock',
|
'lock good',
|
||||||
],
|
],
|
||||||
|
|
||||||
calculateCryptMode: function(data) {
|
calculateCryptMode: function(data) {
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue