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:
		
				
					committed by
					
						 Dietmar Maurer
						Dietmar Maurer
					
				
			
			
				
	
			
			
			
						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,17 +246,24 @@ 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: { | ||||||
| @ -263,51 +277,46 @@ Ext.define('PBS.DataStoreContent', { | |||||||
| 			failure: function(response, opts) { | 			failure: function(response, opts) { | ||||||
| 			    Ext.Msg.alert(gettext('Error'), response.htmlStatus); | 			    Ext.Msg.alert(gettext('Error'), response.htmlStatus); | ||||||
| 			}, | 			}, | ||||||
| 		callback: this.reload.bind(this), | 			callback: me.reload.bind(me), | ||||||
|  | 		    }); | ||||||
|  | 		}, | ||||||
| 	    }); | 	    }); | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	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(); |  | ||||||
|  | 	    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 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; | ||||||
|  |  | ||||||
| 	    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 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, |  | ||||||
| 	    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 = `<i class="fa fa-${iconCls}"></i> `; |  | ||||||
| 		} | 		} | ||||||
| 		    if (file.size)  { | 		let iconCls = PBS.Utils.cryptIconCls[v] || ''; | ||||||
| 			size = ` (${Proxmox.Utils.format_size(file.size)})`; | 		let iconTxt = ""; | ||||||
|  | 		if (iconCls) { | ||||||
|  | 		    iconTxt = `<i class="fa fa-fw fa-${iconCls}"></i> `; | ||||||
|  | 		} | ||||||
|  | 		return (iconTxt + PBS.Utils.cryptText[v]) || Proxmox.Utils.unknownText | ||||||
| 	    } | 	    } | ||||||
| 		    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(); | ||||||
| 		} | 		} | ||||||
| 	    }); | 	    }); | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user