shareupload.html 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. <!--
  2. Copyright (C) 2023 Nicola Murino
  3. This WebUI uses the KeenThemes Mega Bundle, a proprietary theme:
  4. https://keenthemes.com/products/templates-mega-bundle
  5. KeenThemes HTML/CSS/JS components are allowed for use only within the
  6. SFTPGo product and restricted to be used in a resealable HTML template
  7. that can compete with KeenThemes products anyhow.
  8. This WebUI is allowed for use only within the SFTPGo product and
  9. therefore cannot be used in derivative works/products without an
  10. explicit grant from the SFTPGo Team ([email protected]).
  11. -->
  12. {{- template "base" .}}
  13. {{- define "page_body"}}
  14. <div class="d-flex flex-center flex-column flex-column-fluid p-10 pb-lg-20">
  15. <div class="mb-12">
  16. <span>
  17. <img alt="Logo" src="{{.StaticURL}}{{.Branding.LogoPath}}" class="h-80px" />
  18. </span>
  19. <span class="text-gray-800 fs-2 fw-semibold ps-5">
  20. {{.Branding.ShortName}}
  21. </span>
  22. </div>
  23. <div class="card shadow-sm w-lg-600px">
  24. <div class="card-header bg-light">
  25. <h3 data-i18n="title.upload_to_share" class="card-title section-title">Upload one or more files to share</h3>
  26. </div>
  27. <div class="card-body">
  28. {{- template "errmsg" ""}}
  29. <form id="upload_files_form" action="{{.UploadBasePath}}" method="POST" enctype="multipart/form-data">
  30. <div class="fv-row">
  31. <div class="dropzone mh-350px overflow-auto visibility-auto" id="upload_files">
  32. <div class="dz-message needsclick align-items-center">
  33. <i class="ki-duotone ki-file-up fs-3x text-primary"><span class="path1"></span><span class="path2"></span></i>
  34. <div class="ms-4">
  35. <h3 data-i18n="fs.upload.message" class="fs-5 fw-bold text-gray-900 mb-1">Drop files here or click to upload.</h3>
  36. </div>
  37. </div>
  38. </div>
  39. </div>
  40. <div class="d-flex justify-content-end mt-10">
  41. <button data-i18n="general.submit" type="button" id="upload_files_button" class="btn btn-primary">Submit</button>
  42. </div>
  43. </form>
  44. </div>
  45. </div>
  46. </div>
  47. {{- end}}
  48. {{- define "extra_js"}}
  49. <script type="text/javascript" {{- if .CSPNonce}} nonce="{{.CSPNonce}}"{{- end}}>
  50. function uploadFiles(files) {
  51. let has_errors = false;
  52. let index = 0;
  53. let success = 0;
  54. let checkedDirs = [];
  55. $('#errorMsg').addClass("d-none");
  56. $('#loading_message').text("");
  57. KTApp.showPageLoading();
  58. function uploadFile() {
  59. if (index >= files.length || has_errors) {
  60. KTApp.hidePageLoading();
  61. if (!has_errors) {
  62. ModalAlert.fire({
  63. text: $.t('fs.upload.success'),
  64. icon: "success",
  65. confirmButtonText: $.t('general.ok'),
  66. customClass: {
  67. confirmButton: 'btn btn-primary'
  68. }
  69. }).then((result) => {
  70. if (result.isConfirmed){
  71. location.reload();
  72. }
  73. });
  74. }
  75. return;
  76. }
  77. let f = files[index];
  78. let name = f.name;
  79. let mkdirParents = "false";
  80. if (f.fullPath){
  81. name = f.fullPath;
  82. let dirName = name.substr(0, name.lastIndexOf("/"));
  83. if (!checkedDirs.includes(dirName)){
  84. mkdirParents = "true";
  85. checkedDirs.push(dirName);
  86. }
  87. }
  88. let uploadPath = '{{.UploadBasePath}}/'+encodeURIComponent(name)+"?mkdir_parents="+mkdirParents;
  89. let lastModified;
  90. try {
  91. lastModified = f.lastModified;
  92. } catch (e) {
  93. console.error("unable to get last modified time from file: " + e.message);
  94. lastModified = "";
  95. }
  96. let uploadTxt = f.name;
  97. if (files.length > 1){
  98. uploadTxt = $.t('fs.uploading', {
  99. idx: index + 1,
  100. total: files.length,
  101. name: uploadTxt
  102. });
  103. }
  104. $('#loading_message').text(uploadTxt);
  105. axios.post(uploadPath, f, {
  106. headers: {
  107. 'X-SFTPGO-MTIME': lastModified,
  108. 'X-CSRF-TOKEN': '{{.CSRFToken}}'
  109. },
  110. onUploadProgress: function (progressEvent) {
  111. if (!progressEvent.total){
  112. return;
  113. }
  114. const percentage = Math.round((100 * progressEvent.loaded) / progressEvent.total);
  115. if (percentage > 0 && percentage < 100){
  116. $('#loading_message').text(`${uploadTxt} ${percentage}%`);
  117. }
  118. },
  119. validateStatus: function (status) {
  120. return status == 201;
  121. }
  122. }).then(function (response) {
  123. index++;
  124. success++;
  125. uploadFile();
  126. }).catch(function (error) {
  127. let errorMessage;
  128. if (error && error.response) {
  129. switch (error.response.status) {
  130. case 403:
  131. errorMessage = "fs.upload.err_403";
  132. break;
  133. case 429:
  134. errorMessage = "fs.upload.err_429";
  135. break;
  136. }
  137. }
  138. if (!errorMessage){
  139. errorMessage = "fs.upload.err_generic";
  140. }
  141. index++;
  142. has_errors = true;
  143. setI18NData($('#errorTxt'), errorMessage);
  144. $('#errorMsg').removeClass("d-none");
  145. uploadFile();
  146. });
  147. }
  148. uploadFile();
  149. }
  150. KTUtil.onDOMContentLoaded(function () {
  151. var lastAddedFile = Date.now();
  152. function canUpload() {
  153. // Ugly hack to detect if we are still loading a large file list when the user try to upload files.
  154. // The Dropzone addedfiles event is fired for directories before the files within them are added to the queue.
  155. // TODO: investigate if there is a better way.
  156. if (Date.now() - lastAddedFile < 500) {
  157. ModalAlert.fire({
  158. text: $.t('fs.upload_queue_not_ready'),
  159. icon: "warning",
  160. confirmButtonText: $.t('general.ok'),
  161. customClass: {
  162. confirmButton: "btn btn-primary"
  163. }
  164. });
  165. return false;
  166. }
  167. return true;
  168. }
  169. var dropzone = new Dropzone("#upload_files", {
  170. url: "{{.UploadBasePath}}",
  171. paramName: "filenames",
  172. createImageThumbnails: false,
  173. maxFiles: null,
  174. maxFilesize: null,
  175. autoQueue: false,
  176. addRemoveLinks: false,
  177. autoProcessQueue: false,
  178. filesizeBase: 1000,
  179. previewTemplate: `<div class="d-flex align-items-center mb-2">
  180. <span class="bullet bullet-dot bg-primary me-2"></span>
  181. <div class="text-break text-wrap text-left"><span class="fs-5 fw-semibold" data-dz-name></span>&nbsp;(<span class="fs-5 fw-semibold" data-custom-size></span>)</div>
  182. </div>
  183. <div class="dz-error-message d-none" data-dz-errormessage></div>
  184. <div class="dz-progress d-none"><span class="dz-upload" data-dz-uploadprogress></span></div>
  185. `,
  186. init: function() {
  187. var dropzone = this;
  188. $("#upload_files_button").click(function(){
  189. if (canUpload()){
  190. uploadFiles(dropzone.getAcceptedFiles());
  191. }
  192. });
  193. }
  194. });
  195. dropzone.on("addedfile", file => {
  196. for (node of file.previewElement.querySelectorAll("[data-custom-size]")) {
  197. node.textContent = fileSizeIEC(file.size);
  198. }
  199. if (file.fullPath){
  200. for (var node of file.previewElement.querySelectorAll("[data-dz-name]")) {
  201. node.textContent = file.fullPath;
  202. }
  203. }
  204. lastAddedFile = Date.now();
  205. });
  206. });
  207. </script>
  208. {{- end}}