files.html 54 KB

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