]>
Commit | Line | Data |
---|---|---|
4a580e60 DM |
1 | Ext.define('PVE.storage.Upload', { |
2 | extend: 'Ext.window.Window', | |
3f90858a | 3 | alias: 'widget.pveStorageUpload', |
4a580e60 DM |
4 | |
5 | resizable: false, | |
6 | ||
7 | modal: true, | |
8 | ||
1d8306e4 | 9 | initComponent: function() { |
4a580e60 DM |
10 | var me = this; |
11 | ||
4a580e60 DM |
12 | if (!me.nodename) { |
13 | throw "no node name specified"; | |
14 | } | |
177de3de | 15 | if (!me.storage) { |
4a580e60 DM |
16 | throw "no storage ID specified"; |
17 | } | |
18 | ||
1d8306e4 | 19 | let baseurl = `/nodes/${me.nodename}/storage/${me.storage}/upload`; |
4a580e60 | 20 | |
1d8306e4 | 21 | let pbar = Ext.create('Ext.ProgressBar', { |
4a580e60 | 22 | text: 'Ready', |
1d8306e4 | 23 | hidden: true, |
4a580e60 DM |
24 | }); |
25 | ||
c128543f FE |
26 | let acceptedExtensions = { |
27 | iso: ".img, .iso", | |
28 | vztmpl: ".tar.gz, .tar.xz", | |
29 | }; | |
30 | ||
31 | let defaultContent = me.contents[0] || ''; | |
32 | ||
33 | let fileField = Ext.create('Ext.form.field.File', { | |
34 | name: 'filename', | |
35 | buttonText: gettext('Select File...'), | |
36 | allowBlank: false, | |
37 | setAccept: function(content) { | |
38 | let acceptString = acceptedExtensions[content] || ''; | |
39 | this.fileInputEl.set({ | |
40 | accept: acceptString, | |
41 | }); | |
42 | }, | |
43 | listeners: { | |
44 | afterrender: function(cmp) { | |
45 | cmp.setAccept(defaultContent); | |
46 | }, | |
47 | }, | |
48 | }); | |
49 | ||
4a580e60 DM |
50 | me.formPanel = Ext.create('Ext.form.Panel', { |
51 | method: 'POST', | |
52 | waitMsgTarget: true, | |
53 | bodyPadding: 10, | |
54 | border: false, | |
55 | width: 300, | |
56 | fieldDefaults: { | |
57 | labelWidth: 100, | |
1d8306e4 | 58 | anchor: '100%', |
4a580e60 DM |
59 | }, |
60 | items: [ | |
61 | { | |
62 | xtype: 'pveContentTypeSelector', | |
6c1a2b86 | 63 | cts: me.contents, |
4a580e60 DM |
64 | fieldLabel: gettext('Content'), |
65 | name: 'content', | |
c128543f | 66 | value: defaultContent, |
518a3974 SR |
67 | allowBlank: false, |
68 | listeners: { | |
c128543f FE |
69 | change: function(cmp, newValue, oldValue) { |
70 | fileField.setAccept(newValue); | |
71 | }, | |
72 | }, | |
4a580e60 | 73 | }, |
c128543f | 74 | fileField, |
1d8306e4 TL |
75 | pbar, |
76 | ], | |
4a580e60 DM |
77 | }); |
78 | ||
1d8306e4 | 79 | let form = me.formPanel.getForm(); |
4a580e60 | 80 | |
1d8306e4 | 81 | let doStandardSubmit = function() { |
4a580e60 DM |
82 | form.submit({ |
83 | url: "/api2/htmljs" + baseurl, | |
84 | waitMsg: gettext('Uploading file...'), | |
85 | success: function(f, action) { | |
86 | me.close(); | |
87 | }, | |
88 | failure: function(f, action) { | |
89 | var msg = PVE.Utils.extractFormActionError(action); | |
90 | Ext.Msg.alert(gettext('Error'), msg); | |
1d8306e4 | 91 | }, |
4a580e60 DM |
92 | }); |
93 | }; | |
94 | ||
1d8306e4 | 95 | let updateProgress = function(per, bytes) { |
4a580e60 DM |
96 | var text = (per * 100).toFixed(2) + '%'; |
97 | if (bytes) { | |
e7ade592 | 98 | text += " (" + Proxmox.Utils.format_size(bytes) + ')'; |
4a580e60 DM |
99 | } |
100 | pbar.updateProgress(per, text); | |
101 | }; | |
177de3de | 102 | |
1d8306e4 | 103 | let abortBtn = Ext.create('Ext.Button', { |
4a580e60 DM |
104 | text: gettext('Abort'), |
105 | disabled: true, | |
106 | handler: function() { | |
107 | me.close(); | |
1d8306e4 | 108 | }, |
4a580e60 DM |
109 | }); |
110 | ||
1d8306e4 | 111 | let submitBtn = Ext.create('Ext.Button', { |
4a580e60 DM |
112 | text: gettext('Upload'), |
113 | disabled: true, | |
114 | handler: function(button) { | |
115 | var fd; | |
116 | try { | |
117 | fd = new FormData(); | |
118 | } catch (err) { | |
119 | doStandardSubmit(); | |
120 | return; | |
121 | } | |
122 | ||
123 | button.setDisabled(true); | |
124 | abortBtn.setDisabled(false); | |
125 | ||
126 | var field = form.findField('content'); | |
127 | fd.append("content", field.getValue()); | |
128 | field.setDisabled(true); | |
129 | ||
130 | field = form.findField('filename'); | |
131 | var file = field.fileInputEl.dom; | |
132 | fd.append("filename", file.files[0]); | |
133 | field.setDisabled(true); | |
134 | ||
135 | pbar.setVisible(true); | |
136 | updateProgress(0); | |
137 | ||
1d8306e4 TL |
138 | let xhr = new XMLHttpRequest(); |
139 | me.xhr = xhr; | |
4a580e60 | 140 | |
177de3de | 141 | xhr.addEventListener("load", function(e) { |
1d8306e4 | 142 | if (xhr.status === 200) { |
4a580e60 | 143 | me.close(); |
1d8306e4 TL |
144 | return; |
145 | } | |
146 | let err = Ext.htmlEncode(xhr.statusText); | |
147 | let msg = `${gettext('Error')} ${xhr.status.toString()}: ${err}`; | |
148 | if (xhr.responseText !== "") { | |
149 | let result = Ext.decode(xhr.responseText); | |
150 | result.message = msg; | |
151 | msg = Proxmox.Utils.extractRequestError(result, true); | |
177de3de | 152 | } |
1d8306e4 | 153 | Ext.Msg.alert(gettext('Error'), msg, btn => me.close()); |
4a580e60 DM |
154 | }, false); |
155 | ||
156 | xhr.addEventListener("error", function(e) { | |
1d8306e4 TL |
157 | let err = e.target.status.toString(); |
158 | let msg = `Error '${err}' occurred while receiving the document.`; | |
159 | Ext.Msg.alert(gettext('Error'), msg, btn => me.close()); | |
4a580e60 | 160 | }); |
177de3de | 161 | |
4a580e60 | 162 | xhr.upload.addEventListener("progress", function(evt) { |
177de3de | 163 | if (evt.lengthComputable) { |
1d8306e4 | 164 | let percentComplete = evt.loaded / evt.total; |
4a580e60 | 165 | updateProgress(percentComplete, evt.loaded); |
177de3de | 166 | } |
4a580e60 DM |
167 | }, false); |
168 | ||
1d8306e4 | 169 | xhr.open("POST", `/api2/json${baseurl}`, true); |
177de3de | 170 | xhr.send(fd); |
1d8306e4 | 171 | }, |
4a580e60 DM |
172 | }); |
173 | ||
1d8306e4 | 174 | form.on('validitychange', (f, valid) => submitBtn.setDisabled(!valid)); |
4a580e60 | 175 | |
1d8306e4 TL |
176 | Ext.apply(me, { |
177 | title: gettext('Upload'), | |
4a580e60 | 178 | items: me.formPanel, |
1d8306e4 | 179 | buttons: [abortBtn, submitBtn], |
4a580e60 DM |
180 | listeners: { |
181 | close: function() { | |
1d8306e4 TL |
182 | if (me.xhr) { |
183 | me.xhr.abort(); | |
184 | delete me.xhr; | |
4a580e60 | 185 | } |
1d8306e4 TL |
186 | }, |
187 | }, | |
4a580e60 DM |
188 | }); |
189 | ||
190 | me.callParent(); | |
1d8306e4 | 191 | }, |
4a580e60 DM |
192 | }); |
193 | ||
194 | Ext.define('PVE.storage.ContentView', { | |
195 | extend: 'Ext.grid.GridPanel', | |
196 | ||
3f90858a | 197 | alias: 'widget.pveStorageContentView', |
4a580e60 | 198 | |
c90539de DC |
199 | viewConfig: { |
200 | trackOver: false, | |
1d8306e4 | 201 | loadMask: false, |
c90539de | 202 | }, |
1d8306e4 | 203 | initComponent: function() { |
4a580e60 DM |
204 | var me = this; |
205 | ||
dbeddeb3 FE |
206 | if (!me.nodename) { |
207 | me.nodename = me.pveSelNode.data.node; | |
208 | if (!me.nodename) { | |
209 | throw "no node name specified"; | |
210 | } | |
4a580e60 | 211 | } |
1d8306e4 | 212 | const nodename = me.nodename; |
4a580e60 | 213 | |
dbeddeb3 FE |
214 | if (!me.storage) { |
215 | me.storage = me.pveSelNode.data.storage; | |
216 | if (!me.storage) { | |
217 | throw "no storage ID specified"; | |
218 | } | |
4a580e60 | 219 | } |
1d8306e4 | 220 | const storage = me.storage; |
4a580e60 | 221 | |
e8b422bc FE |
222 | var content = me.content; |
223 | if (!content) { | |
224 | throw "no content type specified"; | |
225 | } | |
226 | ||
1d8306e4 TL |
227 | const baseurl = `/nodes/${nodename}/storage/${storage}/content`; |
228 | let store = me.store = Ext.create('Ext.data.Store', { | |
4a580e60 | 229 | model: 'pve-storage-content', |
4a580e60 | 230 | proxy: { |
56a353b9 | 231 | type: 'proxmox', |
e8b422bc FE |
232 | url: '/api2/json' + baseurl, |
233 | extraParams: { | |
234 | content: content, | |
235 | }, | |
4a580e60 | 236 | }, |
177de3de DC |
237 | sorters: { |
238 | property: 'volid', | |
1d8306e4 TL |
239 | order: 'DESC', |
240 | }, | |
4a580e60 DM |
241 | }); |
242 | ||
dbeddeb3 FE |
243 | if (!me.sm) { |
244 | me.sm = Ext.create('Ext.selection.RowModel', {}); | |
245 | } | |
1d8306e4 | 246 | let sm = me.sm; |
4a580e60 | 247 | |
1d8306e4 | 248 | let reload = () => store.load(); |
4a580e60 | 249 | |
e7ade592 | 250 | Proxmox.Utils.monStoreErrors(me, store); |
4a580e60 | 251 | |
1d8306e4 | 252 | let uploadButton = Ext.create('Proxmox.button.Button', { |
6c1a2b86 DC |
253 | text: gettext('Upload'), |
254 | handler: function() { | |
1d8306e4 | 255 | let win = Ext.create('PVE.storage.Upload', { |
6c1a2b86 DC |
256 | nodename: nodename, |
257 | storage: storage, | |
8798c35b | 258 | contents: [content], |
6c1a2b86 DC |
259 | }); |
260 | win.show(); | |
261 | win.on('destroy', reload); | |
1d8306e4 | 262 | }, |
6c1a2b86 DC |
263 | }); |
264 | ||
1d8306e4 | 265 | let removeButton = Ext.create('Proxmox.button.StdRemoveButton', { |
c5e224fc | 266 | selModel: sm, |
4d23cdef | 267 | delay: 5, |
c5e224fc WL |
268 | callback: function() { |
269 | reload(); | |
270 | }, | |
1d8306e4 | 271 | baseurl: baseurl + '/', |
c5e224fc WL |
272 | }); |
273 | ||
9ce0c258 FE |
274 | if (!me.tbar) { |
275 | me.tbar = []; | |
276 | } | |
8798c35b FE |
277 | if (me.useUploadButton) { |
278 | me.tbar.push(uploadButton); | |
279 | } | |
f5e17f15 FE |
280 | if (!me.useCustomRemoveButton) { |
281 | me.tbar.push(removeButton); | |
282 | } | |
9ce0c258 | 283 | me.tbar.push( |
9ce0c258 | 284 | '->', |
1d8306e4 TL |
285 | gettext('Search') + ':', |
286 | ' ', | |
9ce0c258 FE |
287 | { |
288 | xtype: 'textfield', | |
289 | width: 200, | |
290 | enableKeyEvents: true, | |
c8a71b9e | 291 | emptyText: gettext('Name, Format'), |
9ce0c258 | 292 | listeners: { |
c8a71b9e TL |
293 | keyup: { |
294 | buffer: 500, | |
295 | fn: function(field) { | |
296 | store.clearFilter(true); | |
297 | store.filter([ | |
298 | { | |
299 | property: 'text', | |
300 | value: field.getValue(), | |
301 | anyMatch: true, | |
302 | caseSensitive: false, | |
303 | }, | |
304 | ]); | |
305 | }, | |
306 | }, | |
307 | change: function(field, newValue, oldValue) { | |
308 | if (newValue !== this.originalValue) { | |
309 | this.triggers.clear.setVisible(true); | |
310 | } | |
311 | }, | |
312 | }, | |
313 | triggers: { | |
314 | clear: { | |
315 | cls: 'pmx-clear-trigger', | |
316 | weight: -1, | |
317 | hidden: true, | |
318 | handler: function() { | |
319 | this.triggers.clear.setVisible(false); | |
320 | this.setValue(this.originalValue); | |
321 | store.clearFilter(); | |
322 | }, | |
323 | }, | |
324 | }, | |
325 | }, | |
9ce0c258 FE |
326 | ); |
327 | ||
6ecc7420 | 328 | let availableColumns = { |
7ef0f4c3 FE |
329 | 'name': { |
330 | header: gettext('Name'), | |
331 | flex: 2, | |
332 | sortable: true, | |
333 | renderer: PVE.Utils.render_storage_content, | |
1d8306e4 | 334 | dataIndex: 'text', |
7ef0f4c3 | 335 | }, |
ef402242 DC |
336 | 'notes': { |
337 | header: gettext('Notes'), | |
7ef0f4c3 FE |
338 | flex: 1, |
339 | renderer: Ext.htmlEncode, | |
ef402242 | 340 | dataIndex: 'notes', |
7ef0f4c3 FE |
341 | }, |
342 | 'date': { | |
343 | header: gettext('Date'), | |
344 | width: 150, | |
1d8306e4 | 345 | dataIndex: 'vdate', |
7ef0f4c3 FE |
346 | }, |
347 | 'format': { | |
348 | header: gettext('Format'), | |
349 | width: 100, | |
1d8306e4 | 350 | dataIndex: 'format', |
7ef0f4c3 FE |
351 | }, |
352 | 'size': { | |
353 | header: gettext('Size'), | |
354 | width: 100, | |
355 | renderer: Proxmox.Utils.format_size, | |
1d8306e4 | 356 | dataIndex: 'size', |
7ef0f4c3 FE |
357 | }, |
358 | }; | |
359 | ||
6ecc7420 TL |
360 | if (me.hideColumns) { |
361 | me.hideColumns.forEach(key => delete availableColumns[key]); | |
ebea3f45 TL |
362 | } |
363 | if (!me.hasNotesColumn) { | |
ef402242 | 364 | delete availableColumns.notes; |
7ef0f4c3 | 365 | } |
ebea3f45 TL |
366 | if (me.extraColumns && typeof me.extraColumns === 'object') { |
367 | Object.assign(availableColumns, me.extraColumns); | |
368 | } | |
6ecc7420 | 369 | const columns = Object.values(availableColumns); |
7ef0f4c3 | 370 | |
9ce0c258 FE |
371 | Ext.apply(me, { |
372 | store: store, | |
373 | selModel: sm, | |
374 | tbar: me.tbar, | |
7ef0f4c3 | 375 | columns: columns, |
4a580e60 | 376 | listeners: { |
1d8306e4 TL |
377 | activate: reload, |
378 | }, | |
4a580e60 DM |
379 | }); |
380 | ||
381 | me.callParent(); | |
1d8306e4 | 382 | }, |
4a580e60 | 383 | }, function() { |
4a580e60 DM |
384 | Ext.define('pve-storage-content', { |
385 | extend: 'Ext.data.Model', | |
177de3de DC |
386 | fields: [ |
387 | 'volid', 'content', 'format', 'size', 'used', 'vmid', | |
ef402242 | 388 | 'channel', 'id', 'lun', 'notes', 'verification', |
177de3de DC |
389 | { |
390 | name: 'text', | |
4a580e60 | 391 | convert: function(value, record) { |
86cc7049 DC |
392 | // check for volid, because if you click on a grouping header, |
393 | // it calls convert (but with an empty volid) | |
394 | if (value || record.data.volid === null) { | |
4a580e60 DM |
395 | return value; |
396 | } | |
397 | return PVE.Utils.render_storage_content(value, {}, record); | |
1d8306e4 | 398 | }, |
12d50fcd TL |
399 | }, |
400 | { | |
401 | name: 'vdate', | |
402 | convert: function(value, record) { | |
403 | // check for volid, because if you click on a grouping header, | |
404 | // it calls convert (but with an empty volid) | |
405 | if (value || record.data.volid === null) { | |
406 | return value; | |
407 | } | |
408 | let t = record.data.content; | |
409 | if (t === "backup") { | |
410 | let v = record.data.volid; | |
411 | let match = v.match(/(\d{4}_\d{2}_\d{2})-(\d{2}_\d{2}_\d{2})/); | |
412 | if (match) { | |
a4a86fe9 | 413 | let date = match[1].replace(/_/g, '-'); |
12d50fcd TL |
414 | let time = match[2].replace(/_/g, ':'); |
415 | return date + " " + time; | |
416 | } | |
417 | } | |
a4a86fe9 DM |
418 | if (record.data.ctime) { |
419 | let ctime = new Date(record.data.ctime * 1000); | |
1d8306e4 | 420 | return Ext.Date.format(ctime, 'Y-m-d H:i:s'); |
a4a86fe9 | 421 | } |
12d50fcd | 422 | return ''; |
1d8306e4 | 423 | }, |
12d50fcd | 424 | }, |
4a580e60 | 425 | ], |
1d8306e4 | 426 | idProperty: 'volid', |
4a580e60 | 427 | }); |
4a580e60 | 428 | }); |