]> git.proxmox.com Git - proxmox-widget-toolkit.git/blob - src/window/FileBrowser.js
file browser: only disable button if not downloadable and add hint in tooltip
[proxmox-widget-toolkit.git] / src / window / FileBrowser.js
1 Ext.define('proxmox-file-tree', {
2 extend: 'Ext.data.Model',
3
4 fields: [
5 'filepath', 'text', 'type', 'size',
6 {
7 name: 'sizedisplay',
8 calculate: data => {
9 if (data.size === undefined) {
10 return '';
11 } else if (data.type === 'd') {
12 let fs = data.size === 1 ? gettext('{0} item') : gettext('{0} items');
13 return Ext.String.format(fs, data.size);
14 }
15
16 return Proxmox.Utils.format_size(data.size);
17 },
18 },
19 {
20 name: 'mtime',
21 type: 'date',
22 dateFormat: 'timestamp',
23 },
24 {
25 name: 'iconCls',
26 calculate: function(data) {
27 let icon = Proxmox.Schema.pxarFileTypes[data.type]?.icon ?? 'file-o';
28 if (data.expanded && data.type === 'd') {
29 icon = 'folder-open-o';
30 }
31 return `fa fa-${icon}`;
32 },
33 },
34 ],
35 idProperty: 'filepath',
36 });
37
38 Ext.define("Proxmox.window.FileBrowser", {
39 extend: "Ext.window.Window",
40
41 width: 800,
42 height: 600,
43
44 modal: true,
45
46 config: {
47 // the base-URL to get the list of files. required.
48 listURL: '',
49
50 // the base download URL, e.g., something like '/api2/...'
51 downloadURL: '',
52
53 // extra parameters set as proxy paramns and for an actual download request
54 extraParams: {},
55
56 // the file types for which the download button should be enabled
57 downloadableFileTypes: {
58 'h': true, // hardlinks
59 'f': true, // "normal" files
60 'd': true, // directories
61 },
62
63 // enable tar download, this will add a menu to the "Download" button when the selection
64 // can be downloaded as `.tar` files
65 enableTar: false,
66
67 // prefix to prepend to downloaded file names
68 downloadPrefix: '',
69 },
70
71 controller: {
72 xclass: 'Ext.app.ViewController',
73
74 buildUrl: function(baseurl, params) {
75 let url = new URL(baseurl, window.location.origin);
76 for (const [key, value] of Object.entries(params)) {
77 url.searchParams.append(key, value);
78 }
79
80 return url.href;
81 },
82
83 downloadTar: function() {
84 this.downloadFile(true);
85 },
86
87 downloadZip: function() {
88 this.downloadFile(false);
89 },
90
91 downloadFile: function(tar) {
92 let me = this;
93 let view = me.getView();
94 let tree = me.lookup('tree');
95 let selection = tree.getSelection();
96 if (!selection || selection.length < 1) return;
97
98 let data = selection[0].data;
99
100 let params = { ...view.extraParams };
101 params.filepath = data.filepath;
102
103 let atag = document.createElement('a');
104 atag.download = view.downloadPrefix + data.text;
105 if (data.type === 'd') {
106 if (tar) {
107 params.tar = 1;
108 atag.download += ".tar.zst";
109 } else {
110 atag.download += ".zip";
111 }
112 }
113 atag.href = me.buildUrl(view.downloadURL, params);
114 atag.click();
115 },
116
117 fileChanged: function() {
118 let me = this;
119 let view = me.getView();
120 let tree = me.lookup('tree');
121 let selection = tree.getSelection();
122 if (!selection || selection.length < 1) return;
123
124 let data = selection[0].data;
125 let st = Ext.String.format(gettext('Selected "{0}"'), atob(data.filepath));
126 view.lookup('selectText').setText(st);
127
128 let canDownload = view.downloadURL && view.downloadableFileTypes[data.type];
129 let enableMenu = view.enableTar && data.type === 'd';
130
131 let downloadBtn = view.lookup('downloadBtn');
132 downloadBtn.setDisabled(!canDownload || enableMenu);
133 downloadBtn.setHidden(canDownload && enableMenu);
134 let typeLabel = Proxmox.Schema.pxarFileTypes[data.type]?.label ?? Proxmox.Utils.unknownText;
135 let ttip = Ext.String.format(
136 gettext('File of type {0} cannot be downloaded directly, download a parent directory instead.'),
137 typeLabel,
138 );
139 if (!canDownload) { // ensure tooltip gets shown
140 downloadBtn.setStyle({ pointerEvents: 'all' });
141 }
142 downloadBtn.setTooltip(canDownload ? null : ttip);
143
144 let menuBtn = view.lookup('menuBtn');
145 menuBtn.setDisabled(!canDownload || !enableMenu);
146 menuBtn.setHidden(!canDownload || !enableMenu);
147 },
148
149 errorHandler: function(error, msg) {
150 let me = this;
151 if (error?.status === 503) {
152 return false;
153 }
154 me.lookup('downloadBtn').setDisabled(true);
155 me.lookup('menuBtn').setDisabled(true);
156 if (me.initialLoadDone) {
157 Ext.Msg.alert(gettext('Error'), msg);
158 return true;
159 }
160 return false;
161 },
162
163 init: function(view) {
164 let me = this;
165 let tree = me.lookup('tree');
166
167 if (!view.listURL) {
168 throw "no list URL given";
169 }
170
171 let store = tree.getStore();
172 let proxy = store.getProxy();
173
174 let errorCallback = (error, msg) => me.errorHandler(error, msg);
175 proxy.setUrl(view.listURL);
176 proxy.setTimeout(60*1000);
177 proxy.setExtraParams(view.extraParams);
178
179 tree.mon(store, 'beforeload', () => {
180 Proxmox.Utils.setErrorMask(tree, true);
181 });
182 tree.mon(store, 'load', (treestore, rec, success, operation, node) => {
183 if (success) {
184 Proxmox.Utils.setErrorMask(tree, false);
185 return;
186 }
187 if (!node.loadCount) {
188 node.loadCount = 0; // ensure its numeric
189 }
190 // trigger a reload if we got a 503 answer from the proxy
191 if (operation?.error?.status === 503 && node.loadCount < 10) {
192 node.collapse();
193 node.expand();
194 node.loadCount++;
195 return;
196 }
197
198 let error = operation.getError();
199 let msg = Proxmox.Utils.getResponseErrorMessage(error);
200 if (!errorCallback(error, msg)) {
201 Proxmox.Utils.setErrorMask(tree, msg);
202 } else {
203 Proxmox.Utils.setErrorMask(tree, false);
204 }
205 });
206 store.load((rec, op, success) => {
207 let root = store.getRoot();
208 root.expand(); // always expand invisible root node
209 if (view.archive === 'all') {
210 root.expandChildren(false);
211 } else if (view.archive) {
212 let child = root.findChild('text', view.archive);
213 if (child) {
214 child.expand();
215 setTimeout(function() {
216 tree.setSelection(child);
217 tree.getView().focusRow(child);
218 }, 10);
219 }
220 } else if (root.childNodes.length === 1) {
221 root.firstChild.expand();
222 }
223 me.initialLoadDone = success;
224 });
225 },
226
227 control: {
228 'treepanel': {
229 selectionchange: 'fileChanged',
230 },
231 },
232 },
233
234 layout: 'fit',
235 items: [
236 {
237 xtype: 'treepanel',
238 scrollable: true,
239 rootVisible: false,
240 reference: 'tree',
241 store: {
242 autoLoad: false,
243 model: 'proxmox-file-tree',
244 defaultRootId: '/',
245 nodeParam: 'filepath',
246 sorters: 'text',
247 proxy: {
248 appendId: false,
249 type: 'proxmox',
250 },
251 },
252
253 viewConfig: {
254 loadMask: false,
255 },
256
257 columns: [
258 {
259 text: gettext('Name'),
260 xtype: 'treecolumn',
261 flex: 1,
262 dataIndex: 'text',
263 renderer: Ext.String.htmlEncode,
264 },
265 {
266 text: gettext('Size'),
267 dataIndex: 'sizedisplay',
268 align: 'end',
269 sorter: {
270 sorterFn: function(a, b) {
271 if (a.data.type === 'd' && b.data.type !== 'd') {
272 return -1;
273 } else if (a.data.type !== 'd' && b.data.type === 'd') {
274 return 1;
275 }
276
277 let asize = a.data.size || 0;
278 let bsize = b.data.size || 0;
279
280 return asize - bsize;
281 },
282 },
283 },
284 {
285 text: gettext('Modified'),
286 dataIndex: 'mtime',
287 minWidth: 200,
288 },
289 {
290 text: gettext('Type'),
291 dataIndex: 'type',
292 renderer: (v) => Proxmox.Schema.pxarFileTypes[v]?.label ?? Proxmox.Utils.unknownText,
293 },
294 ],
295 },
296 ],
297
298 fbar: [
299 {
300 text: '',
301 xtype: 'label',
302 reference: 'selectText',
303 },
304 {
305 text: gettext('Download'),
306 xtype: 'button',
307 handler: 'downloadZip',
308 reference: 'downloadBtn',
309 disabled: true,
310 hidden: true,
311 },
312 {
313 text: gettext('Download as'),
314 xtype: 'button',
315 reference: 'menuBtn',
316 menu: {
317 items: [
318 {
319 iconCls: 'fa fa-fw fa-file-zip-o',
320 text: gettext('.zip'),
321 handler: 'downloadZip',
322 reference: 'downloadZip',
323 },
324 {
325 iconCls: 'fa fa-fw fa-archive',
326 text: gettext('.tar.zst'),
327 handler: 'downloadTar',
328 reference: 'downloadTar',
329 },
330 ],
331 },
332 },
333 ],
334 });