files.html 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100
  1. {{template "base" .}}
  2. {{define "title"}}{{.Title}}{{end}}
  3. {{define "extra_css"}}
  4. <link href="{{.StaticURL}}/vendor/datatables/dataTables.bootstrap4.min.css" rel="stylesheet">
  5. <link href="{{.StaticURL}}/vendor/datatables/buttons.bootstrap4.min.css" rel="stylesheet">
  6. <link href="{{.StaticURL}}/vendor/datatables/fixedHeader.bootstrap4.min.css" rel="stylesheet">
  7. <link href="{{.StaticURL}}/vendor/datatables/responsive.bootstrap4.min.css" rel="stylesheet">
  8. <link href="{{.StaticURL}}/vendor/datatables/dataTables.checkboxes.css" rel="stylesheet">
  9. <link href="{{.StaticURL}}/vendor/lightbox2/css/lightbox.min.css" rel="stylesheet">
  10. <style>
  11. div.dataTables_wrapper span.selected-info,
  12. div.dataTables_wrapper span.selected-item {
  13. margin-left: 0.5em;
  14. }
  15. </style>
  16. {{end}}
  17. {{define "page_body"}}
  18. <div id="errorMsg" class="card mb-4 border-left-warning" style="display: none;">
  19. <div id="errorTxt" class="card-body text-form-error"></div>
  20. </div>
  21. <div class="card shadow mb-4">
  22. <div class="card-header py-3">
  23. <h6 class="m-0 font-weight-bold"><a href="{{.FilesURL}}?path=%2F"><i class="fas fa-home"></i>&nbsp;Home</a>&nbsp;{{range .Paths}}{{if eq .Href ""}}/{{.DirName}}{{else}}<a href="{{.Href}}">/{{.DirName}}</a>{{end}}{{end}}</h6>
  24. </div>
  25. <div class="card-body">
  26. {{if .Error}}
  27. <div class="card mb-4 border-left-warning">
  28. <div class="card-body text-form-error">{{.Error}}</div>
  29. </div>
  30. {{end}}
  31. <div class="table-responsive">
  32. <table class="table table-hover nowrap" id="dataTable" width="100%" cellspacing="0">
  33. <thead>
  34. <tr>
  35. <th></th>
  36. <th>Type</th>
  37. <th>Name</th>
  38. <th>Size</th>
  39. <th>Last modified</th>
  40. <th></th>
  41. <th></th>
  42. </tr>
  43. </thead>
  44. </table>
  45. </div>
  46. </div>
  47. </div>
  48. {{end}}
  49. {{define "dialog"}}
  50. <div class="modal fade" id="createDirModal" tabindex="-1" role="dialog" aria-labelledby="createDirModalLabel"
  51. aria-hidden="true">
  52. <div class="modal-dialog" role="document">
  53. <div class="modal-content">
  54. <div class="modal-header">
  55. <h5 class="modal-title" id="createDirModalLabel">
  56. Create a new directory
  57. </h5>
  58. <button class="close" type="button" data-dismiss="modal" aria-label="Close">
  59. <span aria-hidden="true">&times;</span>
  60. </button>
  61. </div>
  62. <form id="create_dir_form" action="" method="POST">
  63. <div class="modal-body">
  64. <div class="form-group">
  65. <label for="directory_name" class="col-form-label">Name</label>
  66. <input type="text" class="form-control" id="directory_name" required>
  67. </div>
  68. </div>
  69. <div class="modal-footer">
  70. <button class="btn btn-secondary" type="button" data-dismiss="modal">Cancel</button>
  71. <button type="submit" class="btn btn-primary">Submit</button>
  72. </div>
  73. </form>
  74. </div>
  75. </div>
  76. </div>
  77. <div class="modal fade" id="uploadFilesModal" tabindex="-1" role="dialog" aria-labelledby="uploadFilesModalLabel"
  78. aria-hidden="true">
  79. <div class="modal-dialog" role="document">
  80. <div class="modal-content">
  81. <div class="modal-header">
  82. <h5 class="modal-title" id="uploadFilesModalLabel">
  83. Upload one or more files
  84. </h5>
  85. <button class="close" type="button" data-dismiss="modal" aria-label="Close">
  86. <span aria-hidden="true">&times;</span>
  87. </button>
  88. </div>
  89. <form id="upload_files_form" action="{{.FilesURL}}?path={{.CurrentDir}}" method="POST" enctype="multipart/form-data">
  90. <div class="modal-body">
  91. <input type="file" class="form-control-file" id="files_name" name="filenames" required multiple>
  92. </div>
  93. <div class="modal-footer">
  94. <input type="hidden" name="_form_token" value="{{.CSRFToken}}">
  95. <button class="btn btn-secondary" type="button" data-dismiss="modal">Cancel</button>
  96. <button type="submit" class="btn btn-primary">Submit</button>
  97. </div>
  98. </form>
  99. </div>
  100. </div>
  101. </div>
  102. <div class="modal fade" id="renameModal" tabindex="-1" role="dialog" aria-labelledby="renameModalLabel"
  103. aria-hidden="true">
  104. <div class="modal-dialog" role="document">
  105. <div class="modal-content">
  106. <div class="modal-header">
  107. <h5 class="modal-title" id="renameModalLabel">
  108. Rename the selected item
  109. </h5>
  110. <button class="close" type="button" data-dismiss="modal" aria-label="Close">
  111. <span aria-hidden="true">&times;</span>
  112. </button>
  113. </div>
  114. <form id="rename_form" action="" method="POST">
  115. <div class="modal-body">
  116. <div class="form-group">
  117. <label for="rename_old_name" class="col-form-label">Old name</label>
  118. <input type="text" class="form-control" id="rename_old_name" readonly>
  119. </div>
  120. <div class="form-group">
  121. <label for="rename_new_dir" class="col-form-label">New base dir</label>
  122. <input type="text" class="form-control" id="rename_new_dir" required aria-describedby="renameNewDirHelpBlock">
  123. <small id="renameNewDirHelpBlock" class="form-text text-muted">
  124. Setting a directory other than the current one will move the item there. This directory must exists
  125. </small>
  126. </div>
  127. <div class="form-group">
  128. <label for="rename_new_name" class="col-form-label">New name</label>
  129. <input type="text" class="form-control" id="rename_new_name" required>
  130. </div>
  131. </div>
  132. <div class="modal-footer">
  133. <button class="btn btn-secondary" type="button" data-dismiss="modal">Cancel</button>
  134. <button type="submit" class="btn btn-primary">Submit</button>
  135. </div>
  136. </form>
  137. </div>
  138. </div>
  139. </div>
  140. <div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel"
  141. aria-hidden="true">
  142. <div class="modal-dialog" role="document">
  143. <div class="modal-content">
  144. <div class="modal-header">
  145. <h5 class="modal-title" id="deleteModalLabel">
  146. Confirmation required
  147. </h5>
  148. <button class="close" type="button" data-dismiss="modal" aria-label="Close">
  149. <span aria-hidden="true">&times;</span>
  150. </button>
  151. </div>
  152. <div class="modal-body">Do you want to delete the selected item/s?</div>
  153. <div class="modal-footer">
  154. <button class="btn btn-secondary" type="button" data-dismiss="modal">
  155. Cancel
  156. </button>
  157. <a class="btn btn-warning" href="#" onclick="deleteAction()">
  158. Delete
  159. </a>
  160. </div>
  161. </div>
  162. </div>
  163. </div>
  164. <div class="modal fade" id="spinnerModal" tabindex="-1" role="dialog" data-keyboard="false" data-backdrop="static">
  165. <div class="modal-dialog modal-dialog-centered justify-content-center" role="document">
  166. <span style="color: #333333;" class="fa fa-spinner fa-spin fa-3x"></span>
  167. <!-- <span id="uploadProgress" style="color: #3A3B3C;" class="mx-3"></span> -->
  168. </div>
  169. </div>
  170. {{end}}
  171. {{define "extra_js"}}
  172. <script src="{{.StaticURL}}/vendor/datatables/jquery.dataTables.min.js"></script>
  173. <script src="{{.StaticURL}}/vendor/datatables/dataTables.bootstrap4.min.js"></script>
  174. <script src="{{.StaticURL}}/vendor/datatables/dataTables.buttons.min.js"></script>
  175. <script src="{{.StaticURL}}/vendor/datatables/buttons.bootstrap4.min.js"></script>
  176. <script src="{{.StaticURL}}/vendor/datatables/dataTables.fixedHeader.min.js"></script>
  177. <script src="{{.StaticURL}}/vendor/datatables/dataTables.responsive.min.js"></script>
  178. <script src="{{.StaticURL}}/vendor/datatables/responsive.bootstrap4.min.js"></script>
  179. <script src="{{.StaticURL}}/vendor/datatables/dataTables.checkboxes.min.js"></script>
  180. <script src="{{.StaticURL}}/vendor/lightbox2/js/lightbox.min.js"></script>
  181. <script src="{{.StaticURL}}/vendor/pdfobject/pdfobject.min.js"></script>
  182. <script src="{{.StaticURL}}/vendor/codemirror/codemirror.js"></script>
  183. <script src="{{.StaticURL}}/vendor/codemirror/meta.js"></script>
  184. {{if .HasIntegrations}}
  185. <script type="text/javascript">
  186. var childReference = null;
  187. var checkerStarted = false;
  188. const childProps = new Map();
  189. function openExternalURL(url, fileLink, fileName){
  190. if (childReference == null || childReference.closed) {
  191. childProps.set('link', fileLink);
  192. childProps.set('url', url);
  193. childProps.set('file_name', fileName);
  194. childReference = window.open(url, '_blank');
  195. if (!checkerStarted){
  196. keepAlive();
  197. setInterval(checkExternalWindow, 300000);
  198. checkerStarted = true;
  199. }
  200. } else {
  201. $('#errorTxt').text('An external window is already open, please close it before trying to open a new one');
  202. $('#errorMsg').show();
  203. setTimeout(function () {
  204. $('#errorMsg').hide();
  205. }, 8000);
  206. }
  207. }
  208. function notifyBlobDownloadError(message) {
  209. if (childReference == null || childReference.closed) {
  210. console.log("external window null or closed, cannot notify download error");
  211. return;
  212. }
  213. childReference.postMessage({
  214. type: 'blobDownloadError',
  215. message: message
  216. }, childProps.get('url'));
  217. }
  218. function notifySave(status, message) {
  219. if (childReference == null || childReference.closed) {
  220. console.log("external window null or closed, cannot notify save");
  221. return;
  222. }
  223. childReference.postMessage({
  224. type: 'blobSaveResult',
  225. status: status,
  226. message: message
  227. }, childProps.get('url'));
  228. }
  229. window.addEventListener('message', (event) => {
  230. var url = childProps.get('url');
  231. if (!url || !url.startsWith(event.origin)){
  232. console.log("origin: "+event.origin+" does not match the expected one: "+url+" refusing message");
  233. return;
  234. }
  235. if (childReference == null || childReference.closed) {
  236. console.log("external window null or closed, refusing message");
  237. return;
  238. }
  239. switch (event.data.type){
  240. case 'ready':
  241. // the child is ready send some details
  242. childReference.postMessage({
  243. type: 'readyResponse',
  244. user: '{{.LoggedUser.Username}}',
  245. file_name: childProps.get('file_name')
  246. }, childProps.get('url'));
  247. break;
  248. case 'sendBlob':
  249. // we have to download the blob, this could require some time so
  250. // we first send a blobDownloadStart message so the child can
  251. // show a spinner or something similar
  252. var errorMessage = "Error downloading file";
  253. childReference.postMessage({
  254. type: 'blobDownloadStart'
  255. }, childProps.get('url'));
  256. // download the file and send it as blob to the child window
  257. async function downloadFileAsBlob(){
  258. var errorMessage = "Error downloading file";
  259. let response;
  260. try {
  261. response = await fetch(childProps.get('link'),{
  262. headers: {
  263. 'X-CSRF-TOKEN': '{{.CSRFToken}}'
  264. },
  265. credentials: 'same-origin',
  266. redirect: 'error'
  267. });
  268. } catch (e){
  269. throw Error(errorMessage+": " +e.message);
  270. }
  271. if (response.status == 200){
  272. let responseBlob;
  273. try {
  274. responseBlob = await response.blob();
  275. } catch (e){
  276. throw Error(errorMessage+" as blob: " +e.message);
  277. }
  278. let fileBlob = new File([responseBlob], childProps.get('file_name'), {type: responseBlob.type, lastModified: ""});
  279. childReference.postMessage({
  280. type: 'blob',
  281. file: fileBlob
  282. }, childProps.get('url'));
  283. } else {
  284. let jsonResponse;
  285. try {
  286. jsonResponse = await response.json();
  287. } catch(e){
  288. throw Error(errorMessage);
  289. }
  290. if (jsonResponse.message) {
  291. errorMessage = jsonResponse.message;
  292. }
  293. if (jsonResponse.error) {
  294. errorMessage += ": " + jsonResponse.error;
  295. }
  296. throw Error(errorMessage);
  297. }
  298. }
  299. downloadFileAsBlob().catch(function(error){
  300. notifyBlobDownloadError(error.message);
  301. $('#errorTxt').text(error.message);
  302. $('#errorMsg').show();
  303. setTimeout(function () {
  304. $('#errorMsg').hide();
  305. }, 5000);
  306. });
  307. break;
  308. case 'saveBlob':
  309. spinnerDone = false;
  310. $('#spinnerModal').modal('show');
  311. async function saveBlob() {
  312. var errorMessage = "Error saving external file";
  313. var uploadPath = '{{.FileURL}}?path={{.CurrentDir}}'+encodeURIComponent("/"+childProps.get('file_name'));
  314. let response;
  315. try {
  316. response = await fetch(uploadPath, {
  317. method: 'POST',
  318. headers: {
  319. 'X-CSRF-TOKEN': '{{.CSRFToken}}'
  320. },
  321. credentials: 'same-origin',
  322. redirect: 'error',
  323. body: event.data.file
  324. });
  325. } catch (e){
  326. throw Error(errorMessage+": " +e.message);
  327. }
  328. if (response.status == 201){
  329. $('#spinnerModal').modal('hide');
  330. notifySave("OK", "");
  331. setTimeout(function () {
  332. location.reload();
  333. }, 2000);
  334. } else {
  335. let jsonResponse;
  336. try {
  337. jsonResponse = await response.json();
  338. } catch(e){
  339. throw Error(errorMessage);
  340. }
  341. if (jsonResponse.message) {
  342. errorMessage = jsonResponse.message;
  343. }
  344. if (jsonResponse.error) {
  345. errorMessage += ": " + jsonResponse.error;
  346. }
  347. throw Error(errorMessage);
  348. }
  349. }
  350. saveBlob().catch(function(error){
  351. $('#spinnerModal').modal('hide');
  352. notifySave("KO", error.message);
  353. $('#errorTxt').text(error.message);
  354. $('#errorMsg').show();
  355. setTimeout(function () {
  356. $('#errorMsg').hide();
  357. }, 5000);
  358. });
  359. break;
  360. default:
  361. console.log("Unsupported message: "+JSON.stringify(event.data));
  362. }
  363. });
  364. function checkExternalWindow() {
  365. if (childReference == null || childReference.closed) {
  366. return;
  367. }
  368. keepAlive();
  369. }
  370. </script>
  371. {{end}}
  372. <script type="text/javascript">
  373. var spinnerDone = false;
  374. var escapeHTML = function ( t ) {
  375. return t
  376. .replace( /&/g, '&amp;' )
  377. .replace( /</g, '&lt;' )
  378. .replace( />/g, '&gt;' )
  379. .replace( /"/g, '&quot;' );
  380. };
  381. function shortenData(d, cutoff) {
  382. if ( typeof d !== 'string' ) {
  383. return d;
  384. }
  385. if ( d.length <= cutoff ) {
  386. return d;
  387. }
  388. var shortened = d.substr(0, cutoff-1);
  389. return shortened+'&#8230;';
  390. }
  391. function getIconForFile(filename) {
  392. var extension = filename.slice((filename.lastIndexOf(".") - 1 >>> 0) + 2).toLowerCase();
  393. switch (extension) {
  394. case "doc":
  395. case "docx":
  396. case "odt":
  397. case "wps":
  398. return "far fa-file-word";
  399. case "ppt":
  400. case "pptx":
  401. return "far fa-file-powerpoint";
  402. case "xls":
  403. case "xlsx":
  404. case "ods":
  405. return "far fa-file-excel";
  406. case "pdf":
  407. return "far fa-file-pdf";
  408. case "webm":
  409. case "mkv":
  410. case "flv":
  411. case "vob":
  412. case "ogv":
  413. case "ogg":
  414. case "avi":
  415. case "ts":
  416. case "mov":
  417. case "wmv":
  418. case "asf":
  419. case "mpeg":
  420. case "mpv":
  421. case "3gp":
  422. return "far fa-file-video";
  423. case "jpeg":
  424. case "jpg":
  425. case "png":
  426. case "gif":
  427. case "webp":
  428. case "tiff":
  429. case "psd":
  430. case "bmp":
  431. case "svg":
  432. case "jp2":
  433. return "far fa-file-image";
  434. case "go":
  435. case "sh":
  436. case "bat":
  437. case "java":
  438. case "php":
  439. case "cs":
  440. case "asp":
  441. case "aspx":
  442. case "css":
  443. case "html":
  444. case "xhtml":
  445. case "htm":
  446. case "js":
  447. case "jsp":
  448. case "py":
  449. case "rb":
  450. case "cgi":
  451. case "c":
  452. case "cpp":
  453. case "h":
  454. case "hpp":
  455. case "kt":
  456. case "ktm":
  457. case "kts":
  458. case "swift":
  459. case "r":
  460. return "far fa-file-code";
  461. case "zip":
  462. case "zipx":
  463. case "rar":
  464. case "tar":
  465. case "gz":
  466. case "bz2":
  467. case "zstd":
  468. case "zst":
  469. case "sz":
  470. case "lz":
  471. case "lz4":
  472. case "xz":
  473. case "jar":
  474. return "far fa-file-archive";
  475. case "txt":
  476. case "rtf":
  477. case "json":
  478. case "xml":
  479. case "yaml":
  480. case "toml":
  481. case "log":
  482. case "csv":
  483. case "ini":
  484. case "cfg":
  485. return "far fa-file-alt";
  486. default:
  487. return "far fa-file";
  488. }
  489. }
  490. function getNameFromMeta(meta) {
  491. return meta.split('_').slice(1).join('_');
  492. }
  493. function getTypeFromMeta(meta) {
  494. return meta.split('_')[0];
  495. }
  496. function deleteAction() {
  497. var table = $('#dataTable').DataTable();
  498. table.button('delete:name').enable(false);
  499. var selectedItems = table.column(0).checkboxes.selected()
  500. var has_errors = false;
  501. var index = 0;
  502. var success = 0;
  503. spinnerDone = false;
  504. $('#deleteModal').modal('hide');
  505. $('#spinnerModal').modal('show');
  506. function deleteItem() {
  507. if (index >= selectedItems.length || has_errors){
  508. $('#spinnerModal').modal('hide');
  509. spinnerDone = true;
  510. if (!has_errors){
  511. location.reload();
  512. } else {
  513. table.button('delete:name').enable(true);
  514. }
  515. return;
  516. }
  517. var selected = selectedItems[index];
  518. var itemType = getTypeFromMeta(selected);
  519. var itemName = getNameFromMeta(selected);
  520. var path;
  521. if (itemType == "1"){
  522. path = '{{.DirsURL}}';
  523. } else {
  524. path = '{{.FilesURL}}';
  525. }
  526. path+='?path={{.CurrentDir}}'+encodeURIComponent("/"+itemName);
  527. $.ajax({
  528. url: path,
  529. type: 'DELETE',
  530. dataType: 'json',
  531. headers: { 'X-CSRF-TOKEN': '{{.CSRFToken}}' },
  532. timeout: 60000,
  533. success: function (result) {
  534. index++;
  535. success++;
  536. deleteItem();
  537. },
  538. error: function ($xhr, textStatus, errorThrown) {
  539. index++;
  540. has_errors = true;
  541. var txt = "Unable to delete the selected item/s";
  542. if (success > 0){
  543. txt = "Not all the selected items have been deleted, please reload the page";
  544. }
  545. if ($xhr) {
  546. var json = $xhr.responseJSON;
  547. if (json) {
  548. if (json.message) {
  549. txt = json.message;
  550. }
  551. if (json.error) {
  552. txt += ": " + json.error;
  553. }
  554. }
  555. }
  556. $('#errorTxt').text(txt);
  557. $('#errorMsg').show();
  558. setTimeout(function () {
  559. $('#errorMsg').hide();
  560. }, 10000);
  561. deleteItem();
  562. }
  563. });
  564. }
  565. deleteItem();
  566. }
  567. function keepAlive() {
  568. $.ajax({
  569. url: '{{.ProfileURL}}',
  570. timeout: 15000
  571. });
  572. }
  573. $(document).ready(function () {
  574. $('#spinnerModal').on('shown.bs.modal', function () {
  575. if (spinnerDone){
  576. $('#spinnerModal').modal('hide');
  577. }
  578. });
  579. $("#create_dir_form").submit(function (event) {
  580. event.preventDefault();
  581. $('#createDirModal').modal('hide');
  582. var dirName = replaceSlash($("#directory_name").val());
  583. var path = '{{.DirsURL}}?path={{.CurrentDir}}' + encodeURIComponent("/"+dirName);
  584. $.ajax({
  585. url: path,
  586. type: 'POST',
  587. dataType: 'json',
  588. headers: { 'X-CSRF-TOKEN': '{{.CSRFToken}}' },
  589. timeout: 15000,
  590. success: function (result) {
  591. location.reload();
  592. },
  593. error: function ($xhr, textStatus, errorThrown) {
  594. var txt = "Unable to create the requested directory";
  595. if ($xhr) {
  596. var json = $xhr.responseJSON;
  597. if (json) {
  598. if (json.message) {
  599. txt = json.message;
  600. }
  601. if (json.error) {
  602. txt += ": " + json.error;
  603. }
  604. }
  605. }
  606. $('#errorTxt').text(txt);
  607. $('#errorMsg').show();
  608. setTimeout(function () {
  609. $('#errorMsg').hide();
  610. }, 5000);
  611. }
  612. });
  613. });
  614. $("#upload_files_form").submit(function (event){
  615. event.preventDefault();
  616. keepAlive();
  617. var keepAliveTimer = setInterval(keepAlive, 300000);
  618. var files = $("#files_name")[0].files;
  619. var has_errors = false;
  620. var index = 0;
  621. var success = 0;
  622. spinnerDone = false;
  623. $('#uploadFilesModal').modal('hide');
  624. $('#spinnerModal').modal('show');
  625. function uploadFile() {
  626. if (index >= files.length || has_errors){
  627. //console.log("upload done, index: "+index+" has errors: "+has_errors+" ok: "+success);
  628. clearInterval(keepAliveTimer);
  629. $('#spinnerModal').modal('hide');
  630. spinnerDone = true;
  631. if (!has_errors){
  632. location.reload();
  633. }
  634. return;
  635. }
  636. async function saveFile() {
  637. //console.log("save file, index: "+index);
  638. var errorMessage = "Error uploading files";
  639. let response;
  640. try {
  641. var f = files[index];
  642. var uploadPath = '{{.FileURL}}?path={{.CurrentDir}}'+encodeURIComponent("/"+f.name);
  643. var lastModified;
  644. try {
  645. lastModified = f.lastModified;
  646. } catch (e) {
  647. console.log("unable to get last modified time from file: "+e.message);
  648. lastModified = "";
  649. }
  650. response = await fetch(uploadPath, {
  651. method: 'POST',
  652. headers: {
  653. 'X-SFTPGO-MTIME': lastModified,
  654. 'X-CSRF-TOKEN': '{{.CSRFToken}}'
  655. },
  656. credentials: 'same-origin',
  657. redirect: 'error',
  658. body: f
  659. });
  660. } catch (e){
  661. throw Error(errorMessage+": " +e.message);
  662. }
  663. if (response.status == 201){
  664. index++;
  665. success++;
  666. uploadFile();
  667. } else {
  668. let jsonResponse;
  669. try {
  670. jsonResponse = await response.json();
  671. } catch(e){
  672. throw Error(errorMessage);
  673. }
  674. if (jsonResponse.message) {
  675. errorMessage = jsonResponse.message;
  676. }
  677. if (jsonResponse.error) {
  678. errorMessage += ": " + jsonResponse.error;
  679. }
  680. throw Error(errorMessage);
  681. }
  682. }
  683. saveFile().catch(function(error){
  684. index++;
  685. has_errors = true;
  686. $('#errorTxt').text(error.message);
  687. $('#errorMsg').show();
  688. setTimeout(function () {
  689. $('#errorMsg').hide();
  690. }, 10000);
  691. uploadFile();
  692. });
  693. }
  694. uploadFile();
  695. });
  696. $("#rename_form").submit(function (event){
  697. event.preventDefault();
  698. var table = $('#dataTable').DataTable();
  699. table.button('rename:name').enable(false);
  700. var selected = table.column(0).checkboxes.selected()[0];
  701. var itemType = getTypeFromMeta(selected);
  702. var itemName = getNameFromMeta(selected);
  703. var targetName = replaceSlash($("#rename_new_name").val());
  704. var targetDir = $("#rename_new_dir").val();
  705. if (targetDir != "/") {
  706. targetDir = targetDir.endsWith('/') ? targetDir.slice(0, -1) : targetDir;
  707. }
  708. if (targetDir.trim() == ""){
  709. targetDir = "{{.CurrentDir}}";
  710. } else {
  711. targetDir = encodeURIComponent(targetDir);
  712. }
  713. var path;
  714. if (itemType == "1"){
  715. path = '{{.DirsURL}}';
  716. } else {
  717. path = '{{.FilesURL}}';
  718. }
  719. path+='?path={{.CurrentDir}}'+encodeURIComponent("/"+itemName)+'&target='+targetDir+encodeURIComponent("/"+targetName);
  720. $('#renameModal').modal('hide');
  721. $.ajax({
  722. url: path,
  723. type: 'PATCH',
  724. dataType: 'json',
  725. headers: { 'X-CSRF-TOKEN': '{{.CSRFToken}}' },
  726. timeout: 15000,
  727. success: function (result) {
  728. location.reload();
  729. },
  730. error: function ($xhr, textStatus, errorThrown) {
  731. var txt = "Error renaming item";
  732. if ($xhr) {
  733. var json = $xhr.responseJSON;
  734. if (json) {
  735. if (json.message) {
  736. txt = json.message;
  737. }
  738. if (json.error) {
  739. txt += ": " + json.error;
  740. }
  741. }
  742. }
  743. $('#errorTxt').text(txt);
  744. $('#errorMsg').show();
  745. setTimeout(function () {
  746. $('#errorMsg').hide();
  747. }, 8000);
  748. var selectedItems = table.column(0).checkboxes.selected().length;
  749. table.button('rename:name').enable(selectedItems == 1);
  750. }
  751. });
  752. });
  753. $.fn.dataTable.ext.buttons.refresh = {
  754. text: '<i class="fas fa-sync-alt"></i>',
  755. name: 'refresh',
  756. titleAttr: "Refresh",
  757. action: function (e, dt, node, config) {
  758. location.reload();
  759. }
  760. };
  761. $.fn.dataTable.ext.buttons.download = {
  762. text: '<i class="fas fa-download"></i>',
  763. name: 'download',
  764. titleAttr: "Download Zip",
  765. action: function (e, dt, node, config) {
  766. var filesArray = [];
  767. var selected = dt.column(0).checkboxes.selected();
  768. for (i = 0; i < selected.length; i++) {
  769. filesArray.push(getNameFromMeta(selected[i]));
  770. }
  771. var files = encodeURIComponent(JSON.stringify(filesArray));
  772. var downloadURL = '{{.DownloadURL}}';
  773. var currentDir = '{{.CurrentDir}}';
  774. var ts = new Date().getTime().toString();
  775. window.location = `${downloadURL}?path=${currentDir}&files=${files}&_=${ts}`;
  776. },
  777. enabled: false
  778. };
  779. $.fn.dataTable.ext.buttons.addFiles = {
  780. text: '<i class="fas fa-file-upload"></i>',
  781. name: 'addFiles',
  782. titleAttr: "Upload files",
  783. action: function (e, dt, node, config) {
  784. document.getElementById("files_name").value = null;
  785. $('#uploadFilesModal').modal('show');
  786. },
  787. enabled: true
  788. };
  789. $.fn.dataTable.ext.buttons.addDirectory = {
  790. text: '<i class="fas fa-folder-plus"></i>',
  791. name: 'addDirectory',
  792. titleAttr: "Add directory",
  793. action: function (e, dt, node, config) {
  794. $("#directory_name").val("");
  795. $('#createDirModal').modal('show');
  796. },
  797. enabled: true
  798. };
  799. $.fn.dataTable.ext.buttons.rename = {
  800. text: '<i class="fas fa-pen"></i>',
  801. name: 'rename',
  802. titleAttr: "Rename",
  803. action: function (e, dt, node, config) {
  804. var selected = table.column(0).checkboxes.selected()[0];
  805. var itemName = getNameFromMeta(selected);
  806. $("#rename_old_name").val(itemName);
  807. $("#rename_new_dir").val(decodeURIComponent("{{.CurrentDir}}".replace(/\+/g, '%20')));
  808. $("#rename_new_name").val("");
  809. $('#renameModal').modal('show');
  810. },
  811. enabled: false
  812. };
  813. $.fn.dataTable.ext.buttons.delete = {
  814. text: '<i class="fas fa-trash"></i>',
  815. name: 'delete',
  816. titleAttr: "Delete",
  817. action: function (e, dt, node, config) {
  818. $('#deleteModal').modal('show');
  819. },
  820. enabled: false
  821. };
  822. $.fn.dataTable.ext.buttons.share = {
  823. text: '<i class="fas fa-share-alt"></i>',
  824. name: 'share',
  825. titleAttr: "Share",
  826. action: function (e, dt, node, config) {
  827. var filesArray = [];
  828. var selected = dt.column(0).checkboxes.selected();
  829. for (i = 0; i < selected.length; i++) {
  830. filesArray.push(getNameFromMeta(selected[i]));
  831. }
  832. var files = encodeURIComponent(JSON.stringify(filesArray));
  833. var shareURL = '{{.ShareURL}}';
  834. var currentDir = '{{.CurrentDir}}';
  835. var ts = new Date().getTime().toString();
  836. window.location = `${shareURL}?path=${currentDir}&files=${files}&_=${ts}`;
  837. },
  838. enabled: false
  839. };
  840. var table = $('#dataTable').DataTable({
  841. "ajax": {
  842. "url": "{{.DirsURL}}?path={{.CurrentDir}}",
  843. "dataSrc": "",
  844. "error": function ($xhr, textStatus, errorThrown) {
  845. $(".dataTables_processing").hide();
  846. var txt = "Failed to get directory listing";
  847. if ($xhr) {
  848. var json = $xhr.responseJSON;
  849. if (json) {
  850. if (json.message){
  851. txt += ": " + json.message;
  852. } else {
  853. txt += ": " + json.error;
  854. }
  855. }
  856. }
  857. $('#errorTxt').text(txt);
  858. $('#errorMsg').show();
  859. setTimeout(function () {
  860. $('#errorMsg').hide();
  861. }, 10000);
  862. }
  863. },
  864. "deferRender": true,
  865. "processing": true,
  866. "lengthMenu": [ 10, 25, 50, 100, 250, 500 ],
  867. "stateSave": true,
  868. "stateDuration": 0,
  869. "stateSaveParams": function (settings, data) {
  870. data.sftpgo_dir = '{{.CurrentDir}}';
  871. },
  872. "stateLoadParams": function (settings, data) {
  873. if (!data.sftpgo_dir || data.sftpgo_dir != '{{.CurrentDir}}'){
  874. data.start = 0;
  875. data.search.search = "";
  876. data.checkboxes = [];
  877. }
  878. },
  879. "columns": [
  880. { "data": "meta" },
  881. { "data": "type" },
  882. {
  883. "data": "name",
  884. "render": function (data, type, row) {
  885. if (type === 'display') {
  886. var title = "";
  887. var cssClass = "";
  888. var shortened = shortenData(data, 70);
  889. if (shortened != data){
  890. title = escapeHTML(data);
  891. cssClass = "ellipsis";
  892. }
  893. if (row["type"] == "1") {
  894. return `<i class="fas fa-folder"></i>&nbsp;<a class="${cssClass}" href="${row['url']}" title="${title}">${shortened}</a>`;
  895. }
  896. if (row["size"] == "") {
  897. return `<i class="fas fa-external-link-alt"></i>&nbsp;<a class="${cssClass}" href="${row['url']}" title="${title}">${shortened}</a>`;
  898. }
  899. var icon = getIconForFile(data);
  900. return `<i class="${icon}"></i>&nbsp;<a class="${cssClass}" href="${row['url']}" title="${title}">${shortened}</a>`;
  901. }
  902. return data;
  903. }
  904. },
  905. { "data": "size" },
  906. { "data": "last_modified" },
  907. { "data": "edit_url",
  908. "render": function (data, type, row) {
  909. if (type === 'display') {
  910. var filename = row["name"];
  911. var extension = filename.slice((filename.lastIndexOf(".") - 1 >>> 0) + 2).toLowerCase();
  912. if (data){
  913. if (extension == "csv" || extension == "bat" || CodeMirror.findModeByExtension(extension) != null){
  914. {{if .CanAddFiles}}
  915. return `<a href="${data}"><i class="fas fa-edit"></i></a>`;
  916. {{else}}
  917. return `<a href="${data}"><i class="fas fa-eye"></i></a>`;
  918. {{end}}
  919. }
  920. }
  921. if (row["type"] == "2") {
  922. switch (extension) {
  923. case "jpeg":
  924. case "jpg":
  925. case "png":
  926. case "gif":
  927. case "webp":
  928. case "bmp":
  929. case "svg":
  930. case "ico":
  931. var view_url = row['url']+"&inline=1";
  932. return `<a href="${view_url}" data-lightbox="${filename}" data-title="${filename}"><i class="fas fa-eye"></i></a>`;
  933. case "pdf":
  934. if (PDFObject.supportsPDFs){
  935. var view_url = row['url'];
  936. view_url = view_url.replace('{{.FilesURL}}','{{.ViewPDFURL}}');
  937. return `<a href="${view_url}" target="_blank"><i class="fas fa-eye"></i></a>`;
  938. }
  939. }
  940. }
  941. }
  942. return "";
  943. }
  944. },
  945. { "data": "ext_url",
  946. "render": function (data, type, row) {
  947. {{if .HasIntegrations}}
  948. if (type === 'display') {
  949. if (data){
  950. return `<a href="#" onclick="openExternalURL('${data}', '${row["ext_link"]}', '${row["name"]}');"><i class="fas fa-external-link-alt"></i></a>`;
  951. }
  952. }
  953. {{end}}
  954. return "";
  955. }
  956. }
  957. ],
  958. "buttons": [],
  959. "lengthChange": false,
  960. "columnDefs": [
  961. {
  962. "targets": [0],
  963. "checkboxes": {
  964. "selectCallback": function (nodes, selected) {
  965. var selectedItems = table.column(0).checkboxes.selected().length;
  966. var selectedText = "";
  967. if (selectedItems == 1) {
  968. selectedText = "1 item selected";
  969. } else if (selectedItems > 1) {
  970. selectedText = `${selectedItems} items selected`;
  971. }
  972. {{if .CanDownload}}
  973. table.button('download:name').enable(selectedItems > 0);
  974. {{end}}
  975. {{if .CanRename}}
  976. table.button('rename:name').enable(selectedItems == 1);
  977. {{end}}
  978. {{if .CanDelete}}
  979. table.button('delete:name').enable(selectedItems > 0);
  980. {{end}}
  981. {{if .CanShare}}
  982. table.button('share:name').enable(selectedItems > 0);
  983. {{end}}
  984. $('#dataTable_info').find('span').remove();
  985. $("#dataTable_info").append('<span class="selected-info"><span class="selected-item">' + selectedText + '</span></span>');
  986. }
  987. },
  988. "orderable": false,
  989. "searchable": false
  990. },
  991. {
  992. "targets": [1],
  993. "visible": false,
  994. "searchable": false
  995. },
  996. {
  997. "targets": [3, 4],
  998. "searchable": false
  999. },
  1000. {
  1001. "targets": [5, 6],
  1002. "orderable": false,
  1003. "searchable": false
  1004. }
  1005. ],
  1006. "scrollX": false,
  1007. "scrollY": false,
  1008. "responsive": true,
  1009. "language": {
  1010. "processing": '<i class="fas fa-spinner fa-spin fa-3x fa-fw"></i><span class="sr-only">Loading...</span>',
  1011. "loadingRecords": "",
  1012. "emptyTable": "No files or folders"
  1013. },
  1014. "initComplete": function (settings, json) {
  1015. table.button().add(0, 'refresh');
  1016. table.button().add(0, 'pageLength');
  1017. {{if .CanShare}}
  1018. table.button().add(0, 'share');
  1019. {{end}}
  1020. {{if .CanDownload}}
  1021. table.button().add(0, 'download');
  1022. {{end}}
  1023. {{if .CanDelete}}
  1024. table.button().add(0, 'delete');
  1025. {{end}}
  1026. {{if .CanRename}}
  1027. table.button().add(0, 'rename');
  1028. {{end}}
  1029. {{if .CanCreateDirs}}
  1030. table.button().add(0, 'addDirectory');
  1031. {{end}}
  1032. {{if .CanAddFiles}}
  1033. table.button().add(0, 'addFiles');
  1034. {{end}}
  1035. table.buttons().container().appendTo('.col-md-6:eq(0)', table.table().container());
  1036. },
  1037. "orderFixed": [1, 'asc'],
  1038. "order": [[2, 'asc']]
  1039. });
  1040. new $.fn.dataTable.FixedHeader(table);
  1041. $.fn.dataTable.ext.errMode = 'none';
  1042. });
  1043. </script>
  1044. {{end}}