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:
		
				
					committed by
					
						
						Dietmar Maurer
					
				
			
			
				
	
			
			
			
						parent
						
							d33d8f4e6a
						
					
				
				
					commit
					8567c0d29c
				
			
							
								
								
									
										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,
 | 
			
		||||
	}
 | 
			
		||||
    ],
 | 
			
		||||
});
 | 
			
		||||
		Reference in New Issue
	
	Block a user