Allows queueing multiple files for upload to the storage, which wasn't
possible with the old upload window.

Signed-off-by: Matthias Heiserer <m.heise...@proxmox.com>
---
 www/manager6/window/UploadToStorage.js | 393 +++++++++++++------------
 1 file changed, 210 insertions(+), 183 deletions(-)

diff --git a/www/manager6/window/UploadToStorage.js 
b/www/manager6/window/UploadToStorage.js
index 0de6d89d..bf656164 100644
--- a/www/manager6/window/UploadToStorage.js
+++ b/www/manager6/window/UploadToStorage.js
@@ -1,3 +1,13 @@
+Ext.define('pve-multiupload', {
+       extend: 'Ext.data.Model',
+       fields: [
+           'file', 'filename', 'progressWidget', 'hashsum', 'hashWidget',
+           'xhr', 'mimetype', 'size',
+           {
+               name: 'hash', defaultValue: '__default__',
+           },
+       ],
+});
 Ext.define('PVE.window.UploadToStorage', {
     extend: 'Ext.window.Window',
     alias: 'widget.pveStorageUpload',
@@ -27,93 +37,102 @@ Ext.define('PVE.window.UploadToStorage', {
 
     viewModel: {
        data: {
-           size: '-',
-           mimetype: '-',
-           filename: '',
+           hasFiles: false,
+           uploadInProgress: false,
        },
     },
-
     controller: {
-       submit: function(button) {
-           const view = this.getView();
-           const form = this.lookup('formPanel').getForm();
-           const abortBtn = this.lookup('abortBtn');
-           const pbar = this.lookup('progressBar');
-
-           const updateProgress = function(per, bytes) {
-               let text = (per * 100).toFixed(2) + '%';
-               if (bytes) {
-                   text += " (" + Proxmox.Utils.format_size(bytes) + ')';
-               }
-               pbar.updateProgress(per, text);
-           };
 
+       addFile: function(input) {
+           let me = this;
+           let grid = me.lookup('grid');
+           for (const file of input.fileInputEl.dom.files) {
+               grid.store.add({
+                   file: file,
+                   filename: file.name,
+                   mimetype: Proxmox.Utils.format_size(file.size),
+                   size: file.type,
+               });
+           }
+       },
+
+       currentUploadIndex: 1,
+       startUpload: function() {
+           const me = this;
+           const view = me.getView();
+           const grid = me.lookup('grid');
+           view.taskDone();
+
+           const last = grid.store.last();
+           if (!last) {
+               me.getViewModel().set('uploadInProgress', false);
+               return;
+           }
+           const endId = parseInt(last.id.replace('pve-multiupload-', ''), 10);
+           let record;
+           while (!record && me.currentUploadIndex <= endId) {
+               record = 
grid.store.getById(`pve-multiupload-${me.currentUploadIndex++}`);
+           }
+
+           if (!record) {
+               me.getViewModel().set('uploadInProgress', false);
+               return;
+           }
+
+           const data = record.data;
            const fd = new FormData();
-
-           button.setDisabled(true);
-           abortBtn.setDisabled(false);
-
            fd.append("content", view.content);
-
-           const fileField = form.findField('file');
-           const file = fileField.fileInputEl.dom.files[0];
-           fileField.setDisabled(true);
-
-           const filenameField = form.findField('filename');
-           const filename = filenameField.getValue();
-           filenameField.setDisabled(true);
-
-           const algorithmField = form.findField('checksum-algorithm');
-           algorithmField.setDisabled(true);
-           if (algorithmField.getValue() !== '__default__') {
-               fd.append("checksum-algorithm", algorithmField.getValue());
-
-               const checksumField = form.findField('checksum');
-               fd.append("checksum", checksumField.getValue()?.trim());
-               checksumField.setDisabled(true);
+           if (data.hash !== '__default__') {
+               fd.append("checksum-algorithm", data.hash);
+               fd.append("checksum", data.hashsum.trim());
            }
+           fd.append("filename", data.file, data.filename);
 
-           fd.append("filename", file, filename);
-
-           pbar.setVisible(true);
-           updateProgress(0);
-
-           const xhr = new XMLHttpRequest();
-           view.xhr = xhr;
 
+           const xhr = data.xhr = new XMLHttpRequest();
            xhr.addEventListener("load", function(e) {
                if (xhr.status === 200) {
-                   view.hide();
-
                    const result = JSON.parse(xhr.response);
                    const upid = result.data;
                    Ext.create('Proxmox.window.TaskViewer', {
                        autoShow: true,
                        upid: upid,
-                       taskDone: view.taskDone,
+                       taskDone: function(success) {
+                           if (success) {
+                               this.close();
+                           } else {
+                               const widget = record.get('progressWidget');
+                               widget.updateProgress(0, "ERROR");
+                               widget.setStyle('background-color', 'red');
+                           }
+                       },
                        listeners: {
                            destroy: function() {
-                               view.close();
+                               me?.startUpload?.();
                            },
                        },
                    });
+               } else {
+                   const widget = record.get('progressWidget');
+                   widget.updateProgress(0, `ERROR: ${xhr.status}`);
+                   widget.setStyle('background-color', 'red');
+                   me.getViewModel().set('uploadInProgress', false);
+               }
+           });
 
-                   return;
+           const updateProgress = function(per, bytes) {
+               let text = (per * 100).toFixed(2) + '%';
+               if (bytes) {
+                   text += " (" + Proxmox.Utils.format_size(bytes) + ')';
                }
-               const err = Ext.htmlEncode(xhr.statusText);
-               let msg = `${gettext('Error')} ${xhr.status.toString()}: 
${err}`;
-               if (xhr.responseText !== "") {
-                   const result = Ext.decode(xhr.responseText);
-                   result.message = msg;
-                   msg = Proxmox.Utils.extractRequestError(result, true);
-               }
-               Ext.Msg.alert(gettext('Error'), msg, btn => view.close());
-           }, false);
+               let widget = record.get('progressWidget');
+               widget?.updateProgress(per, text);
+           };
 
            xhr.addEventListener("error", function(e) {
                const err = e.target.status.toString();
                const msg = `Error '${err}' occurred while receiving the 
document.`;
-               Ext.Msg.alert(gettext('Error'), msg, btn => view.close());
+               Ext.Msg.alert(gettext('Error'), msg, _ => view.close());
            });
 
            xhr.upload.addEventListener("progress", function(evt) {
@@ -123,173 +142,181 @@ Ext.define('PVE.window.UploadToStorage', {
                }
            }, false);
 
+           me.getViewModel().set('uploadInProgress', true);
            xhr.open("POST", `/api2/json${view.url}`, true);
            xhr.send(fd);
        },
 
-       validitychange: function(f, valid) {
-           const submitBtn = this.lookup('submitBtn');
-           submitBtn.setDisabled(!valid);
-       },
-
-       fileChange: function(input) {
-           const vm = this.getViewModel();
-           const name = input.value.replace(/^.*(\/|\\)/, '');
-           const fileInput = input.fileInputEl.dom;
-           vm.set('filename', name);
-           vm.set('size', (fileInput.files[0] && 
Proxmox.Utils.format_size(fileInput.files[0].size)) || '-');
-           vm.set('mimetype', (fileInput.files[0] && fileInput.files[0].type) 
|| '-');
-       },
-
-       hashChange: function(field, value) {
-           const checksum = this.lookup('downloadUrlChecksum');
-           if (value === '__default__') {
-               checksum.setDisabled(true);
-               checksum.setValue("");
-           } else {
-               checksum.setDisabled(false);
-           }
+       removeFile: function(_view, _rowIndex, _colIndex, _item, _event, 
record) {
+           let me = this;
+           me.lookup('grid').store.remove(record);
+           me.getViewModel().set('uploadInProgress', false);
        },
     },
 
     items: [
        {
-           xtype: 'form',
-           reference: 'formPanel',
-           method: 'POST',
-           waitMsgTarget: true,
-           bodyPadding: 10,
-           border: false,
-           width: 400,
-           fieldDefaults: {
-               labelWidth: 100,
-               anchor: '100%',
-            },
-           items: [
-               {
-                   xtype: 'filefield',
-                   name: 'file',
-                   buttonText: gettext('Select File'),
-                   allowBlank: false,
-                   fieldLabel: gettext('File'),
-                   cbind: {
-                       accept: '{extensions}',
-                   },
-                   listeners: {
-                       change: 'fileChange',
+           xtype: 'grid',
+           reference: 'grid',
+           height: 700,
+           width: 1100,
+           store: {
+               listeners: {
+                   remove: function(_store, records) {
+                       records.forEach(record => {
+                           record.get('xhr')?.abort();
+                           record.progressWidget = null;
+                           record.hashWidget = null;
+                       });
                    },
                },
+               model: 'pve-multiupload',
+           },
+           listeners: {
+               beforedestroy: function(grid) {
+                   grid.store.each(record => grid.store.remove(record));
+               },
+           },
+           columns: [
                {
-                   xtype: 'textfield',
-                   name: 'filename',
-                   allowBlank: false,
-                   fieldLabel: gettext('File name'),
-                   bind: {
-                       value: '{filename}',
-                   },
-                   cbind: {
-                       regex: '{filenameRegex}',
-                   },
-                   regexText: gettext('Wrong file extension'),
+                   header: gettext('Source Name'),
+                   dataIndex: 'file',
+                   renderer: file => file.name,
+                   flex: 2,
                },
                {
-                   xtype: 'displayfield',
-                   name: 'size',
-                   fieldLabel: gettext('File size'),
-                   bind: {
-                       value: '{size}',
+                   header: gettext('File Name'),
+                   dataIndex: 'filename',
+                   flex: 2,
+                   xtype: 'widgetcolumn',
+                   widget: {
+                       xtype: 'textfield',
+                       listeners: {
+                           change: function(widget, newValue, oldValue) {
+                               const record = widget.getWidgetRecord();
+                               record.set('filename', newValue);
+                           },
+                       },
+                       cbind: {
+                           regex: '{filenameRegex}',
+                       },
+                       regexText: gettext('Wrong file extension'),
                    },
                },
                {
-                   xtype: 'displayfield',
-                   name: 'mimetype',
-                   fieldLabel: gettext('MIME type'),
-                   bind: {
-                       value: '{mimetype}',
-                   },
+                   header: gettext('File size'),
+                   dataIndex: 'size',
                },
                {
-                   xtype: 'pveHashAlgorithmSelector',
-                   name: 'checksum-algorithm',
-                   fieldLabel: gettext('Hash algorithm'),
-                   allowBlank: true,
-                   hasNoneOption: true,
-                   value: '__default__',
-                   listeners: {
-                       change: 'hashChange',
-                   },
+                   header: gettext('MIME type'),
+                   dataIndex: 'mimetype',
                },
                {
-                   xtype: 'textfield',
-                   name: 'checksum',
-                   fieldLabel: gettext('Checksum'),
-                   allowBlank: false,
-                   disabled: true,
-                   emptyText: gettext('none'),
-                   reference: 'downloadUrlChecksum',
+                   header: gettext('Hash'),
+                   dataIndex: 'hash',
+                   flex: 2,
+                   xtype: 'widgetcolumn',
+                   widget: {
+                       xtype: 'pveHashAlgorithmSelector',
+                       listeners: {
+                           change: function(widget, newValue, oldValue) {
+                               const record = widget.getWidgetRecord();
+                               record.set('hash', newValue);
+                               let hashWidget = record.get('hashWidget');
+                               if (newValue === '__default__') {
+                                       hashWidget?.setDisabled(true);
+                                       hashWidget?.setValue('');
+                               } else {
+                                   hashWidget?.setDisabled(false);
+                               }
+                           },
+                       },
+                   },
                },
                {
-                   xtype: 'progressbar',
-                   text: 'Ready',
-                   hidden: true,
-                   reference: 'progressBar',
+                   header: gettext('Hash Value'),
+                   dataIndex: 'hashsum',
+                   renderer: data => data || 'None',
+                   flex: 4,
+                   xtype: 'widgetcolumn',
+                   widget: {
+                       xtype: 'textfield',
+                       disabled: true,
+                       listeners: {
+                           change: function(widget, newValue, oldValue) {
+                               const record = widget.getWidgetRecord();
+                               record.set('hashsum', newValue);
+                           },
+                       },
+                   },
+                   onWidgetAttach: function(col, widget, record) {
+                       record.set('hashWidget', widget);
+                   },
                },
                {
-                   xtype: 'hiddenfield',
-                   name: 'content',
-                   cbind: {
-                       value: '{content}',
+                   header: gettext('Progress Bar'),
+                   xtype: 'widgetcolumn',
+                   widget: {
+                       xtype: 'progressbar',
                    },
+                   onWidgetAttach: function(col, widget, rec) {
+                       rec.set('progressWidget', widget);
+                       widget.updateProgress(0, "");
+                   },
+                   flex: 2,
+               },
+               {
+                   xtype: 'actioncolumn',
+                   items: [{
+                       iconCls: 'fa critical fa-trash-o',
+                       handler: 'removeFile',
+                   }],
+                   flex: 0.5,
                },
            ],
-          listeners: {
-               validitychange: 'validitychange',
-          },
        },
     ],
 
     buttons: [
+       {
+           xtype: 'filefield',
+           name: 'file',
+           buttonText: gettext('Add File'),
+           allowBlank: false,
+           hideLabel: true,
+           fieldStyle: 'display: none;',
+           cbind: {
+               accept: '{extensions}',
+           },
+           listeners: {
+               change: 'addFile',
+               render: function(filefield) {
+                   filefield.fileInputEl.dom.multiple = true;
+               },
+           },
+       },
        {
            xtype: 'button',
            text: gettext('Abort'),
-           reference: 'abortBtn',
-           disabled: true,
            handler: function() {
                const me = this;
                me.up('pveStorageUpload').close();
            },
        },
        {
-           text: gettext('Upload'),
-           reference: 'submitBtn',
-           disabled: true,
-           handler: 'submit',
+           text: gettext('Start upload'),
+           handler: 'startUpload',
+           bind: {
+               disabled: '{!hasFiles || uploadInProgress}',
+           },
        },
     ],
 
-    listeners: {
-       close: function() {
-           const me = this;
-           if (me.xhr) {
-               me.xhr.abort();
-               delete me.xhr;
-           }
-       },
-    },
-
     initComponent: function() {
-        const me = this;
-
-       if (!me.nodename) {
-           throw "no node name specified";
-       }
-       if (!me.storage) {
-           throw "no storage ID specified";
-       }
-       if (!me.acceptedExtensions[me.content]) {
-           throw "content type not supported";
-       }
-
-        me.callParent();
+       let me = this;
+       me.callParent();
+       me.lookup('grid').store.on('datachanged', function(store) {
+           me.getViewModel().set('hasFiles', store.count() > 0);
+       });
     },
 });
-- 
2.30.2



_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel

Reply via email to