ui: add pxar FileBrowser
for unencrypted backups, enables browsing the pxar archives and downloading single files from it Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
This commit is contained in:
parent
d33d8f4e6a
commit
8567c0d29c
@ -214,6 +214,43 @@ Ext.define('PBS.DataStoreContent', {
|
||||
},
|
||||
files: data.files,
|
||||
}).show();
|
||||
},
|
||||
|
||||
openPxarBrowser: function() {
|
||||
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.encrypted) {
|
||||
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 id = data['backup-id'];
|
||||
let time = data['backup-time'];
|
||||
let type = data['backup-type'];
|
||||
let timetext = PBS.Utils.render_datetime_utc(data["backup-time"]);
|
||||
|
||||
Ext.create('PBS.window.FileBrowser', {
|
||||
title: `${type}/${id}/${timetext}`,
|
||||
datastore: view.datastore,
|
||||
'backup-id': id,
|
||||
'backup-time': (time.getTime()/1000).toFixed(0),
|
||||
'backup-type': type,
|
||||
}).show();
|
||||
}
|
||||
},
|
||||
|
||||
@ -306,6 +343,16 @@ Ext.define('PBS.DataStoreContent', {
|
||||
enableFn: function(record) {
|
||||
return !!record.data.leaf;
|
||||
},
|
||||
},
|
||||
{
|
||||
xtype: "proxmoxButton",
|
||||
text: gettext('PXAR File Browser'),
|
||||
disabled: true,
|
||||
handler: 'openPxarBrowser',
|
||||
parentXType: 'pbsDataStoreContent',
|
||||
enableFn: function(record) {
|
||||
return !!record.data.leaf && record.data.files.some(el => el.filename.endsWith('pxar.didx'));
|
||||
},
|
||||
}
|
||||
],
|
||||
});
|
||||
|
@ -19,6 +19,7 @@ JSSRC= \
|
||||
window/ACLEdit.js \
|
||||
window/DataStoreEdit.js \
|
||||
window/CreateDirectory.js \
|
||||
window/FileBrowser.js \
|
||||
window/BackupFileDownloader.js \
|
||||
dashboard/DataStoreStatistics.js \
|
||||
dashboard/LongestTasks.js \
|
||||
|
229
www/window/FileBrowser.js
Normal file
229
www/window/FileBrowser.js
Normal file
@ -0,0 +1,229 @@
|
||||
Ext.define('pbs-file-tree', {
|
||||
extend: 'Ext.data.Model',
|
||||
|
||||
fields: [ 'filepath', 'text', 'type', 'size',
|
||||
{
|
||||
name: 'mtime',
|
||||
type: 'date',
|
||||
dateFormat: 'timestamp',
|
||||
},
|
||||
{
|
||||
name: 'iconCls',
|
||||
calculate: function(data) {
|
||||
let icon = 'file-o';
|
||||
switch (data.type) {
|
||||
case 'b': // block device
|
||||
icon = 'cube';
|
||||
break;
|
||||
case 'c': // char device
|
||||
icon = 'tty';
|
||||
break;
|
||||
case 'd':
|
||||
icon = data.expanded ? 'folder-open-o' : 'folder-o';
|
||||
break;
|
||||
case 'f': //regular file
|
||||
icon = 'file-text-o';
|
||||
break;
|
||||
case 'h': // hardlink
|
||||
icon = 'file-o';
|
||||
break;
|
||||
case 'l': // softlink
|
||||
icon = 'link';
|
||||
break;
|
||||
case 'p': // pipe/fifo
|
||||
icon = 'exchange';
|
||||
break;
|
||||
case 's': // socket
|
||||
icon = 'plug';
|
||||
break;
|
||||
default:
|
||||
icon = 'file-o';
|
||||
break;
|
||||
}
|
||||
|
||||
return `fa fa-${icon}`;
|
||||
},
|
||||
}
|
||||
],
|
||||
idProperty: 'filepath',
|
||||
});
|
||||
|
||||
Ext.define("PBS.window.FileBrowser", {
|
||||
extend: "Ext.window.Window",
|
||||
|
||||
width: 800,
|
||||
height: 400,
|
||||
|
||||
modal: true,
|
||||
|
||||
controller: {
|
||||
xclass: 'Ext.app.ViewController',
|
||||
|
||||
buildUrl: function(baseurl, params) {
|
||||
let url = new URL(baseurl, window.location.origin);
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
url.searchParams.append(key, value);
|
||||
}
|
||||
|
||||
return url.href;
|
||||
},
|
||||
|
||||
downloadFile: function() {
|
||||
let me = this;
|
||||
let view = me.getView();
|
||||
let tree = me.lookup('tree');
|
||||
let selection = tree.getSelection();
|
||||
if (!selection || selection.length < 1) return;
|
||||
|
||||
let data = selection[0].data;
|
||||
|
||||
let atag = document.createElement('a');
|
||||
|
||||
atag.download = data.text;
|
||||
let params = {
|
||||
'backup-id': view['backup-id'],
|
||||
'backup-type': view['backup-type'],
|
||||
'backup-time': view['backup-time'],
|
||||
};
|
||||
params['filepath'] = data.filepath;
|
||||
atag.download = data.text;
|
||||
atag.href = me.buildUrl(`/api2/json/admin/datastore/${view.datastore}/pxar-file-download`, params);
|
||||
atag.click();
|
||||
},
|
||||
|
||||
fileChanged: function() {
|
||||
let me = this;
|
||||
let view = me.getView();
|
||||
let tree = me.lookup('tree');
|
||||
let selection = tree.getSelection();
|
||||
if (!selection || selection.length < 1) return;
|
||||
|
||||
let data = selection[0].data;
|
||||
|
||||
let canDownload = false;
|
||||
switch (data.type) {
|
||||
case 'h':
|
||||
case 'f':
|
||||
canDownload = true;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
me.lookup('downloadBtn').setDisabled(!canDownload);
|
||||
},
|
||||
|
||||
init: function(view) {
|
||||
let me = this;
|
||||
let tree = me.lookup('tree');
|
||||
|
||||
if (!view['backup-id']) {
|
||||
throw "no backup-id given";
|
||||
}
|
||||
|
||||
if (!view['backup-type']) {
|
||||
throw "no backup-id given";
|
||||
}
|
||||
|
||||
if (!view['backup-time']) {
|
||||
throw "no backup-id given";
|
||||
}
|
||||
|
||||
if (!view.datastore) {
|
||||
throw "no datastore given";
|
||||
}
|
||||
|
||||
let store = tree.getStore();
|
||||
let proxy = store.getProxy();
|
||||
|
||||
Proxmox.Utils.monStoreErrors(tree, store, true);
|
||||
proxy.setUrl(`/api2/json/admin/datastore/${view.datastore}/catalog`);
|
||||
proxy.setExtraParams({
|
||||
'backup-id': view['backup-id'],
|
||||
'backup-type': view['backup-type'],
|
||||
'backup-time': view['backup-time'],
|
||||
});
|
||||
store.load();
|
||||
store.getRoot().expand();
|
||||
},
|
||||
|
||||
control: {
|
||||
'treepanel': {
|
||||
selectionchange: 'fileChanged',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
layout: 'fit',
|
||||
items: [
|
||||
{
|
||||
xtype: 'treepanel',
|
||||
scrollable: true,
|
||||
rootVisible: false,
|
||||
reference: 'tree',
|
||||
store: {
|
||||
autoLoad: false,
|
||||
model: 'pbs-file-tree',
|
||||
nodeParam: 'filepath',
|
||||
sorters: 'text',
|
||||
proxy: {
|
||||
appendId: false,
|
||||
type: 'proxmox',
|
||||
},
|
||||
},
|
||||
|
||||
columns: [
|
||||
{
|
||||
text: gettext('Name'),
|
||||
xtype: 'treecolumn',
|
||||
flex: 1,
|
||||
dataIndex: 'text',
|
||||
renderer: Ext.String.htmlEncode,
|
||||
},
|
||||
{
|
||||
text: gettext('Size'),
|
||||
dataIndex: 'size',
|
||||
renderer: value => value === undefined ? '' : Proxmox.Utils.format_size(value),
|
||||
sorter: {
|
||||
sorterFn: function(a, b) {
|
||||
let asize = a.data.size || 0;
|
||||
let bsize = b.data.size || 0;
|
||||
|
||||
return asize - bsize;
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
text: gettext('Modified'),
|
||||
dataIndex: 'mtime',
|
||||
minWidth: 200,
|
||||
},
|
||||
{
|
||||
text: gettext('Type'),
|
||||
dataIndex: 'type',
|
||||
renderer: function(value) {
|
||||
switch (value) {
|
||||
case 'b': return gettext('Block Device');
|
||||
case 'c': return gettext('Character Device');
|
||||
case 'd': return gettext('Directory');
|
||||
case 'f': return gettext('File');
|
||||
case 'h': return gettext('Hardlink');
|
||||
case 'l': return gettext('Softlink');
|
||||
case 'p': return gettext('Pipe/Fifo');
|
||||
case 's': return gettext('Socket');
|
||||
default: return Proxmox.Utils.unknownText;
|
||||
}
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
buttons: [
|
||||
{
|
||||
text: gettext('Download'),
|
||||
handler: 'downloadFile',
|
||||
reference: 'downloadBtn',
|
||||
disabled: true,
|
||||
}
|
||||
],
|
||||
});
|
Loading…
Reference in New Issue
Block a user