From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id CC56870111; Thu, 1 Apr 2021 17:34:55 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id C7C5282A5; Thu, 1 Apr 2021 17:34:55 +0200 (CEST) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [212.186.127.180]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS id 7594A8284; Thu, 1 Apr 2021 17:34:54 +0200 (CEST) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 3D96645990; Thu, 1 Apr 2021 17:34:54 +0200 (CEST) From: Stefan Reiter To: pve-devel@lists.proxmox.com, pbs-devel@lists.proxmox.com Date: Thu, 1 Apr 2021 17:34:42 +0200 Message-Id: <20210401153444.8711-2-s.reiter@proxmox.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20210401153444.8711-1-s.reiter@proxmox.com> References: <20210401153444.8711-1-s.reiter@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL -0.012 Adjusted score from AWL reputation of From: address KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [atag.download] Subject: [pbs-devel] [PATCH proxmox-widget-toolkit 1/3] window: add FileBrowser X-BeenThere: pbs-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Backup Server development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 01 Apr 2021 15:34:55 -0000 from proxmox-backup, only names changed Signed-off-by: Stefan Reiter --- src/Makefile | 1 + src/window/FileBrowser.js | 248 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 249 insertions(+) create mode 100644 src/window/FileBrowser.js diff --git a/src/Makefile b/src/Makefile index 44c11ea..f97c74a 100644 --- a/src/Makefile +++ b/src/Makefile @@ -64,6 +64,7 @@ JSSRC= \ window/ACMEAccount.js \ window/ACMEPluginEdit.js \ window/ACMEDomains.js \ + window/FileBrowser.js \ node/APT.js \ node/NetworkEdit.js \ node/NetworkView.js \ diff --git a/src/window/FileBrowser.js b/src/window/FileBrowser.js new file mode 100644 index 0000000..82fd1b2 --- /dev/null +++ b/src/window/FileBrowser.js @@ -0,0 +1,248 @@ +Ext.define('proxmox-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("Proxmox.window.FileBrowser", { + extend: "Ext.window.Window", + + width: 800, + height: 600, + + 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; + if (data.type === 'd') { + atag.download += ".zip"; + } + atag.href = me + .buildUrl(`/api2/json/admin/datastore/${view.datastore}/pxar-file-download`, params); + atag.click(); + }, + + fileChanged: function() { + let me = this; + 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; + case 'd': + if (data.depth > 1) { + 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"; + } + + 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(() => { + let root = store.getRoot(); + root.expand(); // always expand invisible root node + 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(); + } + }); + }, + + control: { + 'treepanel': { + selectionchange: 'fileChanged', + }, + }, + }, + + layout: 'fit', + items: [ + { + xtype: 'treepanel', + scrollable: true, + rootVisible: false, + reference: 'tree', + store: { + autoLoad: false, + model: 'proxmox-file-tree', + defaultRootId: '/', + 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, + }, + ], +}); -- 2.20.1