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