files.html 81 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810
  1. <!--
  2. Copyright (C) 2023 Nicola Murino
  3. This WebUI uses the KeenThemes Mega Bundle, a proprietary theme:
  4. https://keenthemes.com/products/templates-mega-bundle
  5. KeenThemes HTML/CSS/JS components are allowed for use only within the
  6. SFTPGo product and restricted to be used in a resealable HTML template
  7. that can compete with KeenThemes products anyhow.
  8. This WebUI is allowed for use only within the SFTPGo product and
  9. therefore cannot be used in derivative works/products without an
  10. explicit grant from the SFTPGo Team (support@sftpgo.com).
  11. -->
  12. {{template "base" .}}
  13. {{- define "title"}}{{.Title}}{{- end}}
  14. {{- define "page_body"}}
  15. {{- template "errmsg" .Error}}
  16. <div class="card card-flush shadow-sm">
  17. <div class="card-header pt-8">
  18. <div class="card-title">
  19. <div class="d-flex align-items-center position-relative my-1">
  20. <i class="ki-duotone ki-magnifier fs-1 position-absolute ms-6">
  21. <span class="path1"></span>
  22. <span class="path2"></span>
  23. </i>
  24. <input type="text" data-kt-filemanager-table-filter="search" class="form-control form-control-solid w-250px ps-15" placeholder="Search Files & Folders" />
  25. </div>
  26. </div>
  27. <div class="card-toolbar">
  28. <div class="d-flex justify-content-end" data-kt-filemanager-table-toolbar="base">
  29. {{- if .CanCreateDirs}}
  30. <button type="button" class="btn btn-flex btn-light-primary me-3" onclick="showCreateNewFolder(0);">
  31. <i class="ki-duotone ki-add-folder fs-2">
  32. <span class="path1"></span>
  33. <span class="path2"></span>
  34. </i>
  35. New Folder
  36. </button>
  37. {{- end}}
  38. {{- if .CanAddFiles}}
  39. <button type="button" class="btn btn-flex btn-primary" data-bs-toggle="modal" data-bs-target="#modal_upload">
  40. <i class="ki-duotone ki-folder-up fs-2">
  41. <span class="path1"></span>
  42. <span class="path2"></span>
  43. </i>
  44. Upload Files
  45. </button>
  46. {{- end}}
  47. </div>
  48. <div class="d-flex justify-content-end align-items-center d-none" data-kt-filemanager-table-toolbar="selected">
  49. <div class="fw-bold me-5">
  50. <span class="me-2" data-kt-filemanager-table-select="selected_count"></span>
  51. </div>
  52. <div class="form-check form-switch form-check-custom form-check-solid me-5" data-kt-filemanager-table-select="select_all_pages_container">
  53. <input class="form-check-input" type="checkbox" id="id_select_all_pages" data-kt-filemanager-table-select="select_all_pages" />
  54. <label class="form-check-label fw-semibold text-gray-900" for="id_select_all_pages">
  55. Select across pages
  56. </label>
  57. </div>
  58. {{- if or .CanDownload .CanDelete}}
  59. <div>
  60. <button type="button" class="btn btn-light-primary rotate" data-kt-menu-trigger="click" data-kt-menu-placement="bottom">
  61. Actions
  62. <i class="ki-duotone ki-down fs-3 rotate-180 ms-3 me-0"></i>
  63. </button>
  64. <div class="menu menu-sub menu-sub-dropdown menu-column menu-rounded menu-gray-800 menu-state-bg-light-primary fw-semibold w-auto min-w-200 mw-300px py-4" data-kt-menu="true">
  65. {{- if .CanDownload}}
  66. <div class="menu-item px-3">
  67. <a href="#" class="menu-link px-3 fs-6" data-kt-filemanager-table-select="download_selected">
  68. Download
  69. </a>
  70. </div>
  71. {{- end}}
  72. {{- if not .ShareUploadBaseURL}}
  73. {{- if or .CanRename .CanAddFiles}}
  74. <div class="menu-item px-3">
  75. <a href="#" class="menu-link px-3 fs-6" data-kt-filemanager-table-select="move_or_copy_selected">
  76. Move or copy
  77. </a>
  78. </div>
  79. {{- end}}
  80. {{- if .CanShare}}
  81. <div class="menu-item px-3">
  82. <a href="#" class="menu-link px-3 fs-6" data-kt-filemanager-table-select="share_selected">
  83. Share
  84. </a>
  85. </div>
  86. {{- end}}
  87. {{- end}}
  88. {{- if .CanDelete}}
  89. <div class="menu-item px-3">
  90. <a href="#" class="menu-link px-3 text-danger fs-6" data-kt-filemanager-table-select="delete_selected">
  91. Delete
  92. </a>
  93. </div>
  94. {{- end}}
  95. </div>
  96. </div>
  97. {{- end}}
  98. </div>
  99. </div>
  100. </div>
  101. <div class="card-body">
  102. <div class="d-flex flex-stack">
  103. <div class="badge badge-lg badge-light-primary">
  104. <div class="d-flex align-items-center flex-wrap">
  105. <i class="ki-duotone ki-home fs-1 text-primary me-3"></i>
  106. <a href="{{.FilesURL}}?path=%2F">Home</a>
  107. {{- range .Paths}}
  108. <i class="ki-duotone ki-right fs-2x text-primary mx-1"></i>
  109. {{- if eq .Href ""}}
  110. <span>{{.DirName}}</span>
  111. {{- else}}
  112. <a href="{{.Href}}">{{.DirName}}</a>
  113. {{- end}}
  114. {{- end}}
  115. </div>
  116. </div>
  117. </div>
  118. <div id="file_manager_new_folder" class="d-flex align-items-center py-7 d-none">
  119. <span>
  120. <i class="ki-duotone ki-folder fs-2x text-primary me-4">
  121. <span class="path1"></span>
  122. <span class="path2"></span>
  123. </i>
  124. </span>
  125. <input id="file_manager_new_folder_input" type="text" name="new_folder_name" placeholder="Enter the new folder name" class="form-control mw-250px me-3" />
  126. <button class="btn btn-icon btn-light-primary me-3" id="file_manager_add_folder" onclick="createNewFolder();">
  127. <span class="indicator-label">
  128. <i class="ki-duotone ki-check fs-1"></i>
  129. </span>
  130. <span class="indicator-progress">
  131. <span class="spinner-border spinner-border-sm align-middle"></span>
  132. </span>
  133. </button>
  134. <button class="btn btn-icon btn-light-danger" id="file_manager_cancel_folder" onclick="hideCreateNewFolder(0);">
  135. <i class="ki-duotone ki-cross fs-1">
  136. <span class="path1"></span>
  137. <span class="path2"></span>
  138. </i>
  139. </button>
  140. </div>
  141. <table id="file_manager_list" class="table align-middle table-row-dashed fs-6 gy-5">
  142. <thead>
  143. <tr class="text-start text-muted fw-bold fs-6 gs-0">
  144. <th class="w-10px pe-2">
  145. <div class="form-check form-check-sm form-check-custom form-check-solid me-3">
  146. <input id="select_checkbox" class="form-check-input" type="checkbox"/>
  147. </div>
  148. </th>
  149. <th></th>
  150. <th class="min-w-250px">Name</th>
  151. <th class="min-w-10px">Size</th>
  152. <th class="min-w-125px">Last Modified</th>
  153. <th class="w-125px"></th>
  154. </tr>
  155. </thead>
  156. <tbody id="file_manager_list_body" class="text-gray-700 fw-semibold">
  157. </tbody>
  158. </table>
  159. </div>
  160. </div>
  161. {{- end}}
  162. {{- define "extra_css"}}
  163. <link href="{{.StaticURL}}/assets/plugins/custom/datatables/datatables.bundle.css" rel="stylesheet" type="text/css"/>
  164. <link href="{{.StaticURL}}/vendor/glightbox/glightbox.min.css" rel="stylesheet" type="text/css"/>
  165. <link href="{{.StaticURL}}/vendor/video-js/video-js.min.css" rel="stylesheet" type="text/css"/>
  166. <style>
  167. .gslide-description-bg {
  168. background: var(--bs-app-bg-color) !important;
  169. opacity: 0.9;
  170. }
  171. </style>
  172. {{- end}}
  173. {{- define "extra_js"}}
  174. <script src="{{.StaticURL}}/assets/plugins/custom/datatables/datatables.bundle.js"></script>
  175. <script src="{{.StaticURL}}/vendor/glightbox/glightbox.min.js"></script>
  176. <script src="{{.StaticURL}}/vendor/pdfobject/pdfobject.min.js"></script>
  177. <script src="{{.StaticURL}}/vendor/video-js/video.min.js"></script>
  178. {{- if not .ShareUploadBaseURL}}
  179. <script type="text/javascript">
  180. const supportedEditExtensions = ["csv", "bat", "dyalog", "apl", "asc", "pgp", "sig", "asn", "asn1", "b", "bf",
  181. "c", "h", "ino", "cpp", "c++", "cc", "cxx", "hpp", "h++", "hh", "hxx", "cob", "cpy", "cbl", "cs", "clj",
  182. "cljc", "cljx", "cljs", "gss", "cmake", "cmake.in", "coffee", "cl", "lisp", "el", "cyp", "cypher",
  183. "pyx", "pxd", "pxi", "cr", "css", "cql", "d", "dart", "diff", "patch", "dtd", "dylan", "dyl", "intr",
  184. "ecl", "edn", "e", "elm", "ejs", "erb", "erl", "sql", "factor", "forth", "fth", "4th", "f", "for", "f77",
  185. "f90", "f95", "fsharp", "s", "go", "groovy", "gradle", "haml", "hs", "lhs", "hx", "hxml", "aspx",
  186. "html", "htm", "handlebars", "hbs", "pro", "jade", "pug", "java", "jsp", "js", "json", "map", "jsonld",
  187. "jsx", "j2", "jinja", "jinja2", "jl", "kt", "less", "ls", "lua", "markdown", "md", "mkd", "m", "nb", "wl",
  188. "wls", "mo", "mps", "mbox", "nsh", "nsi", "nt", "nq", "m", "mm", "ml", "mli", "mll", "mly", "oz", "p",
  189. "pas", "pl", "pm", "php", "php3", "php4", "php5", "php7", "phtml", "pig", "txt", "text", "conf", "def",
  190. "list", "log", "pls", "ps1", "psd1", "psm1", "properties", "ini", "in", "proto", "BUILD", "bzl", "py",
  191. "pyw", "pp", "q", "r", "rst", "spec", "rb", "rs", "sas", "sass", "scala", "scm", "ss", "scss", "sh",
  192. "ksh", "bash", "siv", "sieve", "slim", "st", "tpl", "sml", "sig", "fun", "smackspec", "soy", "rq", "sparql",
  193. "nut", "styl", "swift", "text", "ltx", "tex", "v", "sv", "svh", "tcl", "textile", "toml", "1", "2", "3", "4",
  194. "5", "6", "7", "8", "9", "ttcn", "ttcn3", "ttcnpp", "cfg", "ttl", "ts", "tsx", "webidl", "vb", "vbs",
  195. "vtl", "vhd", "vhdl", "vue", "xml", "xsl", "xsd", "svg", "xy", "xquery", "ys", "yaml", "yml", "z80",
  196. "mscgen", "mscin", "msc", "xu", "msgenny", "wat", "wast"];
  197. const supportedEditFilenames = ["readme", "dockerfile", "pkgbuild"]
  198. </script>
  199. {{- end}}
  200. <script type="text/javascript">
  201. function keepAlive() {
  202. //{{- if not .ShareUploadBaseURL}}
  203. axios.get('{{.ProfileURL}}',{
  204. timeout: 15000
  205. }).catch(function (error){});
  206. //{{- end}}
  207. }
  208. //{{- if not .ShareUploadBaseURL}}
  209. var KTDatatablesFoldersExplorer = function () {
  210. var dt;
  211. var curDir;
  212. var initDatatable = function (dirsURL) {
  213. $('#errorModalMsg').addClass("d-none");
  214. dt = $("#dirsbrowser_list").DataTable({
  215. ajax: {
  216. url: dirsURL,
  217. dataSrc: "",
  218. error: function ($xhr, textStatus, errorThrown) {
  219. $(".dataTables_processing").hide();
  220. let txt = "Failed to get directory listing";
  221. if ($xhr) {
  222. let json = $xhr.responseJSON;
  223. if (json) {
  224. if (json.message) {
  225. txt += ": " + json.message;
  226. } else {
  227. txt += ": " + json.error;
  228. }
  229. }
  230. }
  231. $('#errorModalTxt').text(txt);
  232. $('#errorModalMsg').removeClass("d-none");
  233. }
  234. },
  235. deferRender: true,
  236. processing: true,
  237. stateSave: false,
  238. columns: [
  239. {
  240. data: "meta"
  241. },
  242. {
  243. data: "name",
  244. render: function (data, type, row) {
  245. if (type === 'display') {
  246. data = escapeHTML(data);
  247. let dirPath = row['dir_path'];
  248. return `<div class="d-flex align-items-center">
  249. <i class="ki-duotone ki-folder fs-1 text-primary me-4">
  250. <i class="path1"></i>
  251. <i class="path2"></i>
  252. </i>
  253. <a href="#" onclick="onDirBrowserClick('${dirPath}');" class="text-gray-700 text-hover-primary">${data}</a>
  254. </div>`
  255. }
  256. return data;
  257. }
  258. }
  259. ],
  260. lengthChange: true,
  261. columnDefs: [
  262. {
  263. targets: 0,
  264. visible: false,
  265. searchable: false
  266. },
  267. {
  268. targets: 1,
  269. visible: true,
  270. searchable: true
  271. }
  272. ],
  273. language: {
  274. loadingRecords: "",
  275. emptyTable: "No more subfolders in here"
  276. },
  277. order: [1, 'asc']
  278. });
  279. dt.on('draw', function () {
  280. let navHTML = `<i class="ki-duotone ki-home fs-1 text-primary me-3"></i>
  281. <a href="#" onclick="onDirBrowserClick('%2F');">Home</a>`;
  282. let p = "/";
  283. if (curDir && curDir != "%2F") {
  284. p = decodeURIComponent(curDir.replace(/\+/g, '%20'));
  285. const dirPaths = p.split("/");
  286. let fullPath = "%2F";
  287. for (idx in dirPaths) {
  288. let dir = dirPaths[idx];
  289. if (dir) {
  290. if (fullPath.endsWith("%2F")){
  291. fullPath += encodeURIComponent(dir);
  292. } else {
  293. fullPath += encodeURIComponent("/" + dir);
  294. }
  295. dir = escapeHTML(dir);
  296. navHTML += `<i class="ki-duotone ki-right fs-2x text-primary mx-1"></i>
  297. <a href="#" onclick="onDirBrowserClick('${fullPath}');">${dir}</a>`;
  298. }
  299. }
  300. }
  301. document.getElementById("dirs_browser_nav").innerHTML = navHTML;
  302. $('#move_copy_folder').val(p);
  303. });
  304. }
  305. var handleCreateNewFolder = function() {
  306. $('#dirsbrowser_add_folder').click(function(){
  307. let errDivEl = $('#errorModalMsg');
  308. let errTxtEl = $('#errorModalTxt');
  309. let dirName = replaceSlash($("#dirsbrowser_new_folder_input").val());
  310. let submitButton = document.querySelector('#dirsbrowser_add_folder');
  311. let cancelButton = document.querySelector('#dirsbrowser_cancel_folder');
  312. errDivEl.addClass("d-none");
  313. if (!dirName){
  314. errTxtEl.text("Folder name is required");
  315. errDivEl.removeClass("d-none");
  316. return;
  317. }
  318. let path = '{{.DirsURL}}?path='+ curDir + encodeURIComponent("/"+dirName);
  319. submitButton.setAttribute('data-kt-indicator', 'on');
  320. submitButton.disabled = true;
  321. cancelButton.disabled = true;
  322. axios.post(path, null, {
  323. timeout: 15000,
  324. headers: {
  325. 'X-CSRF-TOKEN': '{{.CSRFToken}}'
  326. },
  327. validateStatus: function (status) {
  328. return status == 201;
  329. }
  330. }).then(function (response) {
  331. dt.ajax.reload();
  332. errDivEl.addClass("d-none");
  333. submitButton.removeAttribute('data-kt-indicator');
  334. submitButton.disabled = false;
  335. cancelButton.disabled = false;
  336. $('#dirsbrowser_new_folder').addClass("d-none");
  337. }).catch(function (error) {
  338. let errorMessage = "Unable to create the new folder";
  339. if (error && error.response) {
  340. if (error.response.data.message) {
  341. errorMessage = error.response.data.message;
  342. }
  343. if (error.response.data.error) {
  344. errorMessage += ": " + error.response.data.error;
  345. }
  346. }
  347. errTxtEl.text(errorMessage);
  348. errDivEl.removeClass("d-none");
  349. submitButton.removeAttribute('data-kt-indicator');
  350. submitButton.disabled = false;
  351. cancelButton.disabled = false;
  352. });
  353. });
  354. }
  355. return {
  356. init: function (url, dirPath) {
  357. curDir = dirPath;
  358. if (dt) {
  359. dt.ajax.url(url).load();
  360. return;
  361. }
  362. initDatatable(url);
  363. handleCreateNewFolder();
  364. }
  365. }
  366. }();
  367. //{{- end}}
  368. </script>
  369. <script type="text/javascript">
  370. var KTDatatablesServerSide = function () {
  371. // Shared variables
  372. //var table;
  373. var dt;
  374. var lightbox;
  375. // Private functions
  376. var initDatatable = function () {
  377. $('#errorMsg').addClass("d-none");
  378. dt = $("#file_manager_list").DataTable({
  379. ajax: {
  380. url: "{{.DirsURL}}?path={{.CurrentDir}}",
  381. dataSrc: "",
  382. error: function ($xhr, textStatus, errorThrown) {
  383. $(".dataTables_processing").hide();
  384. let txt = "Failed to get directory listing";
  385. if ($xhr) {
  386. let json = $xhr.responseJSON;
  387. if (json) {
  388. if (json.message){
  389. txt += ": " + json.message;
  390. } else {
  391. txt += ": " + json.error;
  392. }
  393. }
  394. }
  395. $('#errorTxt').text(txt);
  396. $('#errorMsg').removeClass("d-none");
  397. }
  398. },
  399. deferRender: true,
  400. processing: true,
  401. lengthMenu: [ 10, 25, 50, 100, 250, 500 ],
  402. select: {
  403. style: 'multi',
  404. selector: 'td:first-child input[type="checkbox"]',
  405. className: 'row-selected'
  406. },
  407. stateSave: true,
  408. stateDuration: 0,
  409. stateSaveParams: function (settings, data) {
  410. data.sftpgo_dir = '{{.CurrentDir}}';
  411. },
  412. stateLoadParams: function (settings, data) {
  413. if (!data.sftpgo_dir || data.sftpgo_dir != '{{.CurrentDir}}'){
  414. data.start = 0;
  415. data.search.search = "";
  416. }
  417. if (data.search.search){
  418. const filterSearch = document.querySelector('[data-kt-filemanager-table-filter="search"]');
  419. filterSearch.value = data.search.search;
  420. }
  421. },
  422. columns: [
  423. {
  424. data: "meta",
  425. render: function (data, type) {
  426. if (type == 'display'){
  427. return `
  428. <div class="form-check form-check-sm form-check-custom form-check-solid">
  429. <input class="form-check-input" type="checkbox" />
  430. </div>`;
  431. }
  432. return data;
  433. }
  434. },
  435. { data: "type" },
  436. {
  437. data: "name",
  438. render: function (data, type, row) {
  439. if (type === 'display') {
  440. data = escapeHTML(data);
  441. let icon_name = "ki-file";
  442. if (row["type"] == "1") {
  443. icon_name = "ki-folder";
  444. } else if (row["size"] === "") {
  445. icon_name = "ki-file-right"
  446. }
  447. return `<div class="d-flex align-items-center">
  448. <i class="ki-duotone ${icon_name} fs-2x text-primary me-4">
  449. <i class="path1"></i>
  450. <i class="path2"></i>
  451. </i>
  452. <a href="${row['url']}" class="text-gray-800 text-hover-primary">${data}</a>
  453. </div>`
  454. }
  455. return data;
  456. }
  457. },
  458. {
  459. data: "size",
  460. render: function (data, type) {
  461. if (type === 'display') {
  462. if (data || data === 0){
  463. return fileSizeIEC(data);
  464. }
  465. return "";
  466. }
  467. return data;
  468. }
  469. },
  470. { data: "last_modified" },
  471. {
  472. data: "edit_url",
  473. className: 'text-end',
  474. render: function (data, type, row) {
  475. if (type === 'display') {
  476. let previewDiv = "";
  477. if (row["type"] == "2") {
  478. let filename = escapeHTML(row["name"]);
  479. let extension = filename.slice((filename.lastIndexOf(".") - 1 >>> 0) + 2).toLowerCase();
  480. switch (extension) {
  481. case "jpeg":
  482. case "jpg":
  483. case "png":
  484. case "gif":
  485. case "webp":
  486. case "bmp":
  487. case "svg":
  488. case "ico":
  489. let desc = escapeHTML(filename).replace(/"/g, '&quot;');
  490. previewDiv = `<div class="ms-2" data-kt-filemanger-table="view_item">
  491. <a href="${row['url']}" data-gallery="gallery" data-glightbox="description: &lt;span class=&quot;fs-5 fw-bold&quot;&gt;${desc}&lt;/span&gt;" class="btn btn-sm btn-icon btn-light btn-active-light-primary glightbox">
  492. <i class="ki-duotone ki-eye fs-5 m-0">
  493. <span class="path1"></span>
  494. <span class="path2"></span>
  495. <span class="path3"></span>
  496. </i>
  497. </a>
  498. </div>`;
  499. break;
  500. case "mp4":
  501. case "mov":
  502. case "webm":
  503. case "ogv":
  504. case "ogg":
  505. case "mp3":
  506. case "wav":
  507. let mediaType = 'video/mp4';
  508. if (extension == 'webm'){
  509. mediaType = 'video/webm';
  510. } else if (extension == 'ogg' || extension == 'ogv') {
  511. mediaType = 'video/ogg';
  512. } else if (extension == 'mp3'){
  513. mediaType = 'audio/mpeg';
  514. } else if (extension == 'wav'){
  515. mediaType = 'audio/wav';
  516. }
  517. let name = b64EncodeUnicode(row["name"]);
  518. previewDiv = `<div class="ms-2" data-kt-filemanger-table="view_item">
  519. <a href="#" onclick="openVideoPlayer('${name}', '${row['url']}', '${mediaType}');" class="btn btn-sm btn-icon btn-light btn-active-light-primary">
  520. <i class="ki-duotone ki-eye fs-5 m-0">
  521. <span class="path1"></span>
  522. <span class="path2"></span>
  523. <span class="path3"></span>
  524. </i>
  525. </a>
  526. </div>`;
  527. break;
  528. case "pdf":
  529. if (PDFObject.supportsPDFs){
  530. let view_url = row['url'];
  531. view_url = view_url.replace('{{.FilesURL}}','{{.ViewPDFURL}}');
  532. previewDiv = `<div class="ms-2" data-kt-filemanger-table="view_item">
  533. <a href="${view_url}" target="_blank" class="btn btn-sm btn-icon btn-light btn-active-light-primary">
  534. <i class="ki-duotone ki-eye fs-5 m-0">
  535. <span class="path1"></span>
  536. <span class="path2"></span>
  537. <span class="path3"></span>
  538. </i>
  539. </a>
  540. </div>`;
  541. }
  542. break;
  543. default:
  544. //{{- if not .ShareUploadBaseURL}}
  545. if (data && (supportedEditExtensions.includes(extension) || supportedEditFilenames.includes(filename.toLowerCase()))){
  546. previewDiv = `<div class="ms-2" data-kt-filemanger-table="view_item">
  547. <a href="${data}" target="_blank" class="btn btn-sm btn-icon btn-light btn-active-light-primary">
  548. <i class="ki-duotone ki-eye fs-5 m-0">
  549. <span class="path1"></span>
  550. <span class="path2"></span>
  551. <span class="path3"></span>
  552. </i>
  553. </a>
  554. </div>`;
  555. }
  556. //{{- end}}
  557. }
  558. }
  559. let meta = b64EncodeUnicode(row["meta"]);
  560. let more = `{{- if not .ShareUploadBaseURL}}
  561. {{- if or .CanRename .CanAddFiles .CanShare .CanDelete }}
  562. <div class="ms-2">
  563. <button type="button" class="btn btn-sm btn-icon btn-light btn-active-light-primary" data-kt-menu-trigger="click" data-kt-menu-placement="bottom-end">
  564. <i class="ki-duotone ki-dots-square fs-5 m-0">
  565. <span class="path1"></span>
  566. <span class="path2"></span>
  567. <span class="path3"></span>
  568. <span class="path4"></span>
  569. </i>
  570. </button>
  571. <div class="menu menu-sub menu-sub-dropdown menu-column menu-rounded menu-gray-700 menu-state-bg-light-primary fw-semibold fs-7 w-150px py-4" data-kt-menu="true">
  572. {{- if .CanRename}}
  573. <div class="menu-item px-3">
  574. <a href="#" onclick="renameItem('${meta}');" class="menu-link px-3" data-kt-filemanager-table="rename">Rename</a>
  575. </div>
  576. {{- end}}
  577. {{- if or .CanRename .CanAddFiles}}
  578. <div class="menu-item px-3">
  579. <a href="#" onclick="moveOrCopyItem('${meta}');" class="menu-link px-3" data-kt-filemanager-table-filter="move_or_copy">Move or copy</a>
  580. </div>
  581. {{- end}}
  582. {{- if .CanShare}}
  583. <div class="menu-item px-3">
  584. <a href="#" onclick="shareItem('${meta}');" class="menu-link px-3" data-kt-filemanager-table="share">Share</a>
  585. </div>
  586. {{- end}}
  587. {{- if .CanDelete}}
  588. <div class="menu-item px-3">
  589. <a href="#" onclick="deleteItem('${meta}');" class="menu-link text-danger px-3" data-kt-filemanager-table-filter="delete">Delete</a>
  590. </div>
  591. {{- end}}
  592. </div>
  593. </div>
  594. {{- end}}
  595. {{- end}}`;
  596. return `<div class="d-flex justify-content-end">
  597. ${previewDiv}
  598. ${more}
  599. </div>
  600. `;
  601. }
  602. return "";
  603. }
  604. }
  605. ],
  606. lengthChange: true,
  607. columnDefs: [
  608. {
  609. targets: 0,
  610. orderable: false,
  611. searchable: false,
  612. },
  613. {
  614. targets: 1,
  615. visible: false,
  616. searchable: false
  617. },
  618. {
  619. targets: 2,
  620. visible: true,
  621. searchable: true
  622. },
  623. {
  624. targets: [3,4],
  625. searchable: false
  626. },
  627. {
  628. targets: 5,
  629. orderable: false,
  630. searchable: false
  631. }
  632. ],
  633. language: {
  634. loadingRecords: "",
  635. emptyTable: "No files or folders"
  636. },
  637. orderFixed: [1, 'asc'],
  638. order: [2, 'asc']
  639. });
  640. //table = dt.$;
  641. dt.on('draw', function () {
  642. lightbox.reload();
  643. KTMenu.createInstances();
  644. let targets = document.querySelectorAll('#file_manager_list_body tr');
  645. if (targets) {
  646. KTUtil.each(targets, function (target) {
  647. if (!target){
  648. return;
  649. }
  650. let checkbox = target.querySelector('.form-check-input');
  651. if (!checkbox) {
  652. return;
  653. }
  654. if (target.classList.contains('row-selected')) {
  655. checkbox.checked = true;
  656. } else {
  657. checkbox.checked = false;
  658. }
  659. });
  660. }
  661. toggleToolbars();
  662. });
  663. dt.on('user-select', function(e, dt, type, cell, originalEvent){
  664. let pageSelected = dt.rows({ selected: true, page: 'current' }).count();
  665. let totalSelected = dt.rows({ selected: true, search: 'applied' }).count();
  666. if (totalSelected > pageSelected){
  667. let currentIndexes = [];
  668. dt.rows({ page: 'current' }).every(function (rowIdx, tableLoop, rowLoop){
  669. currentIndexes.push(rowIdx);
  670. });
  671. dt.rows().deselect();
  672. currentIndexes.forEach((idx) => {
  673. dt.row(idx).select();
  674. });
  675. totalSelected = dt.rows({ selected: true, search: 'applied' }).count();
  676. }
  677. if ($(originalEvent.target).is(':checked')){
  678. pageSelected++;
  679. totalSelected++;
  680. } else {
  681. pageSelected--;
  682. totalSelected--;
  683. }
  684. handleToogleToolbar(pageSelected, totalSelected);
  685. });
  686. }
  687. function handleToogleToolbar(pageSelected, totalSelected) {
  688. const toolbarBase = document.querySelector('[data-kt-filemanager-table-toolbar="base"]');
  689. const toolbarSelected = document.querySelector('[data-kt-filemanager-table-toolbar="selected"]');
  690. const selectedCount = document.querySelector('[data-kt-filemanager-table-select="selected_count"]');
  691. const selectAllContainer = document.querySelector('[data-kt-filemanager-table-select="select_all_pages_container"]');
  692. const selectAllCheck = document.querySelector('[data-kt-filemanager-table-select="select_all_pages"]');
  693. if (pageSelected > 0) {
  694. let pageTotal = dt.rows({ page: 'current' }).count();
  695. if (pageSelected === pageTotal){
  696. selectAllContainer.classList.remove("d-none");
  697. $('#select_checkbox').prop("checked", true);
  698. } else {
  699. $('#select_checkbox').prop("checked", false);
  700. selectAllCheck.checked = false;
  701. selectAllContainer.classList.add('d-none');
  702. }
  703. if (selectedCount){
  704. selectedCount.innerHTML = `${totalSelected} Selected`;
  705. }
  706. if (toolbarBase){
  707. toolbarBase.classList.add('d-none');
  708. }
  709. if (toolbarSelected){
  710. toolbarSelected.classList.remove('d-none');
  711. }
  712. } else {
  713. $('#select_checkbox').prop("checked", false);
  714. selectAllCheck.checked = false;
  715. if (toolbarBase) {
  716. toolbarBase.classList.remove('d-none');
  717. }
  718. if (toolbarBase) {
  719. toolbarSelected.classList.add('d-none');
  720. }
  721. }
  722. }
  723. var initLightbox = function() {
  724. lightbox = GLightbox({
  725. slideHTML: `<div class="gslide">
  726. <div class="gslide-inner-content">
  727. <div class="ginner-container">
  728. <div class="gslide-media">
  729. </div>
  730. <div class="gslide-description gslide-description-bg">
  731. <div class="gdesc-inner">
  732. <h4 class="gslide-title"></h4>
  733. <div class="gslide-desc"></div>
  734. </div>
  735. </div>
  736. </div>
  737. </div>
  738. </div>`,
  739. preload: false,
  740. closeOnOutsideClick: false
  741. });
  742. }
  743. var handleSearchDatatable = function () {
  744. const filterSearch = document.querySelector('[data-kt-filemanager-table-filter="search"]');
  745. filterSearch.addEventListener('keyup', function (e) {
  746. dt.rows().deselect();
  747. dt.search(e.target.value, true, false).draw();
  748. });
  749. }
  750. var initToggleToolbar = function () {
  751. const selectAllCheck = document.querySelector('[data-kt-filemanager-table-select="select_all_pages"]');
  752. if (selectAllCheck){
  753. selectAllCheck.addEventListener('change', function(e){
  754. if (this.checked){
  755. dt.rows({ search: 'applied' }).select();
  756. } else {
  757. let selectedIndexes = [];
  758. dt.rows({ selected: true, page: 'current' }).every(function (rowIdx, tableLoop, rowLoop){
  759. selectedIndexes.push(rowIdx);
  760. });
  761. dt.rows().deselect();
  762. selectedIndexes.forEach((idx) => {
  763. dt.row(idx).select();
  764. });
  765. }
  766. toggleToolbars();
  767. })
  768. }
  769. const downloadButton = document.querySelector('[data-kt-filemanager-table-select="download_selected"]');
  770. if (downloadButton){
  771. downloadButton.addEventListener('click', function(e){
  772. let filesArray = [];
  773. dt.rows({ selected: true, search: 'applied' }).every(function (rowIdx, tableLoop, rowLoop){
  774. let row = dt.row(rowIdx);
  775. filesArray.push(getNameFromMeta(row.data()['meta']));
  776. });
  777. let token = "{{.CSRFToken}}";
  778. let downloadURL = '{{.DownloadURL}}';
  779. let currentDir = '{{.CurrentDir}}';
  780. let ts = new Date().getTime().toString();
  781. let files = JSON.stringify(filesArray);
  782. $(`<form method="post" action="${downloadURL}?path=${currentDir}&_=${ts}" target="_blank">
  783. <input type="hidden" name="_form_token" value="${token}">
  784. <textarea name="files" hidden>${files}</textarea>
  785. </form>`).appendTo('body').submit().remove();
  786. });
  787. }
  788. const moveOrCopyButton = document.querySelector('[data-kt-filemanager-table-select="move_or_copy_selected"]');
  789. if (moveOrCopyButton){
  790. moveOrCopyButton.addEventListener('click', function(e){
  791. $('#errorMsg').addClass("d-none");
  792. $('#move_copy_name_container').addClass("d-none");
  793. $('#move_copy_source').val("");
  794. $('#modal_move_or_copy').modal('show');
  795. KTDatatablesFoldersExplorer.init('{{.DirsURL}}?dirtree=1&path={{.CurrentDir}}', '{{.CurrentDir}}');
  796. });
  797. }
  798. const shareButton = document.querySelector('[data-kt-filemanager-table-select="share_selected"]');
  799. if (shareButton){
  800. shareButton.addEventListener('click', function(e){
  801. let filesArray = [];
  802. dt.rows({ selected: true, search: 'applied' }).every(function (rowIdx, tableLoop, rowLoop){
  803. let row = dt.row(rowIdx);
  804. filesArray.push(getNameFromMeta(row.data()['meta']));
  805. });
  806. let files = encodeURIComponent(JSON.stringify(filesArray));
  807. let shareURL = '{{.ShareURL}}';
  808. let currentDir = '{{.CurrentDir}}';
  809. let ts = new Date().getTime().toString();
  810. window.open(`${shareURL}?path=${currentDir}&files=${files}&_=${ts}`,'_blank');
  811. });
  812. }
  813. const deleteButton = document.querySelector('[data-kt-filemanager-table-select="delete_selected"]');
  814. if (deleteButton) {
  815. deleteButton.addEventListener('click', function(e){
  816. ModalAlert.fire({
  817. text: "Do you want to delete the selected item/s? This action is irreversible",
  818. icon: "warning",
  819. confirmButtonText: "Delete",
  820. cancelButtonText: 'Cancel',
  821. customClass: {
  822. confirmButton: "btn btn-danger",
  823. cancelButton: 'btn btn-secondary'
  824. }
  825. }).then((result) => {
  826. if (result.isConfirmed){
  827. let hasError = false;
  828. let deleted = 0;
  829. let index = 0;
  830. let errDivEl = $('#errorMsg');
  831. let errTxtEl = $('#errorTxt');
  832. errDivEl.addClass("d-none");
  833. let selectedRowsIdx = [];
  834. dt.rows({ selected: true, search: 'applied' }).every(function (rowIdx, tableLoop, rowLoop){
  835. selectedRowsIdx.push(rowIdx);
  836. });
  837. if (selectedRowsIdx.length == 0){
  838. return;
  839. }
  840. keepAlive();
  841. let keepAliveTimer = setInterval(keepAlive, 300000);
  842. $('#loading_message').text("");
  843. KTApp.showPageLoading();
  844. function deleteSelected() {
  845. if (index >= selectedRowsIdx.length || hasError){
  846. clearInterval(keepAliveTimer);
  847. KTApp.hidePageLoading();
  848. if (!hasError){
  849. location.reload();
  850. }
  851. return;
  852. }
  853. let meta = dt.row(selectedRowsIdx[index]).data()['meta'];
  854. let attrs = getDeleteReqAttrs(meta);
  855. let deleteTxt = "";
  856. if (selectedRowsIdx.length > 1){
  857. let name = getNameFromMeta(meta);
  858. deleteTxt = `Delete ${index+1}/${selectedRowsIdx.length}: ${name}`;
  859. }
  860. $('#loading_message').text(deleteTxt);
  861. axios.delete(attrs.path,{
  862. timeout: attrs.reqTimeout,
  863. headers: {
  864. 'X-CSRF-TOKEN': '{{.CSRFToken}}'
  865. },
  866. validateStatus: function (status) {
  867. return status == 200;
  868. }
  869. }).then(function(response){
  870. index++;
  871. deleted++;
  872. deleteSelected();
  873. }).catch(function(error){
  874. index++;
  875. hasError = true;
  876. let errorMessage = "Unable to delete the selected item/s";
  877. if (deleted > 0){
  878. errorMessage = "Not all the selected items have been deleted, please reload the page";
  879. }
  880. if (error && error.response) {
  881. if (error.response.data.message) {
  882. errorMessage = error.response.data.message;
  883. }
  884. if (error.response.data.error) {
  885. errorMessage += ": " + error.response.data.error;
  886. }
  887. }
  888. errTxtEl.text(errorMessage);
  889. errDivEl.removeClass("d-none");
  890. deleteSelected();
  891. });
  892. }
  893. deleteSelected();
  894. }
  895. });
  896. });
  897. }
  898. $('#select_checkbox').on("click", function(){
  899. let targets = document.querySelectorAll('#file_manager_list_body .form-check-input');
  900. if (!targets){
  901. return;
  902. }
  903. dt.rows().deselect();
  904. let isChecked = $(this).is(':checked');
  905. if (isChecked){
  906. dt.rows({page: 'current'}).select();
  907. }
  908. KTUtil.each(targets, function (target) {
  909. if (target){
  910. target.checked = isChecked;
  911. }
  912. });
  913. toggleToolbars();
  914. });
  915. toggleToolbars();
  916. }
  917. var toggleToolbars = function () {
  918. let pageSelected = dt.rows({ selected: true, page: 'current' }).count();
  919. let totalSelected = dt.rows({ selected: true , search: 'applied'}).count();
  920. handleToogleToolbar(pageSelected, totalSelected);
  921. }
  922. return {
  923. init: function () {
  924. initLightbox();
  925. initDatatable();
  926. handleSearchDatatable();
  927. initToggleToolbar();
  928. }
  929. }
  930. }();
  931. function getNameFromMeta(meta) {
  932. return meta.split('_').slice(1).join('_');
  933. }
  934. function getTypeFromMeta(meta) {
  935. return meta.split('_')[0];
  936. }
  937. //{{- if not .ShareUploadBaseURL}}
  938. function onDirBrowserClick(dirPath) {
  939. KTDatatablesFoldersExplorer.init('{{.DirsURL}}?dirtree=1&path='+dirPath, dirPath);
  940. }
  941. function moveOrCopyItem(meta) {
  942. $('#errorMsg').addClass("d-none");
  943. let decodedMeta = UnicodeDecodeB64(meta);
  944. $('#move_copy_name_container').removeClass("d-none");
  945. $('#move_copy_source').val(meta);
  946. $('#move_copy_name').val(getNameFromMeta(decodedMeta));
  947. $('#modal_move_or_copy').modal('show');
  948. KTDatatablesFoldersExplorer.init('{{.DirsURL}}?dirtree=1&path={{.CurrentDir}}', '{{.CurrentDir}}');
  949. }
  950. function getMoveOtCopyItems() {
  951. let items = [];
  952. let targetDir = $("#move_copy_folder").val();
  953. if (targetDir != "/") {
  954. targetDir = targetDir.endsWith('/') ? targetDir.slice(0, -1) : targetDir;
  955. }
  956. if (targetDir.trim() == ""){
  957. targetDir = "{{.CurrentDir}}";
  958. } else {
  959. targetDir = encodeURIComponent(targetDir);
  960. }
  961. if ($('#move_copy_name_container').hasClass("d-none")){
  962. let dt = $('#file_manager_list').DataTable();
  963. dt.rows({ selected: true, search: 'applied' }).every(function (rowIdx, tableLoop, rowLoop){
  964. let row = dt.row(rowIdx);
  965. let sourceName = getNameFromMeta(row.data()['meta']);
  966. items.push({
  967. targetDir: targetDir,
  968. sourceName: sourceName,
  969. targetName: sourceName
  970. });
  971. });
  972. } else {
  973. let meta = UnicodeDecodeB64($('#move_copy_source').val());
  974. let sourceName = getNameFromMeta(meta);
  975. items.push({
  976. targetDir: targetDir,
  977. sourceName: sourceName,
  978. targetName: $("#move_copy_name").val()
  979. });
  980. }
  981. return items;
  982. }
  983. function doCopy() {
  984. let items = getMoveOtCopyItems();
  985. if (items.length == 0){
  986. return;
  987. }
  988. keepAlive();
  989. let keepAliveTimer = setInterval(keepAlive, 300000);
  990. let hasError = false;
  991. let index = 0;
  992. let errDivEl = $('#errorMsg');
  993. let errTxtEl = $('#errorTxt');
  994. errDivEl.addClass("d-none");
  995. $('#loading_message').text("");
  996. KTApp.showPageLoading();
  997. function copyItem() {
  998. if (index >= items.length || hasError){
  999. clearInterval(keepAliveTimer);
  1000. KTApp.hidePageLoading();
  1001. if (!hasError){
  1002. location.reload();
  1003. }
  1004. return;
  1005. }
  1006. let item = items[index];
  1007. let sourcePath = decodeURIComponent('{{.CurrentDir}}');
  1008. if (!sourcePath.endsWith('/')){
  1009. sourcePath+="/";
  1010. }
  1011. sourcePath+=item.sourceName;
  1012. let targetPath = decodeURIComponent(item.targetDir);
  1013. if (!targetPath.endsWith('/')){
  1014. targetPath+="/";
  1015. }
  1016. targetPath+=item.targetName;
  1017. if (items.length > 1){
  1018. let msgTxt = `${sourcePath} => ${targetPath}`;
  1019. msgTxt = `Copy ${index+1}/${items.length}: ${msgTxt}`;
  1020. $('#loading_message').text(msgTxt);
  1021. }
  1022. let path = '{{.FileActionsURL}}/copy';
  1023. path+='?path={{.CurrentDir}}'+encodeURIComponent("/"+item.sourceName)+'&target='+item.targetDir+encodeURIComponent("/"+item.targetName);
  1024. axios.post(path, null, {
  1025. timeout: 180000,
  1026. headers: {
  1027. 'X-CSRF-TOKEN': '{{.CSRFToken}}'
  1028. },
  1029. validateStatus: function (status) {
  1030. return status == 200;
  1031. }
  1032. }).then(function (response) {
  1033. index++;
  1034. copyItem();
  1035. }).catch(function (error) {
  1036. index++;
  1037. hasError = true;
  1038. let errorMessage = "Error copying item";
  1039. if (error && error.response) {
  1040. if (error.response.data.message) {
  1041. errorMessage = error.response.data.message;
  1042. }
  1043. if (error.response.data.error) {
  1044. errorMessage += ": " + error.response.data.error;
  1045. }
  1046. }
  1047. errTxtEl.text(errorMessage);
  1048. errDivEl.removeClass("d-none");
  1049. copyItem();
  1050. });
  1051. }
  1052. copyItem();
  1053. }
  1054. function doMove() {
  1055. let items = getMoveOtCopyItems();
  1056. if (items.length == 0){
  1057. return;
  1058. }
  1059. keepAlive();
  1060. let keepAliveTimer = setInterval(keepAlive, 300000);
  1061. let hasError = false;
  1062. let index = 0;
  1063. let errDivEl = $('#errorMsg');
  1064. let errTxtEl = $('#errorTxt');
  1065. errDivEl.addClass("d-none");
  1066. $('#loading_message').text("");
  1067. KTApp.showPageLoading();
  1068. function moveItem() {
  1069. if (index >= items.length || hasError){
  1070. clearInterval(keepAliveTimer);
  1071. KTApp.hidePageLoading();
  1072. if (!hasError){
  1073. location.reload();
  1074. }
  1075. return;
  1076. }
  1077. let item = items[index];
  1078. let sourcePath = decodeURIComponent('{{.CurrentDir}}');
  1079. if (!sourcePath.endsWith('/')){
  1080. sourcePath+="/";
  1081. }
  1082. sourcePath+=item.sourceName;
  1083. let targetPath = decodeURIComponent(item.targetDir);
  1084. if (!targetPath.endsWith('/')){
  1085. targetPath+="/";
  1086. }
  1087. targetPath+=item.targetName;
  1088. if (items.length > 1){
  1089. let msgTxt = `${sourcePath} => ${targetPath}`;
  1090. msgTxt = `Move ${index+1}/${items.length}: ${msgTxt}`;
  1091. $('#loading_message').text(msgTxt);
  1092. }
  1093. let path = '{{.FileActionsURL}}/move';
  1094. path+='?path={{.CurrentDir}}'+encodeURIComponent("/"+item.sourceName)+'&target='+item.targetDir+encodeURIComponent("/"+item.targetName);
  1095. axios.post(path, null, {
  1096. timeout: 180000,
  1097. headers: {
  1098. 'X-CSRF-TOKEN': '{{.CSRFToken}}'
  1099. },
  1100. validateStatus: function (status) {
  1101. return status == 200;
  1102. }
  1103. }).then(function (response) {
  1104. index++;
  1105. moveItem();
  1106. }).catch(function (error) {
  1107. index++;
  1108. hasError = true;
  1109. let errorMessage = "Error moving item";
  1110. if (error && error.response) {
  1111. if (error.response.data.message) {
  1112. errorMessage = error.response.data.message;
  1113. }
  1114. if (error.response.data.error) {
  1115. errorMessage += ": " + error.response.data.error;
  1116. }
  1117. }
  1118. errTxtEl.text(errorMessage);
  1119. errDivEl.removeClass("d-none");
  1120. moveItem();
  1121. });
  1122. }
  1123. moveItem();
  1124. }
  1125. function getDeleteReqAttrs(meta) {
  1126. let path;
  1127. let reqTimeout = 15000;
  1128. let itemType = getTypeFromMeta(meta);
  1129. let itemName = getNameFromMeta(meta);
  1130. if (itemType == "1"){
  1131. path = '{{.DirsURL}}';
  1132. reqTimeout = 120000
  1133. } else {
  1134. path = '{{.FilesURL}}';
  1135. }
  1136. path+='?path={{.CurrentDir}}'+encodeURIComponent("/"+itemName);
  1137. return { path, reqTimeout}
  1138. }
  1139. function deleteItem(meta) {
  1140. let errDivEl = $('#errorMsg');
  1141. let errTxtEl = $('#errorTxt');
  1142. errDivEl.addClass("d-none");
  1143. meta = UnicodeDecodeB64(meta);
  1144. let itemName = getNameFromMeta(meta);
  1145. ModalAlert.fire({
  1146. text: `Do you want to delete "${itemName}"? This action is irreversible`,
  1147. icon: "warning",
  1148. confirmButtonText: "Delete",
  1149. cancelButtonText: 'Cancel',
  1150. customClass: {
  1151. confirmButton: "btn btn-danger",
  1152. cancelButton: 'btn btn-secondary'
  1153. }
  1154. }).then((result) => {
  1155. if (result.isConfirmed){
  1156. $('#loading_message').text("");
  1157. KTApp.showPageLoading();
  1158. let attrs = getDeleteReqAttrs(meta);
  1159. axios.delete(attrs.path, {
  1160. timeout: attrs.reqTimeout,
  1161. headers: {
  1162. 'X-CSRF-TOKEN': '{{.CSRFToken}}'
  1163. },
  1164. validateStatus: function (status) {
  1165. return status == 200;
  1166. }
  1167. }).then(function(response){
  1168. location.reload();
  1169. }).catch(function(error){
  1170. KTApp.hidePageLoading();
  1171. let errorMessage = `Unable to delete "${itemName}"`;
  1172. if (error && error.response) {
  1173. if (error.response.data.message) {
  1174. errorMessage = error.response.data.message;
  1175. }
  1176. if (error.response.data.error) {
  1177. errorMessage += ": " + error.response.data.error;
  1178. }
  1179. }
  1180. errTxtEl.text(errorMessage);
  1181. errDivEl.removeClass("d-none");
  1182. });
  1183. }
  1184. });
  1185. }
  1186. function shareItem(meta) {
  1187. meta = UnicodeDecodeB64(meta);
  1188. let filesArray = [];
  1189. filesArray.push(getNameFromMeta(meta));
  1190. let files = encodeURIComponent(JSON.stringify(filesArray));
  1191. let shareURL = '{{.ShareURL}}';
  1192. let currentDir = '{{.CurrentDir}}';
  1193. let ts = new Date().getTime().toString();
  1194. window.open(`${shareURL}?path=${currentDir}&files=${files}&_=${ts}`,'_blank');
  1195. }
  1196. function renameItem(meta) {
  1197. $('#errorMsg').addClass("d-none");
  1198. let decodedMeta = UnicodeDecodeB64(meta);
  1199. let oldName = getNameFromMeta(decodedMeta);
  1200. $('#rename_old_name').val(meta);
  1201. $('#rename_new_name').val(oldName);
  1202. $('#rename_title').text(`Rename "${oldName}"`);
  1203. $('#modal_rename').modal('show');
  1204. }
  1205. function doRename() {
  1206. let decodedMeta = UnicodeDecodeB64($('#rename_old_name').val());
  1207. let oldName = getNameFromMeta(decodedMeta);
  1208. let newName = $('#rename_new_name').val();
  1209. let errDivEl = $('#errorMsg');
  1210. let errTxtEl = $('#errorTxt');
  1211. if (!newName){
  1212. errTxtEl.text("New name is required");
  1213. errDivEl.removeClass("d-none");
  1214. return;
  1215. }
  1216. if (newName == oldName){
  1217. errTxtEl.text("The new name must be different from the current name");
  1218. errDivEl.removeClass("d-none");
  1219. return;
  1220. }
  1221. let path = '{{.FileActionsURL}}/move';
  1222. path+='?path={{.CurrentDir}}'+encodeURIComponent("/"+oldName)+'&target={{.CurrentDir}}'+encodeURIComponent("/"+newName);
  1223. axios.post(path, null, {
  1224. timeout: 15000,
  1225. headers: {
  1226. 'X-CSRF-TOKEN': '{{.CSRFToken}}'
  1227. },
  1228. validateStatus: function (status) {
  1229. return status == 200;
  1230. }
  1231. }).then(function (response) {
  1232. location.reload();
  1233. }).catch(function (error) {
  1234. let errorMessage = `Unable to rename "${oldName}"`;
  1235. if (error && error.response) {
  1236. if (error.response.data.message) {
  1237. errorMessage = error.response.data.message;
  1238. }
  1239. if (error.response.data.error) {
  1240. errorMessage += ": " + error.response.data.error;
  1241. }
  1242. }
  1243. errTxtEl.text(errorMessage);
  1244. errDivEl.removeClass("d-none");
  1245. });
  1246. }
  1247. function showCreateNewFolder(sender) {
  1248. if (sender == 0) {
  1249. $('#file_manager_new_folder').removeClass("d-none");
  1250. $('#errorMsg').addClass("d-none");
  1251. let el = $('#file_manager_new_folder_input');
  1252. el.val("");
  1253. el.focus();
  1254. return;
  1255. }
  1256. $('#dirsbrowser_new_folder').removeClass("d-none");
  1257. $('#errorModalMsg').addClass("d-none");
  1258. let el = $('#dirsbrowser_new_folder_input');
  1259. el.val("");
  1260. el.focus();
  1261. }
  1262. function hideCreateNewFolder(sender) {
  1263. if (sender == 0){
  1264. $('#file_manager_new_folder').addClass("d-none");
  1265. } else {
  1266. $('#dirsbrowser_new_folder').addClass("d-none");
  1267. }
  1268. }
  1269. function createNewFolder() {
  1270. let errDivEl = $('#errorMsg');
  1271. let errTxtEl = $('#errorTxt');
  1272. let dirName = replaceSlash($("#file_manager_new_folder_input").val());
  1273. let submitButton = document.querySelector('#file_manager_add_folder');
  1274. let cancelButton = document.querySelector('#file_manager_cancel_folder');
  1275. errDivEl.addClass("d-none");
  1276. if (!dirName){
  1277. errTxtEl.text("Folder name is required");
  1278. errDivEl.removeClass("d-none");
  1279. return;
  1280. }
  1281. let path = '{{.DirsURL}}?path={{.CurrentDir}}' + encodeURIComponent("/"+dirName);
  1282. submitButton.setAttribute('data-kt-indicator', 'on');
  1283. submitButton.disabled = true;
  1284. cancelButton.disabled = true;
  1285. axios.post(path, null, {
  1286. timeout: 15000,
  1287. headers: {
  1288. 'X-CSRF-TOKEN': '{{.CSRFToken}}'
  1289. },
  1290. validateStatus: function (status) {
  1291. return status == 201;
  1292. }
  1293. }).then(function (response) {
  1294. location.reload();
  1295. }).catch(function (error) {
  1296. let errorMessage = "Unable to create the new folder";
  1297. if (error && error.response) {
  1298. if (error.response.data.message) {
  1299. errorMessage = error.response.data.message;
  1300. }
  1301. if (error.response.data.error) {
  1302. errorMessage += ": " + error.response.data.error;
  1303. }
  1304. }
  1305. errTxtEl.text(errorMessage);
  1306. errDivEl.removeClass("d-none");
  1307. submitButton.removeAttribute('data-kt-indicator');
  1308. submitButton.disabled = false;
  1309. cancelButton.disabled = false;
  1310. });
  1311. }
  1312. //{{- end}}
  1313. var player;
  1314. var playerKeepAlive;
  1315. function uploadFiles(files) {
  1316. keepAlive();
  1317. let keepAliveTimer = setInterval(keepAlive, 300000);
  1318. let has_errors = false;
  1319. let index = 0;
  1320. let success = 0;
  1321. $('#errorMsg').addClass("d-none");
  1322. $('#loading_message').text("");
  1323. KTApp.showPageLoading();
  1324. function uploadFile() {
  1325. if (index >= files.length || has_errors) {
  1326. //console.log("upload done, index: "+index+" has errors: "+has_errors+" ok: "+success);
  1327. clearInterval(keepAliveTimer);
  1328. KTApp.hidePageLoading();
  1329. if (!has_errors) {
  1330. location.reload();
  1331. }
  1332. return;
  1333. }
  1334. let f = files[index];
  1335. let uploadPath;
  1336. //{{- if .ShareUploadBaseURL}}
  1337. uploadPath = '{{.ShareUploadBaseURL}}' + fixedEncodeURIComponent("/"+escapeHTML(f.name));
  1338. //{{- else}}
  1339. uploadPath = '{{.FileURL}}?path={{.CurrentDir}}' + encodeURIComponent("/" + f.name);
  1340. //{{- end}}
  1341. let lastModified;
  1342. try {
  1343. lastModified = f.lastModified;
  1344. } catch (e) {
  1345. console.log("unable to get last modified time from file: " + e.message);
  1346. lastModified = "";
  1347. }
  1348. let uploadTxt = f.name;
  1349. if (files.length > 1){
  1350. uploadTxt = `Upload ${index+1}/${files.length}: ${uploadTxt}`;
  1351. }
  1352. $('#loading_message').text(uploadTxt);
  1353. axios.post(uploadPath, f, {
  1354. headers: {
  1355. 'X-SFTPGO-MTIME': lastModified,
  1356. 'X-CSRF-TOKEN': '{{.CSRFToken}}'
  1357. },
  1358. onUploadProgress: function (progressEvent) {
  1359. if (!progressEvent.total){
  1360. return;
  1361. }
  1362. const percentage = Math.round((100 * progressEvent.loaded) / progressEvent.total);
  1363. if (percentage > 0 && percentage < 100){
  1364. $('#loading_message').text(`${uploadTxt} ${percentage}%`);
  1365. }
  1366. },
  1367. validateStatus: function (status) {
  1368. return status == 201;
  1369. }
  1370. }).then(function (response) {
  1371. index++;
  1372. success++;
  1373. uploadFile();
  1374. }).catch(function (error) {
  1375. let errorMessage = "Error uploading files";
  1376. if (error && error.response) {
  1377. if (error.response.data.message) {
  1378. errorMessage = error.response.data.message;
  1379. }
  1380. if (error.response.data.error) {
  1381. errorMessage += ": " + error.response.data.error;
  1382. }
  1383. }
  1384. index++;
  1385. has_errors = true;
  1386. $('#errorTxt').text(errorMessage);
  1387. $('#errorMsg').removeClass("d-none");
  1388. uploadFile();
  1389. });
  1390. }
  1391. uploadFile();
  1392. }
  1393. function openVideoPlayer(name, url, videoType){
  1394. if (!player){
  1395. player = videojs('video_player', {
  1396. controls: true,
  1397. autoplay: false,
  1398. preload: 'auto'
  1399. });
  1400. }
  1401. $("#video_title").text(UnicodeDecodeB64(name));
  1402. $('#modal_video_player').modal('show');
  1403. player.src({
  1404. type: videoType,
  1405. src: url
  1406. });
  1407. keepAlive();
  1408. playerKeepAlive = setInterval(keepAlive, 300000);
  1409. }
  1410. // On document ready
  1411. KTUtil.onDOMContentLoaded(function () {
  1412. KTDatatablesServerSide.init();
  1413. var dropzone = new Dropzone("#upload_files", {
  1414. url: "{{.FilesURL}}?path={{.CurrentDir}}",
  1415. paramName: "filenames",
  1416. maxFiles: 250,
  1417. maxFilesize: null,
  1418. autoQueue: false,
  1419. addRemoveLinks: true,
  1420. autoProcessQueue: false,
  1421. filesizeBase: 1000,
  1422. init: function() {
  1423. var dropzone = this;
  1424. $("#upload_files_button").click(function(){
  1425. uploadFiles(dropzone.getAcceptedFiles());
  1426. });
  1427. }
  1428. });
  1429. dropzone.on("addedfile", file => {
  1430. file.previewElement.querySelector(".dz-progress").style.display = 'none';
  1431. });
  1432. $('#modal_video_player').on('hide.bs.modal', function () {
  1433. player.pause();
  1434. player.reset();
  1435. if (playerKeepAlive != null) {
  1436. clearInterval(playerKeepAlive);
  1437. playerKeepAlive = null;
  1438. }
  1439. });
  1440. //{{- if not .ShareUploadBaseURL}}
  1441. $('#modal_rename').on('shown.bs.modal', function () {
  1442. let newNameEl = $('#rename_new_name');
  1443. newNameEl.focus();
  1444. newNameEl.select();
  1445. });
  1446. //{{- end}}
  1447. });
  1448. </script>
  1449. {{- end}}
  1450. {{- define "additionalnavitems"}}
  1451. {{- if .QuotaUsage.HasQuotaInfo}}
  1452. <div class="d-flex align-items-center ms-2 ms-lg-3">
  1453. <div class="btn btn-icon btn-active-light-primary position-relative w-35px h-35px w-md-40px h-md-40px" data-kt-menu-trigger="{default:'click', lg: 'hover'}" data-kt-menu-attach="parent" data-kt-menu-placement="bottom-end">
  1454. <i class="ki-duotone {{if .QuotaUsage.IsQuotaLow}}ki-information-5 text-warning{{else}}ki-information-2{{end}} fs-2">
  1455. <span class="path1"></span>
  1456. <span class="path2"></span>
  1457. <span class="path3"></span>
  1458. </i>
  1459. </div>
  1460. <div class="menu menu-sub menu-sub-dropdown menu-column w-350px" data-kt-menu="true">
  1461. <div class="card">
  1462. <div class="card-header">
  1463. <h3 class="card-title"><span class="text-gray-700 fw-semibold fs-6">Quota usage</span></h3>
  1464. </div>
  1465. <div class="card-body p-0">
  1466. {{- if .QuotaUsage.HasDiskQuota}}
  1467. {{- $size := .QuotaUsage.GetQuotaSize}}
  1468. {{- $files := .QuotaUsage.GetQuotaFiles}}
  1469. <div class="d-flex align-items-center bg-hover-lighten py-3 px-9">
  1470. <div class="symbol symbol-40px symbol-circle me-5">
  1471. <span class="symbol-label {{ if .QuotaUsage.IsDiskQuotaLow }}bg-light-warning{{end}}">
  1472. <i class="ki-duotone ki-external-drive {{ if .QuotaUsage.IsDiskQuotaLow }}text-warning{{end}} fs-1">
  1473. <span class="path1"></span>
  1474. <span class="path2"></span>
  1475. <span class="path3"></span>
  1476. <span class="path4"></span>
  1477. <span class="path5"></span>
  1478. </i>
  1479. </span>
  1480. </div>
  1481. <div class="mb-1 pe-3 flex-grow-1">
  1482. <span class="fs-6 text-dark fw-semibold">Disk quota</span>
  1483. {{- if $size}}
  1484. {{- $percentage := .QuotaUsage.GetQuotaSizePercentage}}
  1485. <div class="{{if .QuotaUsage.IsQuotaSizeLow}}text-warning{{else}}text-gray-700{{end}} fw-semibold fs-6">Size: {{$size}}{{if gt $percentage 0}} ({{$percentage}}%){{end}}</div>
  1486. {{- end}}
  1487. {{- if $files}}
  1488. {{- $percentage := .QuotaUsage.GetQuotaFilesPercentage}}
  1489. <div class="{{if .QuotaUsage.IsQuotaFilesLow}}text-warning{{else}}text-gray-700{{end}} fw-semibold fs-6">Files: {{$files}}{{if gt $percentage 0}} ({{$percentage}}%){{end}}</div>
  1490. {{- end}}
  1491. </div>
  1492. </div>
  1493. {{- end}}
  1494. {{- if .QuotaUsage.HasTranferQuota}}
  1495. {{- $total := .QuotaUsage.GetTotalTransferQuota}}
  1496. {{- $upload := .QuotaUsage.GetUploadTransferQuota}}
  1497. {{- $download := .QuotaUsage.GetDownloadTransferQuota}}
  1498. <div class="d-flex align-items-center bg-hover-lighten py-3 px-9">
  1499. <div class="symbol symbol-40px symbol-circle me-5">
  1500. <span class="symbol-label {{ if .QuotaUsage.IsTransferQuotaLow }}bg-light-warning{{end}}">
  1501. <i class="ki-duotone ki-arrow-right-left {{ if .QuotaUsage.IsTransferQuotaLow }}text-warning{{end}} fs-1">
  1502. <span class="path1"></span>
  1503. <span class="path2"></span>
  1504. </i>
  1505. </span>
  1506. </div>
  1507. <div class="mb-1 pe-3 flex-grow-1">
  1508. <span class="fs-6 text-dark fw-semibold">Transfer quota</span>
  1509. {{- if $total}}
  1510. {{$percentage := .QuotaUsage.GetTotalTransferQuotaPercentage}}
  1511. <div class="{{if .QuotaUsage.IsTotalTransferQuotaLow}}text-warning{{else}}text-gray-700{{end}} fw-semibold fs-6">Total: {{$total}}{{if gt $percentage 0}} ({{$percentage}}%){{end}}</div>
  1512. {{- end}}
  1513. {{- if $download}}
  1514. {{$percentage := .QuotaUsage.GetDownloadTransferQuotaPercentage}}
  1515. <div class="{{if .QuotaUsage.IsDownloadTransferQuotaLow}}text-warning{{else}}text-gray-700{{end}} fw-semibold fs-6">Download: {{$download}}{{if gt $percentage 0}} ({{$percentage}}%){{end}}</div>
  1516. {{- end}}
  1517. {{- if $upload}}
  1518. {{$percentage := .QuotaUsage.GetUploadTransferQuotaPercentage}}
  1519. <div class="{{if .QuotaUsage.IsUploadTransferQuotaLow}}text-warning{{else}}text-gray-700{{end}} fw-semibold fs-6">Upload: {{$upload}}{{if gt $percentage 0}} ({{$percentage}}%){{end}}</div>
  1520. {{- end}}
  1521. </div>
  1522. </div>
  1523. {{- end}}
  1524. </div>
  1525. </div>
  1526. </div>
  1527. </div>
  1528. {{- end}}
  1529. {{- end}}
  1530. {{- define "modals"}}
  1531. <div class="modal fade" tabindex="-1" id="modal_upload">
  1532. <div class="modal-dialog modal-dialog-centered mw-600px">
  1533. <div class="modal-content">
  1534. <div class="modal-header border-0">
  1535. <h3 class="modal-title">Upload files</h3>
  1536. <div class="btn btn-icon btn-sm btn-active-color-primary" data-bs-dismiss="modal" aria-label="Close">
  1537. <i class="ki-duotone ki-cross fs-1"><span class="path1"></span><span class="path2"></span></i>
  1538. </div>
  1539. </div>
  1540. <div class="modal-body">
  1541. <form id="upload_files_form" action="{{.FilesURL}}?path={{.CurrentDir}}" method="POST" enctype="multipart/form-data">
  1542. <div class="fv-row">
  1543. <div class="dropzone" id="upload_files">
  1544. <div class="dz-message needsclick align-items-center">
  1545. <i class="ki-duotone ki-file-up fs-3x text-primary"><span class="path1"></span><span class="path2"></span></i>
  1546. <div class="ms-4">
  1547. <h3 class="fs-5 fw-bold text-gray-900 mb-1">Drop files here or click to upload.</h3>
  1548. <!-- <span class="fs-7 fw-semibold text-gray-400">Upload up to 30 files</span> -->
  1549. </div>
  1550. </div>
  1551. </div>
  1552. </div>
  1553. </form>
  1554. </div>
  1555. <div class="modal-footer border-0">
  1556. <button type="button" class="btn btn-light me-5" data-bs-dismiss="modal">Cancel</button>
  1557. <button type="button" id="upload_files_button" class="btn btn-primary" data-bs-dismiss="modal">Submit</button>
  1558. </div>
  1559. </div>
  1560. </div>
  1561. </div>
  1562. <div class="modal fade" tabindex="-1" id="modal_video_player" data-bs-backdrop="static" data-bs-keyboard="false">
  1563. <div class="modal-dialog modal-dialog-centered modal-lg">
  1564. <div class="modal-content">
  1565. <div class="modal-header border-0">
  1566. <h5 class="modal-title">
  1567. <span id="video_title"></span>
  1568. </h5>
  1569. <div class="btn btn-icon btn-sm btn-active-color-primary" data-bs-dismiss="modal" aria-label="Close">
  1570. <i class="ki-duotone ki-cross fs-1"><span class="path1"></span><span class="path2"></span></i>
  1571. </div>
  1572. </div>
  1573. <div class="modal-body">
  1574. <video id="video_player" class="video-js vjs-big-play-centered vjs-fluid">
  1575. <p class="vjs-no-js">To view this video please enable JavaScript, and consider upgrading to a web browser that supports HTML5 video</p>
  1576. </video>
  1577. </div>
  1578. </div>
  1579. </div>
  1580. </div>
  1581. {{- if not .ShareUploadBaseURL}}
  1582. <div class="modal fade" tabindex="-1" id="modal_rename">
  1583. <div class="modal-dialog modal-dialog-centered">
  1584. <div class="modal-content">
  1585. <div class="modal-header border-0">
  1586. <h5 class="modal-title">
  1587. <span id="rename_title"></span>
  1588. </h5>
  1589. <div class="btn btn-icon btn-sm btn-active-color-primary" data-bs-dismiss="modal" aria-label="Close">
  1590. <i class="ki-duotone ki-cross fs-1"><span class="path1"></span><span class="path2"></span></i>
  1591. </div>
  1592. </div>
  1593. <div class="modal-body">
  1594. <input id="rename_old_name" type="text" class="d-none"/>
  1595. <div class="mb-10">
  1596. <label for="rename_new_name" class="form-label">New name</label>
  1597. <input id="rename_new_name" type="text" class="form-control"/>
  1598. </div>
  1599. </div>
  1600. <div class="modal-footer border-0">
  1601. <button type="button" class="btn btn-secondary me-5" data-bs-dismiss="modal">Cancel</button>
  1602. <button type="button" class="btn btn-primary" data-bs-dismiss="modal" onclick="doRename();">Submit</button>
  1603. </div>
  1604. </div>
  1605. </div>
  1606. </div>
  1607. <div class="modal fade" tabindex="-1" id="modal_move_or_copy">
  1608. <div class="modal-dialog modal-dialog-centered modal-lg">
  1609. <div class="modal-content">
  1610. <div class="modal-header border-0">
  1611. <h3 class="modal-title">
  1612. Choose target folder
  1613. </h3>
  1614. <div class="btn btn-icon btn-sm btn-active-color-primary" data-bs-dismiss="modal" aria-label="Close">
  1615. <i class="ki-duotone ki-cross fs-1"><span class="path1"></span><span class="path2"></span></i>
  1616. </div>
  1617. </div>
  1618. <div class="modal-body">
  1619. <div id="errorModalMsg" class="d-none alert alert-dismissible bg-light-warning d-flex align-items-center p-5 mb-10">
  1620. <i class="ki-duotone ki-information-5 fs-3x text-warning me-5"><span class="path1"></span><span class="path2"></span><span class="path3"></span></i>
  1621. <div class="text-gray-700 fw-bold fs-5 d-flex flex-column pe-0 pe-sm-10">
  1622. <span id="errorModalTxt"></span>
  1623. </div>
  1624. <button type="button" class="position-absolute position-sm-relative m-2 m-sm-0 top-0 end-0 btn btn-icon btn-sm btn-active-light-primary ms-sm-auto" onclick="dismissErrorModalMsg();">
  1625. <i class="ki-duotone ki-cross fs-2x text-primary"><span class="path1"></span><span class="path2"></span></i>
  1626. </button>
  1627. </div>
  1628. <script type="text/javascript">
  1629. function dismissErrorModalMsg(){
  1630. $('#errorModalMsg').addClass("d-none");
  1631. }
  1632. </script>
  1633. <div class="row">
  1634. <div class="col-md-9 align-items-center d-flex flex-stack">
  1635. <div class="badge badge-lg badge-light-primary">
  1636. <div id="dirs_browser_nav" class="d-flex align-items-center flex-wrap">
  1637. </div>
  1638. </div>
  1639. </div>
  1640. <div class="col-md-3 align-items-center d-flex justify-content-end">
  1641. <button type="button" class="btn btn-flex btn-primary" onclick="showCreateNewFolder(1);">
  1642. <i class="ki-duotone ki-add-folder fs-2">
  1643. <span class="path1"></span>
  1644. <span class="path2"></span>
  1645. </i>
  1646. Add
  1647. </button>
  1648. </div>
  1649. </div>
  1650. <div id="dirsbrowser_new_folder" class="d-flex align-items-center py-7 d-none">
  1651. <span>
  1652. <i class="ki-duotone ki-folder fs-2x text-primary me-4">
  1653. <span class="path1"></span>
  1654. <span class="path2"></span>
  1655. </i>
  1656. </span>
  1657. <input id="dirsbrowser_new_folder_input" type="text" name="new_folder_name" placeholder="Enter the new folder name" class="form-control mw-250px me-3" />
  1658. <button class="btn btn-icon btn-light-primary me-3" id="dirsbrowser_add_folder">
  1659. <span class="indicator-label">
  1660. <i class="ki-duotone ki-check fs-1"></i>
  1661. </span>
  1662. <span class="indicator-progress">
  1663. <span class="spinner-border spinner-border-sm align-middle"></span>
  1664. </span>
  1665. </button>
  1666. <button class="btn btn-icon btn-light-danger" id="dirsbrowser_cancel_folder" onclick="hideCreateNewFolder(1);">
  1667. <i class="ki-duotone ki-cross fs-1">
  1668. <span class="path1"></span>
  1669. <span class="path2"></span>
  1670. </i>
  1671. </button>
  1672. </div>
  1673. <table id="dirsbrowser_list" class="table align-middle table-row-dashed fs-6 gy-5">
  1674. <thead>
  1675. <tr class="text-start text-muted fw-bold fs-7 text-uppercase gs-0">
  1676. <th></th>
  1677. <th class="min-w-250px">Name</th>
  1678. </tr>
  1679. </thead>
  1680. <tbody id="dirsbrowser_list_body" class="text-gray-600 fw-semibold">
  1681. </tbody>
  1682. </table>
  1683. <div class="form-floating d-none">
  1684. <input type="text" class="form-control form-control-solid" id="move_copy_source"/>
  1685. <label for="move_copy_source">Source name</label>
  1686. </div>
  1687. <div class="form-floating mb-5 mt-7">
  1688. <input type="text" class="form-control form-control-solid" id="move_copy_folder"/>
  1689. <label for="move_copy_folder">Target folder</label>
  1690. </div>
  1691. <div class="form-floating" id="move_copy_name_container">
  1692. <input type="text" class="form-control form-control-solid" id="move_copy_name"/>
  1693. <label for="move_copy_name">Destination name</label>
  1694. </div>
  1695. </div>
  1696. <div class="modal-footer border-0">
  1697. {{- if .CanAddFiles }}
  1698. <button type="button" class="btn btn-light-primary me-5" data-bs-dismiss="modal" onclick="doCopy();">Copy</button>
  1699. {{- end}}
  1700. {{- if .CanRename }}
  1701. <button type="button" class="btn btn-primary" data-bs-dismiss="modal" onclick="doMove();">Move</button>
  1702. {{- end}}
  1703. </div>
  1704. </div>
  1705. </div>
  1706. </div>
  1707. {{- end}}
  1708. {{- end}}