users.html 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711
  1. <!--
  2. Copyright (C) 2024 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 ([email protected]).
  11. -->
  12. {{template "base" .}}
  13. {{- define "extra_css"}}
  14. <link href="{{.StaticURL}}/assets/plugins/custom/datatables/datatables.bundle.css" rel="stylesheet" type="text/css"/>
  15. {{- end}}
  16. {{- define "page_body"}}
  17. {{- template "errmsg" ""}}
  18. <div class="card shadow-sm">
  19. <div class="card-header bg-light">
  20. <h3 data-i18n="user.view_manage" class="card-title section-title">View and manage users</h3>
  21. </div>
  22. <div id="card_body" class="card-body">
  23. <div id="loader" class="align-items-center text-center my-10">
  24. <span class="spinner-border w-15px h-15px text-muted align-middle me-2"></span>
  25. <span data-i18n="general.loading" class="text-gray-700">Loading...</span>
  26. </div>
  27. <div id="card_content" class="d-none">
  28. <div class="d-flex flex-stack flex-wrap mb-5">
  29. <div class="d-flex align-items-center position-relative my-2">
  30. <i class="ki-solid ki-magnifier fs-1 position-absolute ms-6"></i>
  31. <input name="search" data-i18n="[placeholder]general.search" type="text" data-table-filter="search"
  32. class="form-control rounded-1 w-250px ps-15 me-5" placeholder="Search" />
  33. </div>
  34. <div class="d-flex justify-content-end my-2" data-table-toolbar="base">
  35. <button type="button" class="btn btn-light-primary rotate" data-kt-menu-trigger="click" data-kt-menu-placement="bottom" data-kt-menu-permanent="true">
  36. <span data-i18n="general.colvis">Column visibility</span>
  37. <i class="ki-duotone ki-down fs-3 rotate-180 ms-3 me-0"></i>
  38. </button>
  39. <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">
  40. <div class="menu-item px-3 py-2 form-check form-check-sm form-check-custom form-check-solid">
  41. <input type="checkbox" class="form-check-input" value="" id="checkColStatus" />
  42. <label class="form-check-label" for="checkColStatus">
  43. <span data-i18n="general.status" class="text-gray-800 fs-6">Status</span>
  44. </label>
  45. </div>
  46. <div class="menu-item px-3 py-2 form-check form-check-sm form-check-custom form-check-solid">
  47. <input type="checkbox" class="form-check-input" value="" id="checkColLastLogin" />
  48. <label class="form-check-label" for="checkColLastLogin">
  49. <span data-i18n="general.last_login" class="text-gray-800 fs-6">Last login</span>
  50. </label>
  51. </div>
  52. <div class="menu-item px-3 py-2 form-check form-check-sm form-check-custom form-check-solid">
  53. <input type="checkbox" class="form-check-input" value="" id="checkColEmail" />
  54. <label class="form-check-label" for="checkColEmail">
  55. <span data-i18n="general.email" class="text-gray-800 fs-6">Email</span>
  56. </label>
  57. </div>
  58. <div class="menu-item px-3 py-2 form-check form-check-sm form-check-custom form-check-solid">
  59. <input type="checkbox" class="form-check-input" value="" id="checkColStorage" />
  60. <label class="form-check-label" for="checkColStorage">
  61. <span data-i18n="storage.label" class="text-gray-800 fs-6">Storage</span>
  62. </label>
  63. </div>
  64. <div class="menu-item px-3 py-2 form-check form-check-sm form-check-custom form-check-solid">
  65. <input type="checkbox" class="form-check-input" value="" id="checkColRole" />
  66. <label class="form-check-label" for="checkColRole">
  67. <span data-i18n="general.role" class="text-gray-800 fs-6">Role</span>
  68. </label>
  69. </div>
  70. <div class="menu-item px-3 py-2 form-check form-check-sm form-check-custom form-check-solid">
  71. <input type="checkbox" class="form-check-input" value="" id="checkCol2FA" />
  72. <label class="form-check-label" for="checkCol2FA">
  73. <span data-i18n="title.two_factor_auth_short" class="text-gray-800 fs-6">2FA</span>
  74. </label>
  75. </div>
  76. <div class="menu-item px-3 py-2 form-check form-check-sm form-check-custom form-check-solid">
  77. <input type="checkbox" class="form-check-input" value="" id="checkColDiskQuota" />
  78. <label class="form-check-label" for="checkColDiskQuota">
  79. <span data-i18n="fs.quota_usage.disk" class="text-gray-800 fs-6">Disk quota</span>
  80. </label>
  81. </div>
  82. <div class="menu-item px-3 py-2 form-check form-check-sm form-check-custom form-check-solid">
  83. <input type="checkbox" class="form-check-input" value="" id="checkColTransferQuota" />
  84. <label class="form-check-label" for="checkColTransferQuota">
  85. <span data-i18n="fs.quota_usage.transfer" class="text-gray-800 fs-6">Transfer quota</span>
  86. </label>
  87. </div>
  88. <div class="menu-item px-3 py-2 form-check form-check-sm form-check-custom form-check-solid">
  89. <input type="checkbox" class="form-check-input" value="" id="checkColGroups" />
  90. <label class="form-check-label" for="checkColGroups">
  91. <span data-i18n="title.groups" class="text-gray-800 fs-6">Groups</span>
  92. </label>
  93. </div>
  94. </div>
  95. {{- if .LoggedUser.HasPermission "add_users"}}
  96. <a href="{{.UserURL}}" class="btn btn-primary ms-5">
  97. <i class="ki-duotone ki-plus fs-2"></i>
  98. <span data-i18n="general.add">Add</span>
  99. </a>
  100. {{- end}}
  101. </div>
  102. </div>
  103. <table id="dataTable" class="table align-middle table-row-dashed fs-6 gy-5">
  104. <thead>
  105. <tr class="text-start text-muted fw-bold fs-6 gs-0">
  106. <th data-i18n="login.username">Username</th>
  107. <th data-i18n="general.status">Status</th>
  108. <th data-i18n="general.last_login">Last login</th>
  109. <th data-i18n="general.email">Email</th>
  110. <th data-i18n="storage.label">Storage</th>
  111. <th data-i18n="general.role">Role</th>
  112. <th data-i18n="title.two_factor_auth_short">2FA</th>
  113. <th data-i18n="fs.quota_usage.disk">Disk quota</th>
  114. <th data-i18n="fs.quota_usage.transfer">Transfer quota</th>
  115. <th data-i18n="title.groups">Groups</th>
  116. <th class="min-w-100px"></th>
  117. </tr>
  118. </thead>
  119. <tbody id="table_body" class="text-gray-800 fw-semibold"></tbody>
  120. </table>
  121. </div>
  122. </div>
  123. </div>
  124. {{- end}}
  125. {{- define "extra_js"}}
  126. <script {{- if .CSPNonce}} nonce="{{.CSPNonce}}"{{- end}} src="{{.StaticURL}}/assets/plugins/custom/datatables/datatables.bundle.js"></script>
  127. <script type="text/javascript" {{- if .CSPNonce}} nonce="{{.CSPNonce}}"{{- end}}>
  128. function deleteAction(username) {
  129. ModalAlert.fire({
  130. text: $.t('general.delete_confirm_generic'),
  131. icon: "warning",
  132. confirmButtonText: $.t('general.delete_confirm_btn'),
  133. cancelButtonText: $.t('general.cancel'),
  134. customClass: {
  135. confirmButton: "btn btn-danger",
  136. cancelButton: 'btn btn-secondary'
  137. }
  138. }).then((result) => {
  139. if (result.isConfirmed){
  140. $('#loading_message').text("");
  141. KTApp.showPageLoading();
  142. let path = '{{.UserURL}}' + "/" + encodeURIComponent(username);
  143. axios.delete(path, {
  144. timeout: 15000,
  145. headers: {
  146. 'X-CSRF-TOKEN': '{{.CSRFToken}}'
  147. },
  148. validateStatus: function (status) {
  149. return status == 200;
  150. }
  151. }).then(function(response){
  152. location.reload();
  153. }).catch(function(error){
  154. KTApp.hidePageLoading();
  155. let errorMessage;
  156. if (error && error.response) {
  157. switch (error.response.status) {
  158. case 403:
  159. errorMessage = "general.delete_error_403";
  160. break;
  161. case 404:
  162. errorMessage = "general.delete_error_404";
  163. break;
  164. }
  165. }
  166. if (!errorMessage){
  167. errorMessage = "general.delete_error_generic";
  168. }
  169. ModalAlert.fire({
  170. text: $.t(errorMessage),
  171. icon: "warning",
  172. confirmButtonText: $.t('general.ok'),
  173. customClass: {
  174. confirmButton: "btn btn-primary"
  175. }
  176. });
  177. });
  178. }
  179. });
  180. }
  181. function disableSeconFactorAction(username) {
  182. ModalAlert.fire({
  183. text: $.t('2fa.disable_confirm'),
  184. icon: "warning",
  185. confirmButtonText: $.t('general.disable_confirm_btn'),
  186. cancelButtonText: $.t('general.cancel'),
  187. customClass: {
  188. confirmButton: "btn btn-danger",
  189. cancelButton: 'btn btn-secondary'
  190. }
  191. }).then((result) => {
  192. if (result.isConfirmed){
  193. $('#loading_message').text("");
  194. KTApp.showPageLoading();
  195. let path = '{{.UserURL}}' + "/" + encodeURIComponent(username)+"/2fa/disable";
  196. axios.put(path, null, {
  197. timeout: 15000,
  198. headers: {
  199. 'X-CSRF-TOKEN': '{{.CSRFToken}}'
  200. },
  201. validateStatus: function (status) {
  202. return status == 200;
  203. }
  204. }).then(function(response){
  205. location.reload();
  206. }).catch(function(error){
  207. KTApp.hidePageLoading();
  208. ModalAlert.fire({
  209. text: $.t('2fa.save_err'),
  210. icon: "warning",
  211. confirmButtonText: $.t('general.ok'),
  212. customClass: {
  213. confirmButton: "btn btn-primary"
  214. }
  215. });
  216. });
  217. }
  218. });
  219. }
  220. function quotaScanAction(username) {
  221. $('#loading_message').text("");
  222. KTApp.showPageLoading();
  223. let path = '{{.QuotaScanURL}}' + "/" + encodeURIComponent(username);
  224. axios.post(path, null, {
  225. timeout: 15000,
  226. headers: {
  227. 'X-CSRF-TOKEN': '{{.CSRFToken}}'
  228. },
  229. validateStatus: function (status) {
  230. return status == 202;
  231. }
  232. }).then(function (response) {
  233. KTApp.hidePageLoading();
  234. showToast(1, 'general.quota_scan_started');
  235. }).catch(function (error) {
  236. KTApp.hidePageLoading();
  237. let errorMessage;
  238. if (error && error.response) {
  239. switch (error.response.status) {
  240. case 409:
  241. errorMessage = "general.quota_scan_conflit";
  242. break;
  243. }
  244. }
  245. if (!errorMessage) {
  246. errorMessage = "general.quota_scan_error";
  247. }
  248. ModalAlert.fire({
  249. text: $.t(errorMessage),
  250. icon: "warning",
  251. confirmButtonText: $.t('general.ok'),
  252. customClass: {
  253. confirmButton: "btn btn-primary"
  254. }
  255. });
  256. });
  257. }
  258. var datatable = function(){
  259. var dt;
  260. var initDatatable = function () {
  261. $('#errorMsg').addClass("d-none");
  262. dt = $('#dataTable').DataTable({
  263. ajax: {
  264. url: "{{.UsersURL}}/json",
  265. dataSrc: "",
  266. error: function ($xhr, textStatus, errorThrown) {
  267. $(".dataTables_processing").hide();
  268. $('#loader').addClass("d-none");
  269. let txt = "";
  270. if ($xhr) {
  271. let json = $xhr.responseJSON;
  272. if (json) {
  273. if (json.message){
  274. txt = json.message;
  275. }
  276. }
  277. }
  278. if (!txt){
  279. txt = "general.error500";
  280. }
  281. setI18NData($('#errorTxt'), txt);
  282. $('#errorMsg').removeClass("d-none");
  283. }
  284. },
  285. columns: [
  286. {
  287. data: "username",
  288. render: function(data, type, row) {
  289. if (type === 'display') {
  290. return escapeHTML(data);
  291. }
  292. return data;
  293. }
  294. },
  295. {
  296. data: "status",
  297. render: function(data, type, row) {
  298. let result = data;
  299. if (row.expiration_date){
  300. if (row.expiration_date < Date.now()){
  301. result = -1;
  302. }
  303. }
  304. if (type === 'display') {
  305. switch (result){
  306. case 1:
  307. return $.t('general.active');
  308. case -1:
  309. return $.t('general.expired');
  310. default:
  311. return $.t('general.inactive');
  312. }
  313. }
  314. return result;
  315. }
  316. },
  317. {
  318. data: "last_login",
  319. defaultContent: "",
  320. render: function(data, type, row) {
  321. if (type === 'display') {
  322. if (data > 0){
  323. return $.t('general.datetime', {
  324. val: parseInt(data, 10),
  325. formatParams: {
  326. val: { year: 'numeric', month: 'numeric', day: 'numeric', hour: 'numeric', minute: 'numeric' },
  327. }
  328. });
  329. }
  330. return ""
  331. }
  332. return data;
  333. }
  334. },
  335. {
  336. data: "email",
  337. visible: false,
  338. defaultContent: "",
  339. render: function(data, type, row) {
  340. if (type === 'display') {
  341. if (data){
  342. return escapeHTML(data);
  343. }
  344. return "";
  345. }
  346. return data;
  347. }
  348. },
  349. {
  350. data: "filesystem.provider",
  351. visible: false,
  352. defaultContent: "",
  353. render: function(data, type, row) {
  354. if (type === 'display') {
  355. switch (data){
  356. case 1:
  357. return $.t('storage.s3');
  358. case 2:
  359. return $.t('storage.gcs');
  360. case 3:
  361. return $.t('storage.azblob');
  362. case 4:
  363. return $.t('storage.encrypted');
  364. case 5:
  365. return $.t('storage.sftp');
  366. case 6:
  367. return $.t('storage.http');
  368. default:
  369. return $.t('storage.local');
  370. }
  371. }
  372. return data;
  373. }
  374. },
  375. {
  376. data: "role",
  377. visible: false,
  378. defaultContent: "",
  379. render: function(data, type, row) {
  380. if (type === 'display') {
  381. if (data){
  382. return escapeHTML(data);
  383. }
  384. return ""
  385. }
  386. return data;
  387. }
  388. },
  389. {
  390. data: "filters.totp_config",
  391. visible: false,
  392. defaultContent: "",
  393. render: function(data, type, row) {
  394. let protocols = "";
  395. if (data && data.enabled){
  396. protocols = data.protocols.join(', ');
  397. }
  398. if (type === 'display') {
  399. if (protocols){
  400. return escapeHTML(protocols);
  401. }
  402. }
  403. return protocols;
  404. }
  405. },
  406. {
  407. data: "quota_files",
  408. visible: false,
  409. searchable: false,
  410. orderable: false,
  411. defaultContent: "",
  412. render: function(data, type, row) {
  413. if (type === 'display') {
  414. let val = "";
  415. if (row.quota_size > 0) {
  416. let used = row.used_quota_size;
  417. if (!used){
  418. used = 0;
  419. }
  420. let usage = fileSizeIEC(used)+"/"+fileSizeIEC(row.quota_size);
  421. val += $.t('fs.quota_usage.size', {val: usage})+". ";
  422. } else if (row.used_quota_size && row.used_quota_size > 0) {
  423. val += $.t('fs.quota_usage.size', {val: fileSizeIEC(row.used_quota_size)})+". ";
  424. }
  425. if (row.quota_files > 0){
  426. let used = row.used_quota_files;
  427. if (!used){
  428. used = 0;
  429. }
  430. let usage = used+"/"+row.quota_files;
  431. val += $.t('fs.quota_usage.files', {val: usage});
  432. } else if (row.used_quota_files && row.used_quota_files > 0) {
  433. val += $.t('fs.quota_usage.files', {val: row.used_quota_files});
  434. }
  435. return val
  436. }
  437. return "";
  438. }
  439. },
  440. {
  441. data: "total_data_transfer",
  442. visible: false,
  443. searchable: false,
  444. orderable: false,
  445. defaultContent: "",
  446. render: function(data, type, row) {
  447. if (type === 'display') {
  448. let val = "";
  449. if (row.total_data_transfer > 0) {
  450. let used;
  451. if (row.used_download_data_transfer){
  452. used+=row.used_download_data_transfer;
  453. }
  454. if (row.used_upload_data_transfer){
  455. used+=row.used_upload_data_transfer;
  456. }
  457. let usage = fileSizeIEC(used)+"/"+fileSizeIEC(row.total_data_transfer*1048576);
  458. val = $.t('fs.quota_usage.total', {val: usage});
  459. } else {
  460. if (row.upload_data_transfer > 0) {
  461. let used = row.used_upload_data_transfer;
  462. if (!used){
  463. used = 0;
  464. }
  465. let usage = fileSizeIEC(used)+"/"+fileSizeIEC(row.upload_data_transfer*1048576);
  466. val = $.t('fs.quota_usage.uploads', {val: usage})+". ";
  467. }
  468. if (row.download_data_transfer > 0) {
  469. let used = row.used_download_data_transfer;
  470. if (!used){
  471. used = 0;
  472. }
  473. let usage = fileSizeIEC(used)+"/"+fileSizeIEC(row.download_data_transfer*1048576);
  474. val = $.t('fs.quota_usage.downloads', {val: usage});
  475. }
  476. }
  477. return val;
  478. }
  479. return "";
  480. }
  481. },
  482. {
  483. data: "groups",
  484. visible: false,
  485. defaultContent: [],
  486. render: function(data, type, row) {
  487. if (!data){
  488. return "";
  489. }
  490. let groups = [];
  491. let result = "";
  492. for (i = 0; i < data.length; i++){
  493. groups.push(data[i].name);
  494. }
  495. result = groups.join(", ");
  496. if (type === 'display') {
  497. return escapeHTML(result);
  498. }
  499. return result;
  500. }
  501. },
  502. {
  503. data: "id",
  504. searchable: false,
  505. orderable: false,
  506. className: 'text-end',
  507. render: function (data, type, row) {
  508. if (type === 'display') {
  509. let numActions = 0;
  510. let actions = `<button class="btn btn-light btn-active-light-primary btn-flex btn-center btn-sm rotate" data-kt-menu-trigger="click" data-kt-menu-placement="bottom-end">
  511. <span data-i18n="general.actions" class="fs-6">Actions</span>
  512. <i class="ki-duotone ki-down fs-5 ms-1 rotate-180"></i>
  513. </button>
  514. <div class="menu menu-sub menu-sub-dropdown menu-column menu-rounded menu-gray-700 menu-state-bg-light-primary fw-semibold fs-6 w-200px py-4" data-kt-menu="true">`;
  515. //{{- if .LoggedUser.HasPermission "edit_users"}}
  516. numActions++;
  517. actions+=`<div class="menu-item px-3">
  518. <a data-i18n="general.edit" href="#" class="menu-link px-3" data-table-action="edit_row">Edit</a>
  519. </div>`;
  520. //{{- end}}
  521. //{{- if .LoggedUser.HasPermission "manage_system"}}
  522. numActions++;
  523. actions+=`<div class="menu-item px-3">
  524. <a data-i18n="general.template" href="#" class="menu-link px-3" data-table-action="template_row">Template</a>
  525. </div>`;
  526. //{{- end}}
  527. //{{- if .LoggedUser.HasPermission "quota_scans"}}
  528. numActions++;
  529. actions+=`<div class="menu-item px-3">
  530. <a data-i18n="general.quota_scan" href="#" class="menu-link px-3" data-table-action="quota_scan_row">Quota scan</a>
  531. </div>`;
  532. //{{- end}}
  533. //{{- if .LoggedUser.HasPermission "disable_mfa"}}
  534. if (row.filters.totp_config && row.filters.totp_config.enabled){
  535. numActions++;
  536. actions+=`<div class="menu-item px-3">
  537. <a data-i18n="2fa.disable_msg" href="#" class="menu-link text-danger px-3" data-table-action="disable_2fa_row">Disable 2FA</a>
  538. </div>`;
  539. }
  540. //{{- end}}
  541. //{{- if .LoggedUser.HasPermission "del_users"}}
  542. numActions++;
  543. actions+=`<div class="menu-item px-3">
  544. <a data-i18n="general.delete" href="#" class="menu-link text-danger px-3" data-table-action="delete_row">Delete</a>
  545. </div>`;
  546. //{{- end}}
  547. if (numActions > 0){
  548. actions+=`</div>`;
  549. return actions;
  550. }
  551. }
  552. return "";
  553. }
  554. },
  555. ],
  556. deferRender: true,
  557. stateSave: true,
  558. stateDuration: 0,
  559. colReorder: {
  560. enable: true,
  561. fixedColumnsLeft: 1
  562. },
  563. stateLoadParams: function (settings, data) {
  564. if (data.search.search){
  565. const filterSearch = document.querySelector('[data-table-filter="search"]');
  566. filterSearch.value = data.search.search;
  567. }
  568. },
  569. language: {
  570. info: $.t('datatable.info'),
  571. infoEmpty: $.t('datatable.info_empty'),
  572. infoFiltered: $.t('datatable.info_filtered'),
  573. loadingRecords: "",
  574. processing: $.t('datatable.processing'),
  575. zeroRecords: "",
  576. emptyTable: $.t('datatable.no_records')
  577. },
  578. order: [[0, 'asc']],
  579. initComplete: function(settings, json) {
  580. $('#loader').addClass("d-none");
  581. $('#card_content').removeClass("d-none");
  582. let api = $.fn.dataTable.Api(settings);
  583. api.columns.adjust().draw("page");
  584. drawAction();
  585. }
  586. });
  587. dt.on('draw', drawAction);
  588. dt.on('column-reorder', function(e, settings, details){
  589. drawAction();
  590. });
  591. }
  592. function drawAction() {
  593. KTMenu.createInstances();
  594. handleRowActions();
  595. $('#table_body').localize();
  596. }
  597. function handleColVisibilityCheckbox(el, index) {
  598. el.off("change");
  599. el.prop('checked', dt.column(index).visible());
  600. el.on("change", function(e){
  601. dt.column(index).visible($(this).is(':checked'));
  602. dt.draw('page');
  603. });
  604. }
  605. var handleDatatableActions = function () {
  606. const filterSearch = $(document.querySelector('[data-table-filter="search"]'));
  607. filterSearch.off("keyup");
  608. filterSearch.on('keyup', function (e) {
  609. dt.rows().deselect();
  610. dt.search(e.target.value).draw();
  611. });
  612. handleColVisibilityCheckbox($('#checkColStatus'), 1);
  613. handleColVisibilityCheckbox($('#checkColLastLogin'), 2);
  614. handleColVisibilityCheckbox($('#checkColEmail'), 3);
  615. handleColVisibilityCheckbox($('#checkColStorage'), 4);
  616. handleColVisibilityCheckbox($('#checkColRole'), 5);
  617. handleColVisibilityCheckbox($('#checkCol2FA'), 6);
  618. handleColVisibilityCheckbox($('#checkColDiskQuota'), 7);
  619. handleColVisibilityCheckbox($('#checkColTransferQuota'), 8);
  620. handleColVisibilityCheckbox($('#checkColGroups'), 9);
  621. }
  622. function handleRowActions() {
  623. const editButtons = document.querySelectorAll('[data-table-action="edit_row"]');
  624. editButtons.forEach(d => {
  625. let el = $(d);
  626. el.off("click");
  627. el.on("click", function(e){
  628. e.preventDefault();
  629. let rowData = dt.row(e.target.closest('tr')).data();
  630. window.location.replace('{{.UserURL}}' + "/" + encodeURIComponent(rowData['username']));
  631. });
  632. });
  633. const templateButtons = document.querySelectorAll('[data-table-action="template_row"]');
  634. templateButtons.forEach(d => {
  635. let el = $(d);
  636. el.off("click");
  637. el.on("click", function(e){
  638. e.preventDefault();
  639. let rowData = dt.row(e.target.closest('tr')).data();
  640. window.location.replace('{{.UserTemplateURL}}' + "?from=" + encodeURIComponent(rowData['username']));
  641. });
  642. });
  643. const quotaScanButtons = document.querySelectorAll('[data-table-action="quota_scan_row"]');
  644. quotaScanButtons.forEach(d => {
  645. let el = $(d);
  646. el.off("click");
  647. el.on("click", function(e){
  648. e.preventDefault();
  649. let rowData = dt.row(e.target.closest('tr')).data();
  650. quotaScanAction(rowData['username']);
  651. });
  652. });
  653. const diable2FAButtons = document.querySelectorAll('[data-table-action="disable_2fa_row"]');
  654. diable2FAButtons.forEach(d => {
  655. let el = $(d);
  656. el.off("click");
  657. el.on("click", function(e){
  658. e.preventDefault();
  659. let rowData = dt.row(e.target.closest('tr')).data();
  660. disableSeconFactorAction(rowData['username']);
  661. });
  662. });
  663. const deleteButtons = document.querySelectorAll('[data-table-action="delete_row"]');
  664. deleteButtons.forEach(d => {
  665. let el = $(d);
  666. el.off("click");
  667. el.on("click", function(e){
  668. e.preventDefault();
  669. const parent = e.target.closest('tr');
  670. deleteAction(dt.row(parent).data()['username']);
  671. });
  672. });
  673. }
  674. return {
  675. init: function () {
  676. initDatatable();
  677. handleDatatableActions();
  678. }
  679. }
  680. }();
  681. $(document).on("i18nshow", function(){
  682. datatable.init();
  683. });
  684. </script>
  685. {{- end}}