sftpgo-mirror/templates/webadmin/eventrules.html
Nicola Murino 3dd412f6e3
WebAdmin and REST API: remove too granular permissions
Our permissions system for admin users is too granular and some
permissions overlap. For example, you can define an administrator
with the "manage_system" permission and not with the "manage_admins"
or "manage_user" permission, but the "manage_system" permission
allows you to restore a backup and then create users and
administrators. The following permissions will be removed:
"manage_admins", "manage_apikeys", "manage_system", "retention_checks",
"manage_event_rules", "manage_roles", "manage_ip_lists". Now you
need to add the "*" permission to replace the removed granular
permissions because the removed permissions allow actions that
should only be allowed to super administrators.
There is no point in having separate, overlapping permissions.

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2024-11-10 10:46:28 +01:00

407 lines
No EOL
17 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="rules.view_manage" class="card-title section-title">View and manage event rules</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">
{{- if .LoggedUser.HasPermission "*"}}
<a href="{{.EventRuleURL}}" class="btn btn-primary ms-5">
<i class="ki-duotone ki-plus fs-2"></i>
<span data-i18n="general.add">Add</span>
</a>
{{- end}}
</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="general.name">Name</th>
<th data-i18n="general.status">Status</th>
<th data-i18n="rules.trigger">Trigger</th>
<th data-i18n="title.event_actions">Actions</th>
<th class="min-w-100px"></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 deleteAction(name) {
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){
clearLoading();
KTApp.showPageLoading();
let path = '{{.EventRuleURL}}' + "/" + encodeURIComponent(name);
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"
}
});
});
}
});
}
function runAction(name) {
ModalAlert.fire({
text: $.t('rules.run_confirm'),
icon: "warning",
confirmButtonText: $.t('rules.run_confirm_btn'),
cancelButtonText: $.t('general.cancel'),
customClass: {
confirmButton: "btn btn-danger",
cancelButton: 'btn btn-secondary'
}
}).then((result) => {
if (result.isConfirmed){
clearLoading();
KTApp.showPageLoading();
let path = '{{.EventRuleURL}}' + "/run/" + encodeURIComponent(name);
axios.post(path, null, {
timeout: 15000,
headers: {
'X-CSRF-TOKEN': '{{.CSRFToken}}'
},
validateStatus: function (status) {
return status == 202;
}
}).then(function(response){
KTApp.hidePageLoading();
ModalAlert.fire({
text: $.t("rules.run_ok"),
icon: "success",
confirmButtonText: $.t('general.ok'),
customClass: {
confirmButton: "btn btn-primary"
}
});
}).catch(function(error){
KTApp.hidePageLoading();
ModalAlert.fire({
text: $.t("rules.run_error_generic"),
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: "{{.EventRulesURL}}/json",
dataSrc: "",
error: function ($xhr, textStatus, errorThrown) {
$(".dt-processing").hide();
$('#loader').addClass("d-none");
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: "name",
render: function(data, type, row) {
if (type === 'display') {
return escapeHTML(data);
}
return data;
}
},
{
data: "status",
render: function(data, type, row) {
if (type === 'display') {
switch (data){
case 1:
return $.t('general.active');
default:
return $.t('general.inactive');
}
}
return data;
}
},
{
data: "trigger",
render: function(data, type, row) {
if (type === 'display') {
switch (data){
case 1:
return $.t('rules.triggers.fs_event');
case 2:
return $.t('rules.triggers.provider_event');
case 3:
return $.t('rules.triggers.schedule');
case 4:
return $.t('rules.triggers.ip_blocked');
case 5:
return $.t('rules.triggers.certificate_renewal');
case 6:
return $.t('rules.triggers.on_demand');
case 7:
return $.t('rules.triggers.idp_login');
default:
return "";
}
}
return data;
}
},
{
data: "actions",
defaultContent: [],
render: function(data, type, row) {
if (!data){
return "";
}
let actions = [];
let result = "";
for (i = 0; i < data.length; i++){
actions.push(data[i].name);
}
result = actions.join(", ");
if (type === 'display') {
return escapeHTML(result);
}
return result;
}
},
{
data: "id",
searchable: false,
orderable: false,
className: 'text-end',
render: function (data, type, row) {
if (type === 'display') {
let numActions = 0;
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">
<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">`;
//{{- if .LoggedUser.HasPermission "*"}}
numActions++;
actions+=`<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>`;
numActions++;
if (row.trigger === 6){
actions+=`<div class="menu-item px-3">
<a data-i18n="rules.run" href="#" class="menu-link px-3" data-table-action="run_row">Run</a>
</div>`;
numActions++;
}
actions+=`<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>`;
//{{- end}}
if (numActions > 0){
actions+=`</div>`;
return actions;
}
}
return "";
}
},
],
deferRender: true,
stateSave: true,
stateDuration: 0,
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).draw();
});
}
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('{{.EventRuleURL}}' + "/" + encodeURIComponent(rowData['name']));
});
});
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');
deleteAction(dt.row(parent).data()['name']);
});
});
const runButtons = document.querySelectorAll('[data-table-action="run_row"]');
runButtons.forEach(d => {
let el = $(d);
el.off("click");
el.on("click", function(e){
e.preventDefault();
const parent = e.target.closest('tr');
runAction(dt.row(parent).data()['name']);
});
});
}
return {
init: function () {
initDatatable();
handleDatatableActions();
}
}
}();
$(document).on("i18nshow", function(){
datatable.init();
});
</script>
{{- end}}