d01fccf28c
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
501 lines
No EOL
20 KiB
HTML
501 lines
No EOL
20 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"}}
|
|
{{- template "errmsg" ""}}
|
|
<div class="card shadow-sm">
|
|
<div class="card-header bg-light">
|
|
<h3 data-i18n="ip_list.view_manage" class="card-title section-title">View and manage IP Lists</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">
|
|
<div class="input-group">
|
|
<input name="search" id="idSearch" data-i18n="[placeholder]ip_list.search" type="text" class="form-control rounded-left w-250px" placeholder="Search" />
|
|
<button id="search_button" type="button" class="btn btn-primary">
|
|
<i class="ki-solid ki-magnifier fs-2"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="d-flex justify-content-end my-2" data-table-toolbar="base">
|
|
<div>
|
|
<select id="idListType" name="list_type" class="form-select me-3" data-control="i18n-select2" data-hide-search="true">
|
|
<option data-i18n="ip_list.defender_list" value="2">Defender</option>
|
|
<option data-i18n="ip_list.allow_list" value="1">Allow list</option>
|
|
<option data-i18n="ip_list.ratelimiters_safe_list" value="3">Rate limiters safe list</option>
|
|
</select>
|
|
</div>
|
|
|
|
<a href="#" id="idAdd" class="btn btn-primary ms-5">
|
|
<i class="ki-duotone ki-plus fs-2"></i>
|
|
<span data-i18n="general.add">Add</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 data-i18n="ip_list.ip_net">IP/Network</th>
|
|
<th data-i18n="ip_list.protocols">Protocols</th>
|
|
<th data-i18n="general.mode">Mode</th>
|
|
<th data-i18n="general.description">Description</th>
|
|
<th class="min-w-100px"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="table_body" class="text-gray-800 fw-semibold"></tbody>
|
|
</table>
|
|
|
|
<div id="paginationContainer" class="d-flex mt-4 mb-4 justify-content-end d-none">
|
|
<div class="btn-group" role="group" aria-label="Pagination">
|
|
<button id="pagePrevious" data-i18n="general.previous" type="button" class="btn btn-outline btn-active-primary disabled">Previous</button>
|
|
<button id="pageNext" data-i18n="general.next" type="button" class="btn btn-outline btn-active-primary disabled">Next</button>
|
|
</div>
|
|
</div>
|
|
|
|
</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}}>
|
|
|
|
const prefListTypeName = 'sftpgo_pref_{{.LoggedUser.Username}}_iplist_type';
|
|
const prefListFilter = 'sftpgo_pref_{{.LoggedUser.Username}}_iplist_search_filter';
|
|
const listType = getListType();
|
|
const listFilter = getSearchFilter();
|
|
|
|
const pageSize = 15;
|
|
const paginationData = new Map();
|
|
|
|
function saveListType(val) {
|
|
localStorage.setItem(prefListTypeName, val);
|
|
}
|
|
|
|
function getListType() {
|
|
return localStorage.getItem(prefListTypeName);
|
|
}
|
|
|
|
function saveSearchFilter() {
|
|
let val = $("#idSearch").val();
|
|
if (val){
|
|
localStorage.setItem(prefListFilter, val);
|
|
} else {
|
|
localStorage.removeItem(prefListFilter);
|
|
}
|
|
}
|
|
|
|
function getSearchFilter() {
|
|
return localStorage.getItem(prefListFilter);
|
|
}
|
|
|
|
function resetPagination() {
|
|
$('#pagePrevious').addClass("disabled");
|
|
$('#pageNext').addClass("disabled");
|
|
$('#paginationContainer').addClass("d-none");
|
|
paginationData.delete("firstIpOrNet");
|
|
paginationData.delete("lastIpOrNet");
|
|
paginationData.set("prevClicked",false);
|
|
paginationData.set("nextClicked",false);
|
|
}
|
|
|
|
function handleResponseData(data) {
|
|
let length = data.length;
|
|
let isNext = paginationData.get("nextClicked");
|
|
let isPrev = paginationData.get("prevClicked");
|
|
|
|
if (length > pageSize) {
|
|
data.pop();
|
|
length--;
|
|
if (isPrev || isNext){
|
|
$('#pagePrevious').removeClass("disabled");
|
|
}
|
|
$('#pageNext').removeClass("disabled");
|
|
} else {
|
|
if (isPrev){
|
|
$('#pagePrevious').addClass("disabled");
|
|
$('#pageNext').removeClass("disabled");
|
|
} else if (isNext){
|
|
$('#pagePrevious').removeClass("disabled");
|
|
$('#pageNext').addClass("disabled");
|
|
} else {
|
|
$('#pageNext').addClass("disabled");
|
|
}
|
|
}
|
|
if (isPrev){
|
|
data = data.reverse();
|
|
}
|
|
if (length > 0){
|
|
paginationData.set("firstIpOrNet",data[0].ipornet);
|
|
paginationData.set("lastIpOrNet",data[length-1].ipornet);
|
|
$('#paginationContainer').removeClass("d-none");
|
|
} else {
|
|
resetPagination();
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
function getSearchURL(){
|
|
let listType = encodeURIComponent($("#idListType").val());
|
|
let filter = encodeURIComponent($("#idSearch").val());
|
|
let limit = pageSize + 1;
|
|
let from = "";
|
|
let order = "ASC"
|
|
if (paginationData.get("nextClicked") && paginationData.has("lastIpOrNet")){
|
|
from = encodeURIComponent(paginationData.get("lastIpOrNet"));
|
|
}
|
|
if (paginationData.get("prevClicked") && paginationData.has("firstIpOrNet")){
|
|
from = encodeURIComponent(paginationData.get("firstIpOrNet"));
|
|
order = "DESC";
|
|
}
|
|
return "{{.IPListsURL}}"+`/${listType}?filter=${filter}&from=${from}&limit=${limit}&order=${order}`;
|
|
}
|
|
|
|
function checkSelectedListType(val) {
|
|
switch (val){
|
|
case "1":
|
|
//{{- if not .IsAllowListEnabled}}
|
|
showToast(0, 'ip_list.allow_list_disabled');
|
|
//{{- end}}
|
|
break;
|
|
case "2":
|
|
//{{- if not .HasDefender}}
|
|
showToast(0, 'ip_list.defender_disabled');
|
|
//{{- end}}
|
|
break;
|
|
case "3":
|
|
//{{- if not .RateLimitersStatus}}
|
|
showToast(0, 'ip_list.ratelimiters_disabled');
|
|
//{{- end}}
|
|
break;
|
|
}
|
|
}
|
|
|
|
function deleteAction(listType, ipNet) {
|
|
ModalAlert.fire({
|
|
text: $.t('general.delete_confirm_generic'),
|
|
icon: "warning",
|
|
confirmButtonText: $.t('general.delete_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 = '{{.IPListURL}}' + "/" + encodeURIComponent(listType)+ "/" + encodeURIComponent(ipNet);
|
|
axios.delete(path, {
|
|
timeout: 15000,
|
|
headers: {
|
|
'X-CSRF-TOKEN': '{{.CSRFToken}}'
|
|
},
|
|
validateStatus: function (status) {
|
|
return status == 200;
|
|
}
|
|
}).then(function(response){
|
|
location.reload();
|
|
}).catch(function(error){
|
|
KTApp.hidePageLoading();
|
|
let errorMessage;
|
|
if (error && error.response) {
|
|
switch (error.response.status) {
|
|
case 403:
|
|
errorMessage = "general.delete_error_403";
|
|
break;
|
|
case 404:
|
|
errorMessage = "general.delete_error_404";
|
|
break;
|
|
}
|
|
}
|
|
if (!errorMessage){
|
|
errorMessage = "general.delete_error_generic";
|
|
}
|
|
ModalAlert.fire({
|
|
text: $.t(errorMessage),
|
|
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: getSearchURL(),
|
|
dataSrc: handleResponseData,
|
|
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: "ipornet",
|
|
render: function(data, type, row) {
|
|
if (type === 'display') {
|
|
return escapeHTML(data);
|
|
}
|
|
return data;
|
|
}
|
|
},
|
|
{
|
|
data: "protocols",
|
|
render: function(data, type, row) {
|
|
if (type === 'display') {
|
|
if (data == 0){
|
|
return $.t('ip_list.any');
|
|
}
|
|
const protocols = [];
|
|
if ((data & 1) != 0){
|
|
protocols.push('SSH');
|
|
}
|
|
if ((data & 2) != 0){
|
|
protocols.push('FTP');
|
|
}
|
|
if ((data & 4) != 0){
|
|
protocols.push('DAV');
|
|
}
|
|
if ((data & 8) != 0){
|
|
protocols.push('HTTP');
|
|
}
|
|
return protocols.join(', ');
|
|
}
|
|
return data;
|
|
}
|
|
},
|
|
{
|
|
data: "mode",
|
|
render: function(data, type, row) {
|
|
if (type === 'display') {
|
|
if (data == 1){
|
|
return $.t('ip_list.allow');
|
|
}
|
|
return $.t('ip_list.deny');
|
|
}
|
|
return data;
|
|
}
|
|
},
|
|
{
|
|
data: "description",
|
|
visible: false,
|
|
render: function(data, type, row) {
|
|
if (type === 'display') {
|
|
return escapeHTML(data);
|
|
}
|
|
return data;
|
|
}
|
|
},
|
|
{
|
|
data: "",
|
|
searchable: false,
|
|
orderable: false,
|
|
className: 'text-end',
|
|
render: function (data, type, row) {
|
|
if (type === 'display') {
|
|
return `<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">
|
|
<span data-i18n="general.actions" class="fs-6">Actions</span>
|
|
<i class="ki-duotone ki-down fs-5 ms-1 rotate-180"></i>
|
|
</button>
|
|
<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">
|
|
<div class="menu-item px-3">
|
|
<a data-i18n="general.edit" href="#" class="menu-link px-3" data-table-action="edit_row">Edit</a>
|
|
</div>
|
|
<div class="menu-item px-3">
|
|
<a data-i18n="general.delete" href="#" class="menu-link text-danger px-3" data-table-action="delete_row">Delete</a>
|
|
</div>
|
|
</div>`;
|
|
}
|
|
return "";
|
|
}
|
|
},
|
|
],
|
|
deferRender: true,
|
|
processing: true,
|
|
lengthChange: false,
|
|
searching: false,
|
|
paging: false,
|
|
info: false,
|
|
ordering: false,
|
|
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')
|
|
},
|
|
initComplete: function(settings, json) {
|
|
handleColumnVisibility($('#idListType').val());
|
|
$('#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);
|
|
}
|
|
|
|
function drawAction() {
|
|
KTMenu.createInstances();
|
|
handleRowActions();
|
|
$('#table_body').localize();
|
|
}
|
|
|
|
function handleColumnVisibility(val) {
|
|
switch (val){
|
|
case '2':
|
|
dt.column(2).visible(true);
|
|
break;
|
|
default:
|
|
dt.column(2).visible(false);
|
|
}
|
|
}
|
|
|
|
function doSearch(){
|
|
dt.clear().draw();
|
|
dt.ajax.url(getSearchURL()).load();
|
|
}
|
|
|
|
var handleDatatableActions = function () {
|
|
$('#idListType').on("change", function(e){
|
|
let val = $(this).find("option:selected").attr('value');
|
|
saveListType(val);
|
|
handleColumnVisibility(val);
|
|
doSearch();
|
|
checkSelectedListType(val);
|
|
});
|
|
|
|
$('#search_button').on("click", function(){
|
|
resetPagination();
|
|
doSearch();
|
|
saveSearchFilter();
|
|
});
|
|
|
|
$('#pagePrevious').on("click", function(e){
|
|
e.preventDefault();
|
|
this.blur();
|
|
paginationData.set("prevClicked",true);
|
|
paginationData.set("nextClicked",false);
|
|
doSearch();
|
|
});
|
|
$('#pageNext').on("click", function(e){
|
|
e.preventDefault();
|
|
this.blur();
|
|
paginationData.set("prevClicked",false);
|
|
paginationData.set("nextClicked",true);
|
|
doSearch();
|
|
});
|
|
}
|
|
|
|
function handleRowActions() {
|
|
const editButtons = document.querySelectorAll('[data-table-action="edit_row"]');
|
|
editButtons.forEach(d => {
|
|
let el = $(d);
|
|
el.off("click");
|
|
el.on("click", function(e){
|
|
e.preventDefault();
|
|
let rowData = dt.row(e.target.closest('tr')).data();
|
|
window.location.replace('{{.IPListURL}}' + "/" + encodeURIComponent(rowData['type'])+"/"+encodeURIComponent(rowData['ipornet']));
|
|
});
|
|
});
|
|
|
|
const deleteButtons = document.querySelectorAll('[data-table-action="delete_row"]');
|
|
deleteButtons.forEach(d => {
|
|
let el = $(d);
|
|
el.off("click");
|
|
el.on("click", function(e){
|
|
e.preventDefault();
|
|
const parent = e.target.closest('tr');
|
|
let rowData = dt.row(parent).data();
|
|
deleteAction(rowData['type'],rowData['ipornet']);
|
|
});
|
|
});
|
|
}
|
|
|
|
return {
|
|
init: function () {
|
|
initDatatable();
|
|
handleDatatableActions();
|
|
}
|
|
}
|
|
}();
|
|
|
|
$(document).on("i18nload", function(){
|
|
resetPagination();
|
|
if (listType === '1' || listType === '3'){
|
|
$('#idListType').val(listType);
|
|
} else {
|
|
$('#idListType').val('2');
|
|
}
|
|
$('#idListType').trigger('change');
|
|
|
|
if (listFilter){
|
|
$('#idSearch').val(listFilter);
|
|
} else {
|
|
$('#idSearch').val('');
|
|
}
|
|
$("#idAdd").on("click", function(){
|
|
window.location.href = '{{.IPListURL}}'+"/"+encodeURIComponent($("#idListType").val());
|
|
});
|
|
});
|
|
|
|
$(document).on("i18nshow", function(){
|
|
datatable.init();
|
|
checkSelectedListType(listType);
|
|
});
|
|
</script>
|
|
{{- end}} |