sftpgo-mirror/templates/webadmin/connections.html
Nicola Murino 8180b75ef1
WIP new WebAdmin: IP lists pages
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-01-24 19:23:15 +01:00

376 lines
No EOL
16 KiB
HTML

<!--
Copyright (C) 2024 Nicola Murino
This WebUI uses the KeenThemes Mega Bundle, a proprietary theme:
https://keenthemes.com/products/templates-mega-bundle
KeenThemes HTML/CSS/JS components are allowed for use only within the
SFTPGo product and restricted to be used in a resealable HTML template
that can compete with KeenThemes products anyhow.
This WebUI is allowed for use only within the SFTPGo product and
therefore cannot be used in derivative works/products without an
explicit grant from the SFTPGo Team (support@sftpgo.com).
-->
{{template "base" .}}
{{- define "extra_css"}}
<link href="{{.StaticURL}}/assets/plugins/custom/datatables/datatables.bundle.css" rel="stylesheet" type="text/css"/>
{{- end}}
{{- define "page_body"}}
<div class="card shadow-sm">
<div class="card-header bg-light">
<h3 data-i18n="connections.view_manage" class="card-title section-title">View and manage connections</h3>
</div>
<div id="card_body" class="card-body">
<div id="loader" class="align-items-center text-center my-10">
<span class="spinner-border w-15px h-15px text-muted align-middle me-2"></span>
<span data-i18n="general.loading" class="text-gray-700">Loading...</span>
</div>
<div id="card_content" class="d-none">
<div class="d-flex flex-stack flex-wrap mb-5">
<div class="d-flex align-items-center position-relative my-2">
<i class="ki-solid ki-magnifier fs-1 position-absolute ms-6"></i>
<input name="search" data-i18n="[placeholder]general.search" type="text" data-table-filter="search"
class="form-control rounded-1 w-250px ps-15 me-5" placeholder="Search" />
</div>
<div class="d-flex justify-content-end my-2" data-table-toolbar="base">
<a href="{{.ConnectionsURL}}" class="btn btn-primary">
<i class="ki-solid ki-arrows-circle fs-2"></i>
<span data-i18n="general.refresh">Refresh</span>
</a>
</div>
</div>
<table id="dataTable" class="table align-middle table-row-dashed fs-6 gy-5">
<thead>
<tr class="text-start text-muted fw-bold fs-6 gs-0">
<th>ID</th>
<th>Node</th>
<th data-i18n="login.username">Username</th>
<th data-i18n="connections.started">Started</th>
<th data-i18n="connections.remote_address">Remote address</th>
<th data-i18n="general.protocol">Protocol</th>
<th data-i18n="connections.last_activity">Last activity</th>
<th data-i18n="general.info">Info</th>
<th></th>
</tr>
</thead>
<tbody id="table_body" class="text-gray-800 fw-semibold"></tbody>
</table>
</div>
</div>
</div>
{{- end}}
{{- define "extra_js"}}
<script {{- if .CSPNonce}} nonce="{{.CSPNonce}}"{{- end}} src="{{.StaticURL}}/assets/plugins/custom/datatables/datatables.bundle.js"></script>
<script type="text/javascript" {{- if .CSPNonce}} nonce="{{.CSPNonce}}"{{- end}}>
function disconnectAction(connectionID, node) {
ModalAlert.fire({
text: $.t('connections.disconnect_confirm'),
icon: "warning",
confirmButtonText: $.t('connections.disconnect_confirm_btn'),
cancelButtonText: $.t('general.cancel'),
customClass: {
confirmButton: "btn btn-danger",
cancelButton: 'btn btn-secondary'
}
}).then((result) => {
if (result.isConfirmed){
$('#loading_message').text("");
KTApp.showPageLoading();
let path = '{{.ConnectionsURL}}' + "/" + encodeURIComponent(connectionID);
if (node) {
path+="?node="+ encodeURIComponent(node);
}
axios.delete(path, {
timeout: 15000,
headers: {
'X-CSRF-TOKEN': '{{.CSRFToken}}'
},
validateStatus: function (status) {
return status == 200;
}
}).then(function(response){
setTimeout(function() {
location.reload();
},250);
}).catch(function(error){
KTApp.hidePageLoading();
ModalAlert.fire({
text: $.t('connections.disconnect_ko'),
icon: "warning",
confirmButtonText: $.t('general.ok'),
customClass: {
confirmButton: "btn btn-primary"
}
});
});
}
});
}
var datatable = function(){
var dt;
var initDatatable = function () {
$('#errorMsg').addClass("d-none");
dt = $('#dataTable').DataTable({
ajax: {
url: "{{.ConnectionsURL}}/json",
dataSrc: "",
error: function ($xhr, textStatus, errorThrown) {
$(".dataTables_processing").hide();
let txt = "";
if ($xhr) {
let json = $xhr.responseJSON;
if (json) {
if (json.message){
txt = json.message;
}
}
}
if (!txt){
txt = "general.error500";
}
setI18NData($('#errorTxt'), txt);
$('#errorMsg').removeClass("d-none");
}
},
columns: [
{
data: "connection_id",
visible: false,
searchable: false,
orderable: false,
render: function(data, type, row) {
if (type === 'display') {
return escapeHTML(data);
}
return data;
}
},
{
data: "node",
visible: false,
searchable: false,
orderable: false,
defaultContent: "",
render: function(data, type, row) {
if (type === 'display') {
return escapeHTML(data);
}
return data;
}
},
{
data: "username",
defaultContent: "",
render: function(data, type, row) {
if (type === 'display') {
return escapeHTML(data);
}
return data;
}
},
{
data: "connection_time",
searchable: false,
defaultContent: 0,
render: function(data, type, row) {
if (type === 'display') {
if (data > 0){
return $.t('general.datetime', {
val: parseInt(data, 10),
formatParams: {
val: { year: '2-digit', month: 'numeric', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric' },
}
});
}
return ""
}
return data;
}
},
{
data: "remote_address",
defaultContent: "",
render: function(data, type, row) {
if (type === 'display') {
return escapeHTML(data);
}
return data;
}
},
{
data: "protocol",
defaultContent: "",
render: function(data, type, row) {
if (type === 'display') {
return escapeHTML(data);
}
return data;
}
},
{
data: "last_activity",
searchable: false,
defaultContent: 0,
render: function(data, type, row) {
if (type === 'display') {
if (data > 0){
return $.t('general.datetime', {
val: parseInt(data, 10),
formatParams: {
val: { year: '2-digit', month: 'numeric', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric' },
}
});
}
return ""
}
return data;
}
},
{
data: "active_transfers",
searchable: false,
orderable: false,
render: function (data, type, row) {
if (type === 'display') {
let result = "";
if (row.active_transfers && row.active_transfers.length > 0){
let transfer = row.active_transfers[0];
let path = escapeHTML(transfer.path);
let elapsed = row.current_time - transfer.start_time;
if (elapsed > 0 && transfer.size > 0){
let speed = (transfer.size*1.0) / (elapsed/1000.0);
if (transfer.operation_type === 'upload'){
result = $.t('connections.upload_info', {path: path, size: fileSizeIEC(transfer.size), speed: humanizeSpeed(speed)});
} else {
result = $.t('connections.download_info', {path: path, size: fileSizeIEC(transfer.size), speed: humanizeSpeed(speed)});
}
} else {
if (transfer.operation_type === 'upload'){
result = $.t('connections.upload', {path: path});
} else {
result = $.t('connections.download', {path: path});
}
}
}
if (row.client_version){
if (result){
result+= ". ";
}
result+= $.t('connections.client', {val: escapeHTML(row.client_version)});
}
return result;
}
return "";
}
},
{
data: "",
searchable: false,
orderable: false,
className: 'text-end',
render: function (data, type, row) {
if (type === 'display') {
//{{- if .LoggedUser.HasPermission "close_conns"}}
return `<div class="d-flex justify-content-end">
<div class="ms-2">
<a href="#" class="btn btn-sm btn-icon btn-light-danger" data-table-action="close_conn">
<i class="ki-solid ki-cross fs-1"></i>
</a>
</div>
</div>`;
//{{- end}}
}
return "";
}
},
],
deferRender: true,
stateSave: true,
stateDuration: 0,
colReorder: {
enable: true
},
stateLoadParams: function (settings, data) {
if (data.search.search){
const filterSearch = document.querySelector('[data-table-filter="search"]');
filterSearch.value = data.search.search;
}
},
language: {
info: $.t('datatable.info'),
infoEmpty: $.t('datatable.info_empty'),
infoFiltered: $.t('datatable.info_filtered'),
loadingRecords: "",
processing: $.t('datatable.processing'),
zeroRecords: "",
emptyTable: $.t('datatable.no_records')
},
order: [[0, 'asc']],
initComplete: function(settings, json) {
$('#loader').addClass("d-none");
$('#card_content').removeClass("d-none");
let api = $.fn.dataTable.Api(settings);
api.columns.adjust().draw("page");
drawAction();
}
});
dt.on('draw', drawAction);
dt.on('column-reorder', function(e, settings, details){
drawAction();
});
}
function drawAction() {
KTMenu.createInstances();
handleRowActions();
$('#table_body').localize();
}
var handleDatatableActions = function () {
const filterSearch = $(document.querySelector('[data-table-filter="search"]'));
filterSearch.off("keyup");
filterSearch.on('keyup', function (e) {
dt.rows().deselect();
dt.search(e.target.value, true, false).draw();
});
}
function handleRowActions() {
const closeButtons = document.querySelectorAll('[data-table-action="close_conn"]');
closeButtons.forEach(d => {
let el = $(d);
el.off("click");
el.on("click", function(e){
e.preventDefault();
const parent = e.target.closest('tr');
let data = dt.row(parent).data();
disconnectAction(data.connection_id, data.node);
});
});
}
return {
init: function () {
initDatatable();
handleDatatableActions();
}
}
}();
$(document).on("i18nshow", function(){
datatable.init();
});
</script>
{{- end}}