diff --git a/frappe/app.py b/frappe/app.py index 8e1534e7ef..70575fe2f1 100644 --- a/frappe/app.py +++ b/frappe/app.py @@ -120,6 +120,8 @@ def init_request(request): else: frappe.connect(set_admin_as_user=False) + request.max_content_length = frappe.local.conf.get('max_file_size') or 10 * 1024 * 1024 + make_form_dict(request) if request.method != "OPTIONS": diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index 4df9ef3132..0021240106 100755 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -716,13 +716,11 @@ def delete_file(path): os.remove(path) - - +@frappe.whitelist() def get_max_file_size(): return cint(conf.get('max_file_size')) or 10485760 - def has_permission(doc, ptype=None, user=None): has_access = False user = user or frappe.session.user diff --git a/frappe/public/js/frappe/file_uploader/FilePreview.vue b/frappe/public/js/frappe/file_uploader/FilePreview.vue index 43dbacb17d..5972a975f2 100644 --- a/frappe/public/js/frappe/file_uploader/FilePreview.vue +++ b/frappe/public/js/frappe/file_uploader/FilePreview.vue @@ -29,21 +29,26 @@ +
+ + {{ file.error_message }} + +
-
+
- +
@@ -89,18 +94,18 @@ export default { return this.file.doc ? this.file.doc.is_private : this.file.private; }, uploaded() { - return this.file.total && this.file.total === this.file.progress && !this.file.failed; + return this.file.request_succeeded; }, is_image() { return this.file.file_obj.type.startsWith('image'); }, is_optimizable() { let is_svg = this.file.file_obj.type == 'image/svg+xml'; - return this.is_image && !is_svg; + return this.is_image && !is_svg && !this.uploaded && !this.file.failed; }, is_cropable() { let croppable_types = ['image/jpeg', 'image/png']; - return !this.uploaded && !this.file.uploading && croppable_types.includes(this.file.file_obj.type); + return !this.uploaded && !this.file.uploading && !this.file.failed && croppable_types.includes(this.file.file_obj.type); }, progress() { let value = Math.round((this.file.progress * 100) / this.file.total); @@ -208,4 +213,9 @@ export default { align-items: center; padding-top: 0.25rem; } + +.file-error { + font-size: var(--text-sm); + font-weight: var(--text-bold); +} diff --git a/frappe/public/js/frappe/file_uploader/FileUploader.vue b/frappe/public/js/frappe/file_uploader/FileUploader.vue index 90aa545941..167b4955fa 100644 --- a/frappe/public/js/frappe/file_uploader/FileUploader.vue +++ b/frappe/public/js/frappe/file_uploader/FileUploader.vue @@ -197,6 +197,7 @@ export default { show_image_cropper: false, crop_image_with_index: -1, trigger_upload: false, + close_dialog: false, hide_dialog_footer: false, allow_take_photo: false, allow_web_link: true, @@ -218,6 +219,12 @@ export default { } }); } + if (this.restrictions.max_file_size == null) { + frappe.call('frappe.core.doctype.file.file.get_max_file_size') + .then(res => { + this.restrictions.max_file_size = Number(res.message); + }); + } }, watch: { files(newvalue, oldvalue) { @@ -289,6 +296,8 @@ export default { progress: 0, total: 0, failed: false, + request_succeeded: false, + error_message: null, uploading: false, private: !is_image } @@ -329,9 +338,17 @@ export default { if (!is_correct_type) { console.warn('File skipped because of invalid file type', file); + frappe.show_alert({ + message: __('File "{0}" was skipped because of invalid file type', [file.name]), + indicator: 'orange' + }); } if (!valid_file_size) { console.warn('File skipped because of invalid file size', file.size, file); + frappe.show_alert({ + message: __('File "{0}" was skipped because size exceeds {1} MB', [file.name, max_file_size / (1024 * 1024)]), + indicator: 'orange' + }); } return is_correct_type && valid_file_size; @@ -357,9 +374,10 @@ export default { let selected_file = this.$refs.file_browser.selected_node; if (!selected_file.value) { frappe.msgprint(__('Click on a file to select it.')); + this.close_dialog = true; return Promise.reject(); } - + this.close_dialog = true; return this.upload_file({ file_url: selected_file.file_url }); @@ -368,9 +386,11 @@ export default { let file_url = this.$refs.web_link.url; if (!file_url) { frappe.msgprint(__('Invalid URL')); + this.close_dialog = true; return Promise.reject(); } file_url = decodeURI(file_url) + this.close_dialog = true; return this.upload_file({ file_url }); @@ -383,6 +403,7 @@ export default { this.on_success && this.on_success(file); }) ); + this.close_dialog = true; return Promise.all(promises); }, upload_file(file, i) { @@ -410,6 +431,7 @@ export default { xhr.onreadystatechange = () => { if (xhr.readyState == XMLHttpRequest.DONE) { if (xhr.status === 200) { + file.request_succeeded = true; let r = null; let file_doc = null; try { @@ -426,15 +448,24 @@ export default { if (this.on_success) { this.on_success(file_doc, r); } + + if (i == this.files.length - 1 && this.files.every(file => file.request_succeeded)) { + this.close_dialog = true; + } + } else if (xhr.status === 403) { + file.failed = true; let response = JSON.parse(xhr.responseText); - frappe.msgprint({ - title: __('Not permitted'), - indicator: 'red', - message: response._error_message - }); + file.error_message = `Not permitted. ${response._error_message || ''}`; + + } else if (xhr.status === 413) { + file.failed = true; + file.error_message = 'Size exceeds the maximum allowed file size.'; + } else { file.failed = true; + file.error_message = xhr.status === 0 ? 'XMLHttpRequest Error' : `${xhr.status} : ${xhr.statusText}`; + let error = null; try { error = JSON.parse(xhr.responseText); diff --git a/frappe/public/js/frappe/file_uploader/index.js b/frappe/public/js/frappe/file_uploader/index.js index 87bc1c8ec8..ec90b19a1a 100644 --- a/frappe/public/js/frappe/file_uploader/index.js +++ b/frappe/public/js/frappe/file_uploader/index.js @@ -67,6 +67,12 @@ export default class FileUploader { } }); + this.uploader.$watch('close_dialog', (close_dialog) => { + if (close_dialog) { + this.dialog && this.dialog.hide(); + } + }); + this.uploader.$watch('hide_dialog_footer', (hide_dialog_footer) => { if (hide_dialog_footer) { this.dialog && this.dialog.footer.addClass('hide'); @@ -84,10 +90,8 @@ export default class FileUploader { upload_files() { this.dialog && this.dialog.get_primary_btn().prop('disabled', true); - return this.uploader.upload_files() - .then(() => { - this.dialog && this.dialog.hide(); - }); + this.dialog && this.dialog.get_secondary_btn().prop('disabled', true); + return this.uploader.upload_files(); } make_dialog() {