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