files.html 46 KB

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