]>
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, | |
291 | listeners: { | |
292 | buffer: 500, | |
293 | keyup: function(field) { | |
294 | store.clearFilter(true); | |
295 | store.filter([ | |
296 | { | |
297 | property: 'text', | |
298 | value: field.getValue(), | |
299 | anyMatch: true, | |
300 | caseSensitive: false | |
301 | } | |
302 | ]); | |
4a580e60 DM |
303 | } |
304 | } | |
9ce0c258 FE |
305 | } |
306 | ); | |
307 | ||
6ecc7420 | 308 | let availableColumns = { |
7ef0f4c3 FE |
309 | 'name': { |
310 | header: gettext('Name'), | |
311 | flex: 2, | |
312 | sortable: true, | |
313 | renderer: PVE.Utils.render_storage_content, | |
1d8306e4 | 314 | dataIndex: 'text', |
7ef0f4c3 FE |
315 | }, |
316 | 'comment': { | |
317 | header: gettext('Comment'), | |
318 | flex: 1, | |
319 | renderer: Ext.htmlEncode, | |
320 | dataIndex: 'comment', | |
321 | }, | |
322 | 'date': { | |
323 | header: gettext('Date'), | |
324 | width: 150, | |
1d8306e4 | 325 | dataIndex: 'vdate', |
7ef0f4c3 FE |
326 | }, |
327 | 'format': { | |
328 | header: gettext('Format'), | |
329 | width: 100, | |
1d8306e4 | 330 | dataIndex: 'format', |
7ef0f4c3 FE |
331 | }, |
332 | 'size': { | |
333 | header: gettext('Size'), | |
334 | width: 100, | |
335 | renderer: Proxmox.Utils.format_size, | |
1d8306e4 | 336 | dataIndex: 'size', |
7ef0f4c3 FE |
337 | }, |
338 | }; | |
339 | ||
6ecc7420 TL |
340 | if (me.hideColumns) { |
341 | me.hideColumns.forEach(key => delete availableColumns[key]); | |
342 | } else if (!me.hasCommentColumn) { | |
343 | delete availableColumns.comment; | |
7ef0f4c3 | 344 | } |
6ecc7420 | 345 | const columns = Object.values(availableColumns); |
7ef0f4c3 | 346 | |
9ce0c258 FE |
347 | Ext.apply(me, { |
348 | store: store, | |
349 | selModel: sm, | |
350 | tbar: me.tbar, | |
7ef0f4c3 | 351 | columns: columns, |
4a580e60 | 352 | listeners: { |
1d8306e4 TL |
353 | activate: reload, |
354 | }, | |
4a580e60 DM |
355 | }); |
356 | ||
357 | me.callParent(); | |
1d8306e4 | 358 | }, |
4a580e60 | 359 | }, function() { |
4a580e60 DM |
360 | Ext.define('pve-storage-content', { |
361 | extend: 'Ext.data.Model', | |
177de3de DC |
362 | fields: [ |
363 | 'volid', 'content', 'format', 'size', 'used', 'vmid', | |
957a53bd | 364 | 'channel', 'id', 'lun', 'comment', 'verification', |
177de3de DC |
365 | { |
366 | name: 'text', | |
4a580e60 | 367 | convert: function(value, record) { |
86cc7049 DC |
368 | // check for volid, because if you click on a grouping header, |
369 | // it calls convert (but with an empty volid) | |
370 | if (value || record.data.volid === null) { | |
4a580e60 DM |
371 | return value; |
372 | } | |
373 | return PVE.Utils.render_storage_content(value, {}, record); | |
1d8306e4 | 374 | }, |
12d50fcd TL |
375 | }, |
376 | { | |
377 | name: 'vdate', | |
378 | convert: function(value, record) { | |
379 | // check for volid, because if you click on a grouping header, | |
380 | // it calls convert (but with an empty volid) | |
381 | if (value || record.data.volid === null) { | |
382 | return value; | |
383 | } | |
384 | let t = record.data.content; | |
385 | if (t === "backup") { | |
386 | let v = record.data.volid; | |
387 | let match = v.match(/(\d{4}_\d{2}_\d{2})-(\d{2}_\d{2}_\d{2})/); | |
388 | if (match) { | |
a4a86fe9 | 389 | let date = match[1].replace(/_/g, '-'); |
12d50fcd TL |
390 | let time = match[2].replace(/_/g, ':'); |
391 | return date + " " + time; | |
392 | } | |
393 | } | |
a4a86fe9 DM |
394 | if (record.data.ctime) { |
395 | let ctime = new Date(record.data.ctime * 1000); | |
1d8306e4 | 396 | return Ext.Date.format(ctime, 'Y-m-d H:i:s'); |
a4a86fe9 | 397 | } |
12d50fcd | 398 | return ''; |
1d8306e4 | 399 | }, |
12d50fcd | 400 | }, |
4a580e60 | 401 | ], |
1d8306e4 | 402 | idProperty: 'volid', |
4a580e60 | 403 | }); |
4a580e60 | 404 | }); |