files.html 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642
  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. <style>
  10. div.dataTables_wrapper span.selected-info,
  11. div.dataTables_wrapper span.selected-item {
  12. margin-left: 0.5em;
  13. }
  14. </style>
  15. {{end}}
  16. {{define "page_body"}}
  17. <div id="errorMsg" class="card mb-4 border-left-warning" style="display: none;">
  18. <div id="errorTxt" class="card-body text-form-error"></div>
  19. </div>
  20. <div class="card shadow mb-4">
  21. <div class="card-header py-3">
  22. <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>
  23. </div>
  24. <div class="card-body">
  25. {{if .Error}}
  26. <div class="card mb-4 border-left-warning">
  27. <div class="card-body text-form-error">{{.Error}}</div>
  28. </div>
  29. {{end}}
  30. <div class="table-responsive">
  31. <table class="table table-hover nowrap" id="dataTable" width="100%" cellspacing="0">
  32. <thead>
  33. <tr>
  34. <th></th>
  35. <th>Type</th>
  36. <th>Name</th>
  37. <th>Size</th>
  38. <th>Last modified</th>
  39. </tr>
  40. </thead>
  41. </table>
  42. </div>
  43. </div>
  44. </div>
  45. {{end}}
  46. {{define "dialog"}}
  47. <div class="modal fade" id="createDirModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel"
  48. aria-hidden="true">
  49. <div class="modal-dialog" role="document">
  50. <div class="modal-content">
  51. <div class="modal-header">
  52. <h5 class="modal-title" id="deleteModalLabel">
  53. Create a new directory
  54. </h5>
  55. <button class="close" type="button" data-dismiss="modal" aria-label="Close">
  56. <span aria-hidden="true">&times;</span>
  57. </button>
  58. </div>
  59. <form id="create_dir_form" action="" method="POST">
  60. <div class="modal-body">
  61. <div class="form-group">
  62. <label for="directory_name" class="col-form-label">Name</label>
  63. <input type="text" class="form-control" id="directory_name" required>
  64. </div>
  65. </div>
  66. <div class="modal-footer">
  67. <button class="btn btn-secondary" type="button" data-dismiss="modal">Cancel</button>
  68. <button type="submit" class="btn btn-primary">Submit</button>
  69. </div>
  70. </form>
  71. </div>
  72. </div>
  73. </div>
  74. <div class="modal fade" id="uploadFilesModal" tabindex="-1" role="dialog" aria-labelledby="uploadFilesModalLabel"
  75. aria-hidden="true">
  76. <div class="modal-dialog" role="document">
  77. <div class="modal-content">
  78. <div class="modal-header">
  79. <h5 class="modal-title" id="uploadFilesModalLabel">
  80. Upload one or more files
  81. </h5>
  82. <button class="close" type="button" data-dismiss="modal" aria-label="Close">
  83. <span aria-hidden="true">&times;</span>
  84. </button>
  85. </div>
  86. <form id="upload_files_form" action="" method="POST" enctype="multipart/form-data">
  87. <div class="modal-body">
  88. <input type="file" class="form-control-file" id="files_name" name="filename" required multiple>
  89. </div>
  90. <div class="modal-footer">
  91. <button class="btn btn-secondary" type="button" data-dismiss="modal">Cancel</button>
  92. <button type="submit" class="btn btn-primary">Submit</button>
  93. </div>
  94. </form>
  95. </div>
  96. </div>
  97. </div>
  98. <div class="modal fade" id="renameModal" tabindex="-1" role="dialog" aria-labelledby="renameModalLabel"
  99. aria-hidden="true">
  100. <div class="modal-dialog" role="document">
  101. <div class="modal-content">
  102. <div class="modal-header">
  103. <h5 class="modal-title" id="renameModalLabel">
  104. Rename the selected item
  105. </h5>
  106. <button class="close" type="button" data-dismiss="modal" aria-label="Close">
  107. <span aria-hidden="true">&times;</span>
  108. </button>
  109. </div>
  110. <form id="rename_form" action="" method="POST">
  111. <div class="modal-body">
  112. <div class="form-group">
  113. <label for="rename_old_name" class="col-form-label">Old name</label>
  114. <input type="text" class="form-control" id="rename_old_name" readonly>
  115. </div>
  116. <div class="form-group">
  117. <label for="rename_new_name" class="col-form-label">New name</label>
  118. <input type="text" class="form-control" id="rename_new_name" required>
  119. </div>
  120. </div>
  121. <div class="modal-footer">
  122. <button class="btn btn-secondary" type="button" data-dismiss="modal">Cancel</button>
  123. <button type="submit" class="btn btn-primary">Submit</button>
  124. </div>
  125. </form>
  126. </div>
  127. </div>
  128. </div>
  129. <div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel"
  130. aria-hidden="true">
  131. <div class="modal-dialog" role="document">
  132. <div class="modal-content">
  133. <div class="modal-header">
  134. <h5 class="modal-title" id="deleteModalLabel">
  135. Confirmation required
  136. </h5>
  137. <button class="close" type="button" data-dismiss="modal" aria-label="Close">
  138. <span aria-hidden="true">&times;</span>
  139. </button>
  140. </div>
  141. <div class="modal-body">Do you want to delete the selected item?</div>
  142. <div class="modal-footer">
  143. <button class="btn btn-secondary" type="button" data-dismiss="modal">
  144. Cancel
  145. </button>
  146. <a class="btn btn-warning" href="#" onclick="deleteAction()">
  147. Delete
  148. </a>
  149. </div>
  150. </div>
  151. </div>
  152. </div>
  153. {{end}}
  154. {{define "extra_js"}}
  155. <script src="{{.StaticURL}}/vendor/datatables/jquery.dataTables.min.js"></script>
  156. <script src="{{.StaticURL}}/vendor/datatables/dataTables.bootstrap4.min.js"></script>
  157. <script src="{{.StaticURL}}/vendor/datatables/dataTables.buttons.min.js"></script>
  158. <script src="{{.StaticURL}}/vendor/datatables/buttons.bootstrap4.min.js"></script>
  159. <script src="{{.StaticURL}}/vendor/datatables/dataTables.fixedHeader.min.js"></script>
  160. <script src="{{.StaticURL}}/vendor/datatables/dataTables.responsive.min.js"></script>
  161. <script src="{{.StaticURL}}/vendor/datatables/responsive.bootstrap4.min.js"></script>
  162. <script src="{{.StaticURL}}/vendor/datatables/dataTables.checkboxes.min.js"></script>
  163. <script type="text/javascript">
  164. function getIconForFile(filename) {
  165. var extension = filename.slice((filename.lastIndexOf(".") - 1 >>> 0) + 2).toLowerCase();
  166. switch (extension) {
  167. case "doc":
  168. case "docx":
  169. case "odt":
  170. case "wps":
  171. return "far fa-file-word";
  172. case "ppt":
  173. case "pptx":
  174. return "far fa-file-powerpoint";
  175. case "xls":
  176. case "xlsx":
  177. case "ods":
  178. return "far fa-file-excel";
  179. case "pdf":
  180. return "far fa-file-pdf";
  181. case "webm":
  182. case "mkv":
  183. case "flv":
  184. case "vob":
  185. case "ogv":
  186. case "ogg":
  187. case "avi":
  188. case "ts":
  189. case "mov":
  190. case "wmv":
  191. case "asf":
  192. case "mpeg":
  193. case "mpv":
  194. case "3gp":
  195. return "far fa-file-video";
  196. case "jpeg":
  197. case "jpg":
  198. case "png":
  199. case "gif":
  200. case "webp":
  201. case "tiff":
  202. case "psd":
  203. case "bmp":
  204. case "svg":
  205. case "jp2":
  206. return "far fa-file-image";
  207. case "go":
  208. case "sh":
  209. case "bat":
  210. case "java":
  211. case "php":
  212. case "cs":
  213. case "asp":
  214. case "aspx":
  215. case "css":
  216. case "html":
  217. case "xhtml":
  218. case "htm":
  219. case "js":
  220. case "jsp":
  221. case "py":
  222. case "rb":
  223. case "cgi":
  224. case "c":
  225. case "cpp":
  226. case "h":
  227. case "hpp":
  228. case "kt":
  229. case "ktm":
  230. case "kts":
  231. case "swift":
  232. case "r":
  233. return "far fa-file-code";
  234. case "zip":
  235. case "zipx":
  236. case "rar":
  237. case "tar":
  238. case "gz":
  239. case "bz2":
  240. case "zstd":
  241. case "zst":
  242. case "sz":
  243. case "lz":
  244. case "lz4":
  245. case "xz":
  246. case "jar":
  247. return "far fa-file-archive";
  248. case "txt":
  249. case "rtf":
  250. case "json":
  251. case "xml":
  252. case "yaml":
  253. case "toml":
  254. case "log":
  255. case "csv":
  256. case "ini":
  257. case "cfg":
  258. return "far fa-file-alt";
  259. default:
  260. return "far fa-file";
  261. }
  262. }
  263. function getNameFromTypeName(typeName) {
  264. return typeName.split('_').slice(1).join('_');
  265. }
  266. function getTypeFromTypeName(typeName) {
  267. return typeName.split('_')[0];
  268. }
  269. function deleteAction() {
  270. var table = $('#dataTable').DataTable();
  271. table.button('delete:name').enable(false);
  272. var selected = table.column(0).checkboxes.selected()[0];
  273. var itemType = getTypeFromTypeName(selected);
  274. var itemName = getNameFromTypeName(selected);
  275. var path;
  276. if (itemType == "1"){
  277. path = '{{.DirsURL}}';
  278. } else {
  279. path = '{{.FilesURL}}';
  280. }
  281. path+='?path={{.CurrentDir}}'+fixedEncodeURIComponent("/"+itemName);
  282. $('#deleteModal').modal('hide');
  283. $.ajax({
  284. url: path,
  285. type: 'DELETE',
  286. dataType: 'json',
  287. headers: { 'X-CSRF-TOKEN': '{{.CSRFToken}}' },
  288. timeout: 15000,
  289. success: function (result) {
  290. location.reload();
  291. },
  292. error: function ($xhr, textStatus, errorThrown) {
  293. var txt = "Unable to delete the selected item";
  294. if ($xhr) {
  295. var json = $xhr.responseJSON;
  296. if (json) {
  297. if (json.message) {
  298. txt = json.message;
  299. }
  300. if (json.error) {
  301. txt += ": " + json.error;
  302. }
  303. }
  304. }
  305. $('#errorTxt').text(txt);
  306. $('#errorMsg').show();
  307. setTimeout(function () {
  308. $('#errorMsg').hide();
  309. }, 5000);
  310. }
  311. });
  312. }
  313. $(document).ready(function () {
  314. $("#create_dir_form").submit(function (event) {
  315. event.preventDefault();
  316. $('#createDirModal').modal('hide');
  317. var dirName = replaceSlash($("#directory_name").val());
  318. var path = '{{.DirsURL}}?path={{.CurrentDir}}' + fixedEncodeURIComponent("/"+dirName);
  319. $.ajax({
  320. url: path,
  321. type: 'POST',
  322. dataType: 'json',
  323. headers: { 'X-CSRF-TOKEN': '{{.CSRFToken}}' },
  324. timeout: 15000,
  325. success: function (result) {
  326. location.reload();
  327. },
  328. error: function ($xhr, textStatus, errorThrown) {
  329. var txt = "Unable to create the requested directory";
  330. if ($xhr) {
  331. var json = $xhr.responseJSON;
  332. if (json) {
  333. if (json.message) {
  334. txt = json.message;
  335. }
  336. if (json.error) {
  337. txt += ": " + json.error;
  338. }
  339. }
  340. }
  341. $('#errorTxt').text(txt);
  342. $('#errorMsg').show();
  343. setTimeout(function () {
  344. $('#errorMsg').hide();
  345. }, 5000);
  346. }
  347. });
  348. });
  349. $("#upload_files_form").submit(function (event){
  350. event.preventDefault();
  351. $('uploadFilesModal').modal('hide');
  352. var path = '{{.FilesURL}}?path={{.CurrentDir}}';
  353. $.ajax({
  354. url: path,
  355. type: 'POST',
  356. data: new FormData(this),
  357. processData: false,
  358. contentType: false,
  359. headers: { 'X-CSRF-TOKEN': '{{.CSRFToken}}' },
  360. timeout: 15000,
  361. success: function (result) {
  362. location.reload();
  363. },
  364. error: function ($xhr, textStatus, errorThrown) {
  365. var txt = "Error uploading files";
  366. if ($xhr) {
  367. var json = $xhr.responseJSON;
  368. if (json) {
  369. if (json.message) {
  370. txt = json.message;
  371. }
  372. if (json.error) {
  373. txt += ": " + json.error;
  374. }
  375. }
  376. }
  377. $('#errorTxt').text(txt);
  378. $('#errorMsg').show();
  379. setTimeout(function () {
  380. $('#errorMsg').hide();
  381. }, 5000);
  382. }
  383. });
  384. });
  385. $("#rename_form").submit(function (event){
  386. event.preventDefault();
  387. var table = $('#dataTable').DataTable();
  388. table.button('rename:name').enable(false);
  389. var selected = table.column(0).checkboxes.selected()[0];
  390. var itemType = getTypeFromTypeName(selected);
  391. var itemName = getNameFromTypeName(selected);
  392. var targetName = replaceSlash($("#rename_new_name").val());
  393. var path;
  394. if (itemType == "1"){
  395. path = '{{.DirsURL}}';
  396. } else {
  397. path = '{{.FilesURL}}';
  398. }
  399. path+='?path={{.CurrentDir}}'+fixedEncodeURIComponent("/"+itemName)+'&target={{.CurrentDir}}'+fixedEncodeURIComponent("/"+targetName);
  400. $('renameModal').modal('hide');
  401. $.ajax({
  402. url: path,
  403. type: 'PATCH',
  404. dataType: 'json',
  405. headers: { 'X-CSRF-TOKEN': '{{.CSRFToken}}' },
  406. timeout: 15000,
  407. success: function (result) {
  408. location.reload();
  409. },
  410. error: function ($xhr, textStatus, errorThrown) {
  411. var txt = "Error renaming item";
  412. if ($xhr) {
  413. var json = $xhr.responseJSON;
  414. if (json) {
  415. if (json.message) {
  416. txt = json.message;
  417. }
  418. if (json.error) {
  419. txt += ": " + json.error;
  420. }
  421. }
  422. }
  423. $('#errorTxt').text(txt);
  424. $('#errorMsg').show();
  425. setTimeout(function () {
  426. $('#errorMsg').hide();
  427. }, 5000);
  428. }
  429. });
  430. });
  431. $.fn.dataTable.ext.buttons.refresh = {
  432. text: '<i class="fas fa-sync-alt"></i>',
  433. name: 'refresh',
  434. titleAttr: "Refresh",
  435. action: function (e, dt, node, config) {
  436. location.reload();
  437. }
  438. };
  439. $.fn.dataTable.ext.buttons.download = {
  440. text: '<i class="fas fa-download"></i>',
  441. name: 'download',
  442. titleAttr: "Download Zip",
  443. action: function (e, dt, node, config) {
  444. var filesArray = [];
  445. var selected = dt.column(0).checkboxes.selected();
  446. for (i = 0; i < selected.length; i++) {
  447. filesArray.push(getNameFromTypeName(selected[i]));
  448. }
  449. var files = fixedEncodeURIComponent(JSON.stringify(filesArray));
  450. var downloadURL = '{{.DownloadURL}}';
  451. var currentDir = '{{.CurrentDir}}';
  452. window.location = `${downloadURL}?path=${currentDir}&files=${files}`;
  453. },
  454. enabled: false
  455. };
  456. $.fn.dataTable.ext.buttons.addFiles = {
  457. text: '<i class="fas fa-file-upload"></i>',
  458. name: 'addFiles',
  459. titleAttr: "Upload files",
  460. action: function (e, dt, node, config) {
  461. $('#uploadFilesModal').modal('show');
  462. },
  463. enabled: true
  464. };
  465. $.fn.dataTable.ext.buttons.addDirectory = {
  466. text: '<i class="fas fa-folder-plus"></i>',
  467. name: 'addDirectory',
  468. titleAttr: "Add directory",
  469. action: function (e, dt, node, config) {
  470. $("#directory_name").val("");
  471. $('#createDirModal').modal('show');
  472. },
  473. enabled: true
  474. };
  475. $.fn.dataTable.ext.buttons.rename = {
  476. text: '<i class="fas fa-edit"></i>',
  477. name: 'rename',
  478. titleAttr: "Rename",
  479. action: function (e, dt, node, config) {
  480. var selected = table.column(0).checkboxes.selected()[0];
  481. var itemName = getNameFromTypeName(selected);
  482. $("#rename_old_name").val(itemName);
  483. $("#rename_new_name").val("");
  484. $('#renameModal').modal('show');
  485. },
  486. enabled: false
  487. };
  488. $.fn.dataTable.ext.buttons.delete = {
  489. text: '<i class="fas fa-trash"></i>',
  490. name: 'delete',
  491. titleAttr: "Delete",
  492. action: function (e, dt, node, config) {
  493. $('#deleteModal').modal('show');
  494. },
  495. enabled: false
  496. };
  497. var table = $('#dataTable').DataTable({
  498. "ajax": {
  499. "url": "{{.DirsURL}}?path={{.CurrentDir}}",
  500. "dataSrc": "",
  501. "error": function ($xhr, textStatus, errorThrown) {
  502. $(".dataTables_processing").hide();
  503. var txt = "Failed to get directory listing";
  504. if ($xhr) {
  505. var json = $xhr.responseJSON;
  506. if (json) {
  507. if (json.message){
  508. txt += ": " + json.message;
  509. } else {
  510. txt += ": " + json.error;
  511. }
  512. }
  513. }
  514. $('#errorTxt').text(txt);
  515. $('#errorMsg').show();
  516. setTimeout(function () {
  517. $('#errorMsg').hide();
  518. }, 10000);
  519. }
  520. },
  521. "deferRender": true,
  522. "processing": true,
  523. "columns": [
  524. { "data": "type_name" },
  525. { "data": "type" },
  526. {
  527. "data": "name",
  528. "render": function (data, type, row) {
  529. if (type === 'display') {
  530. if (row["type"] == "1") {
  531. return `<i class="fas fa-folder"></i>&nbsp;<a href="${row['url']}">${data}</a>`;
  532. }
  533. if (row["size"] == "") {
  534. return `<i class="fas fa-external-link-alt"></i>&nbsp;<a href="${row['url']}">${data}</a>`;
  535. }
  536. var icon = getIconForFile(data);
  537. return `<i class="${icon}"></i>&nbsp;<a href="${row['url']}">${data}</a>`;
  538. }
  539. return data;
  540. }
  541. },
  542. { "data": "size" },
  543. { "data": "last_modified" }
  544. ],
  545. "buttons": [],
  546. "lengthChange": false,
  547. "columnDefs": [
  548. {
  549. "targets": [0],
  550. "checkboxes": {
  551. "selectCallback": function (nodes, selected) {
  552. var selectedItems = table.column(0).checkboxes.selected().length;
  553. var selectedText = "";
  554. if (selectedItems == 1) {
  555. selectedText = "1 item selected";
  556. } else if (selectedItems > 1) {
  557. selectedText = `${selectedItems} items selected`;
  558. }
  559. table.button('download:name').enable(selectedItems > 0);
  560. {{if .CanRename}}
  561. table.button('rename:name').enable(selectedItems == 1);
  562. {{end}}
  563. {{if .CanDelete}}
  564. table.button('delete:name').enable(selectedItems == 1);
  565. {{end}}
  566. $('#dataTable_info').find('span').remove();
  567. $("#dataTable_info").append('<span class="selected-info"><span class="selected-item">' + selectedText + '</span></span>');
  568. }
  569. },
  570. "orderable": false,
  571. "searchable": false
  572. },
  573. {
  574. "targets": [1],
  575. "visible": false,
  576. "searchable": false
  577. },
  578. {
  579. "targets": [3, 4],
  580. "searchable": false
  581. }
  582. ],
  583. "scrollX": false,
  584. "scrollY": false,
  585. "responsive": true,
  586. "language": {
  587. "processing": '<i class="fas fa-spinner fa-spin fa-3x fa-fw"></i><span class="sr-only">Loading...</span>',
  588. "loadingRecords": "",
  589. "emptyTable": "No files or folders"
  590. },
  591. "initComplete": function (settings, json) {
  592. table.button().add(0, 'refresh');
  593. table.button().add(0, 'pageLength');
  594. table.button().add(0, 'download');
  595. {{if .CanDelete}}
  596. table.button().add(0, 'delete');
  597. {{end}}
  598. {{if .CanRename}}
  599. table.button().add(0, 'rename');
  600. {{end}}
  601. {{if .CanCreateDirs}}
  602. table.button().add(0, 'addDirectory');
  603. {{end}}
  604. {{if .CanAddFiles}}
  605. table.button().add(0, 'addFiles');
  606. {{end}}
  607. table.buttons().container().appendTo('#dataTable_wrapper .col-md-6:eq(0)');
  608. },
  609. "orderFixed": [1, 'asc'],
  610. "order": [[2, 'asc']]
  611. });
  612. new $.fn.dataTable.FixedHeader(table);
  613. $.fn.dataTable.ext.errMode = 'none';
  614. });
  615. </script>
  616. {{end}}