connections.html 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  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. <div class="card shadow-sm">
  18. <div class="card-header bg-light">
  19. <h3 data-i18n="connections.view_manage" class="card-title section-title">View and manage connections</h3>
  20. </div>
  21. <div id="card_body" class="card-body">
  22. <div id="loader" class="align-items-center text-center my-10">
  23. <span class="spinner-border w-15px h-15px text-muted align-middle me-2"></span>
  24. <span data-i18n="general.loading" class="text-gray-700">Loading...</span>
  25. </div>
  26. <div id="card_content" class="d-none">
  27. <div class="d-flex flex-stack flex-wrap mb-5">
  28. <div class="d-flex align-items-center position-relative my-2">
  29. <i class="ki-solid ki-magnifier fs-1 position-absolute ms-6"></i>
  30. <input name="search" data-i18n="[placeholder]general.search" type="text" data-table-filter="search"
  31. class="form-control rounded-1 w-250px ps-15 me-5" placeholder="Search" />
  32. </div>
  33. <div class="d-flex justify-content-end my-2" data-table-toolbar="base">
  34. <a href="{{.ConnectionsURL}}" class="btn btn-primary">
  35. <i class="ki-solid ki-arrows-circle fs-2"></i>
  36. <span data-i18n="general.refresh">Refresh</span>
  37. </a>
  38. </div>
  39. </div>
  40. <table id="dataTable" class="table align-middle table-row-dashed fs-6 gy-5">
  41. <thead>
  42. <tr class="text-start text-muted fw-bold fs-6 gs-0">
  43. <th>ID</th>
  44. <th>Node</th>
  45. <th data-i18n="login.username">Username</th>
  46. <th data-i18n="connections.started">Started</th>
  47. <th data-i18n="connections.remote_address">Remote address</th>
  48. <th data-i18n="general.protocol">Protocol</th>
  49. <th data-i18n="connections.last_activity">Last activity</th>
  50. <th data-i18n="general.info">Info</th>
  51. <th></th>
  52. </tr>
  53. </thead>
  54. <tbody id="table_body" class="text-gray-800 fw-semibold"></tbody>
  55. </table>
  56. </div>
  57. </div>
  58. </div>
  59. {{- end}}
  60. {{- define "extra_js"}}
  61. <script {{- if .CSPNonce}} nonce="{{.CSPNonce}}"{{- end}} src="{{.StaticURL}}/assets/plugins/custom/datatables/datatables.bundle.js"></script>
  62. <script type="text/javascript" {{- if .CSPNonce}} nonce="{{.CSPNonce}}"{{- end}}>
  63. function disconnectAction(connectionID, node) {
  64. ModalAlert.fire({
  65. text: $.t('connections.disconnect_confirm'),
  66. icon: "warning",
  67. confirmButtonText: $.t('connections.disconnect_confirm_btn'),
  68. cancelButtonText: $.t('general.cancel'),
  69. customClass: {
  70. confirmButton: "btn btn-danger",
  71. cancelButton: 'btn btn-secondary'
  72. }
  73. }).then((result) => {
  74. if (result.isConfirmed){
  75. $('#loading_message').text("");
  76. KTApp.showPageLoading();
  77. let path = '{{.ConnectionsURL}}' + "/" + encodeURIComponent(connectionID);
  78. if (node) {
  79. path+="?node="+ encodeURIComponent(node);
  80. }
  81. axios.delete(path, {
  82. timeout: 15000,
  83. headers: {
  84. 'X-CSRF-TOKEN': '{{.CSRFToken}}'
  85. },
  86. validateStatus: function (status) {
  87. return status == 200;
  88. }
  89. }).then(function(response){
  90. setTimeout(function() {
  91. location.reload();
  92. },250);
  93. }).catch(function(error){
  94. KTApp.hidePageLoading();
  95. ModalAlert.fire({
  96. text: $.t('connections.disconnect_ko'),
  97. icon: "warning",
  98. confirmButtonText: $.t('general.ok'),
  99. customClass: {
  100. confirmButton: "btn btn-primary"
  101. }
  102. });
  103. });
  104. }
  105. });
  106. }
  107. var datatable = function(){
  108. var dt;
  109. var initDatatable = function () {
  110. $('#errorMsg').addClass("d-none");
  111. dt = $('#dataTable').DataTable({
  112. ajax: {
  113. url: "{{.ConnectionsURL}}/json",
  114. dataSrc: "",
  115. error: function ($xhr, textStatus, errorThrown) {
  116. $(".dataTables_processing").hide();
  117. $('#loader').addClass("d-none");
  118. let txt = "";
  119. if ($xhr) {
  120. let json = $xhr.responseJSON;
  121. if (json) {
  122. if (json.message){
  123. txt = json.message;
  124. }
  125. }
  126. }
  127. if (!txt){
  128. txt = "general.error500";
  129. }
  130. setI18NData($('#errorTxt'), txt);
  131. $('#errorMsg').removeClass("d-none");
  132. }
  133. },
  134. columns: [
  135. {
  136. data: "connection_id",
  137. visible: false,
  138. searchable: false,
  139. orderable: false,
  140. render: function(data, type, row) {
  141. if (type === 'display') {
  142. return escapeHTML(data);
  143. }
  144. return data;
  145. }
  146. },
  147. {
  148. data: "node",
  149. visible: false,
  150. searchable: false,
  151. orderable: false,
  152. defaultContent: "",
  153. render: function(data, type, row) {
  154. if (type === 'display') {
  155. return escapeHTML(data);
  156. }
  157. return data;
  158. }
  159. },
  160. {
  161. data: "username",
  162. defaultContent: "",
  163. render: function(data, type, row) {
  164. if (type === 'display') {
  165. return escapeHTML(data);
  166. }
  167. return data;
  168. }
  169. },
  170. {
  171. data: "connection_time",
  172. searchable: false,
  173. defaultContent: 0,
  174. render: function(data, type, row) {
  175. if (type === 'display') {
  176. if (data > 0){
  177. return $.t('general.datetime', {
  178. val: parseInt(data, 10),
  179. formatParams: {
  180. val: { year: '2-digit', month: 'numeric', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric' },
  181. }
  182. });
  183. }
  184. return ""
  185. }
  186. return data;
  187. }
  188. },
  189. {
  190. data: "remote_address",
  191. defaultContent: "",
  192. render: function(data, type, row) {
  193. if (type === 'display') {
  194. return escapeHTML(data);
  195. }
  196. return data;
  197. }
  198. },
  199. {
  200. data: "protocol",
  201. defaultContent: "",
  202. render: function(data, type, row) {
  203. if (type === 'display') {
  204. return escapeHTML(data);
  205. }
  206. return data;
  207. }
  208. },
  209. {
  210. data: "last_activity",
  211. searchable: false,
  212. defaultContent: 0,
  213. render: function(data, type, row) {
  214. if (type === 'display') {
  215. if (data > 0){
  216. return $.t('general.datetime', {
  217. val: parseInt(data, 10),
  218. formatParams: {
  219. val: { year: '2-digit', month: 'numeric', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric' },
  220. }
  221. });
  222. }
  223. return ""
  224. }
  225. return data;
  226. }
  227. },
  228. {
  229. data: "active_transfers",
  230. searchable: false,
  231. orderable: false,
  232. render: function (data, type, row) {
  233. if (type === 'display') {
  234. let result = "";
  235. if (row.active_transfers && row.active_transfers.length > 0){
  236. let transfer = row.active_transfers[0];
  237. let path = escapeHTML(transfer.path);
  238. let elapsed = row.current_time - transfer.start_time;
  239. if (elapsed > 0 && transfer.size > 0){
  240. let speed = (transfer.size*1.0) / (elapsed/1000.0);
  241. if (transfer.operation_type === 'upload'){
  242. result = $.t('connections.upload_info', {path: path, size: fileSizeIEC(transfer.size), speed: humanizeSpeed(speed)});
  243. } else {
  244. result = $.t('connections.download_info', {path: path, size: fileSizeIEC(transfer.size), speed: humanizeSpeed(speed)});
  245. }
  246. } else {
  247. if (transfer.operation_type === 'upload'){
  248. result = $.t('connections.upload', {path: path});
  249. } else {
  250. result = $.t('connections.download', {path: path});
  251. }
  252. }
  253. }
  254. if (row.client_version){
  255. if (result){
  256. result+= ". ";
  257. }
  258. result+= $.t('connections.client', {val: escapeHTML(row.client_version)});
  259. }
  260. return result;
  261. }
  262. return "";
  263. }
  264. },
  265. {
  266. data: "",
  267. searchable: false,
  268. orderable: false,
  269. className: 'text-end',
  270. render: function (data, type, row) {
  271. if (type === 'display') {
  272. //{{- if .LoggedUser.HasPermission "close_conns"}}
  273. return `<div class="d-flex justify-content-end">
  274. <div class="ms-2">
  275. <a href="#" class="btn btn-sm btn-icon btn-light-danger" data-table-action="close_conn">
  276. <i class="ki-solid ki-cross fs-1"></i>
  277. </a>
  278. </div>
  279. </div>`;
  280. //{{- end}}
  281. }
  282. return "";
  283. }
  284. },
  285. ],
  286. deferRender: true,
  287. stateSave: true,
  288. stateDuration: 0,
  289. colReorder: {
  290. enable: true
  291. },
  292. stateLoadParams: function (settings, data) {
  293. if (data.search.search){
  294. const filterSearch = document.querySelector('[data-table-filter="search"]');
  295. filterSearch.value = data.search.search;
  296. }
  297. },
  298. language: {
  299. info: $.t('datatable.info'),
  300. infoEmpty: $.t('datatable.info_empty'),
  301. infoFiltered: $.t('datatable.info_filtered'),
  302. loadingRecords: "",
  303. processing: $.t('datatable.processing'),
  304. zeroRecords: "",
  305. emptyTable: $.t('datatable.no_records')
  306. },
  307. order: [[0, 'asc']],
  308. initComplete: function(settings, json) {
  309. $('#loader').addClass("d-none");
  310. $('#card_content').removeClass("d-none");
  311. let api = $.fn.dataTable.Api(settings);
  312. api.columns.adjust().draw("page");
  313. drawAction();
  314. }
  315. });
  316. dt.on('draw', drawAction);
  317. dt.on('column-reorder', function(e, settings, details){
  318. drawAction();
  319. });
  320. }
  321. function drawAction() {
  322. KTMenu.createInstances();
  323. handleRowActions();
  324. $('#table_body').localize();
  325. }
  326. var handleDatatableActions = function () {
  327. const filterSearch = $(document.querySelector('[data-table-filter="search"]'));
  328. filterSearch.off("keyup");
  329. filterSearch.on('keyup', function (e) {
  330. dt.rows().deselect();
  331. dt.search(e.target.value, true, false).draw();
  332. });
  333. }
  334. function handleRowActions() {
  335. const closeButtons = document.querySelectorAll('[data-table-action="close_conn"]');
  336. closeButtons.forEach(d => {
  337. let el = $(d);
  338. el.off("click");
  339. el.on("click", function(e){
  340. e.preventDefault();
  341. const parent = e.target.closest('tr');
  342. let data = dt.row(parent).data();
  343. disconnectAction(data.connection_id, data.node);
  344. });
  345. });
  346. }
  347. return {
  348. init: function () {
  349. initDatatable();
  350. handleDatatableActions();
  351. }
  352. }
  353. }();
  354. $(document).on("i18nshow", function(){
  355. datatable.init();
  356. });
  357. </script>
  358. {{- end}}