]>
Commit | Line | Data |
---|---|---|
09195cb6 SR |
1 | Ext.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 | ||
54 | Ext.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 | }); |