mirror of
https://github.com/mailcow/mailcow-dockerized.git
synced 2025-01-24 05:16:55 +02:00
240b2c63f6
Timestamps retrieved from the API were always converted to a browser local format. The format specified for moment.js added in 5160eff29446ba94329116a6ed3787400b03e461 did not work because of this. Additionally, the format specified used `dd` which looks for two letter days, such as "Mo", "Tu", "We", etc. Furthermore, `mm` is used for minutes, not months. Because the locale formatted datetime can vary a lot, it is not easy to get this into moment.js to enable the sorting of datetimes in the datatables. In other words, there is no conversion from an `Intl.DateTimeFormat` specifier string to moment.js. Adding many `$.fn.dataTable.moment(format);` with different `format`s is not useful. I have fixed this rewriting how the timestamps from the API are added to the tables. It still uses the locale of the browser, because not everyone wants to use ISO 8601, but no longer requires moment.js (which has been removed). Two data attributes are added to the `td`s of the timestamps: - `data-order` - `data-sort` The values of these are the timestamps as returned by the server, which are very easily sorted (as they are just UNIX timestamps). Then, when creating the cell in the table, it will be converted to what the locale of the browser specified (this has not changed).
398 lines
14 KiB
JavaScript
398 lines
14 KiB
JavaScript
$(document).ready(function() {
|
|
mass_action = false;
|
|
function validateEmail(email) {
|
|
var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
|
return re.test(email);
|
|
}
|
|
function validateRegex(e){var t=e.split("/"),n=e,r="";t.length>1&&(n=t[1],r=t[2]);try{return new RegExp(n,r),!0}catch(e){return!1}}
|
|
function is_active(elem) {
|
|
if ($(elem).data('submitted') == '1') {
|
|
return true;
|
|
} else {
|
|
var parent_btn_grp = $(elem).parentsUntil(".btn-group").parent();
|
|
if (parent_btn_grp.hasClass('btn-group')) {
|
|
parent_btn_grp.replaceWith('<button class="btn btn-secondary btn-sm" disabled>' + lang_footer.loading + '</a>');
|
|
}
|
|
$(elem).text(lang_footer.loading);
|
|
$(elem).attr('data-submitted', '1');
|
|
function disableF5(e) { if ((e.which || e.keyCode) == 116 || (e.which || e.keyCode) == 82) e.preventDefault(); };
|
|
$(document).on("keydown", disableF5);
|
|
return false;
|
|
}
|
|
}
|
|
$.fn.serializeObject = function() {
|
|
var o = {};
|
|
var a = this.serializeArray();
|
|
$.each(a, function() {
|
|
if (o[this.name]) {
|
|
if (!o[this.name].push) {
|
|
o[this.name] = [o[this.name]];
|
|
}
|
|
o[this.name].push(this.value || '');
|
|
} else {
|
|
o[this.name] = this.value || '';
|
|
}
|
|
});
|
|
return o;
|
|
};
|
|
// Collect values of input fields with name "multi_select" and same data-id to js array multi_data[data-id]
|
|
var multi_data = [];
|
|
$(document).on('change', 'input[name=multi_select]:checkbox', function(e) {
|
|
if(mass_action === true) {
|
|
multi_data = [];
|
|
mass_action = false;
|
|
}
|
|
if ($(this).is(':checked') && $(this).data('id')) {
|
|
var id = $(this).data('id');
|
|
if (typeof multi_data[id] == "undefined") {
|
|
multi_data[id] = [];
|
|
}
|
|
multi_data[id].push($(this).val());
|
|
}
|
|
else {
|
|
var id = $(this).data('id');
|
|
if (typeof multi_data[id] !== "undefined") {
|
|
multi_data[id].splice($.inArray($(this).val(), multi_data[id]),1);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Select checkbox by click on parent tr
|
|
$(document).on('click', 'tbody>tr', function(e) {
|
|
if(e.target.tagName.toLowerCase() === 'button') {
|
|
e.stopPropagation();
|
|
}
|
|
else if(e.target.tagName.toLowerCase() === 'a') {
|
|
e.stopPropagation();
|
|
}
|
|
else if (e.target.type == "checkbox") {
|
|
e.stopPropagation();
|
|
}
|
|
else {
|
|
var checkbox = $(this).find(':checkbox');
|
|
checkbox.trigger('click');
|
|
}
|
|
});
|
|
|
|
// Select or deselect all checkboxes with same data-id
|
|
$(document).on('click', '#toggle_multi_select_all', function(e) {
|
|
mass_action = true
|
|
e.preventDefault();
|
|
id = $(this).data("id");
|
|
var all_checkboxes = $("input[data-id=" + id + "]:enabled");
|
|
all_checkboxes.prop("checked", !all_checkboxes.prop("checked")).change();
|
|
});
|
|
|
|
// General API edit actions
|
|
$(document).on('click', "[data-action='edit_selected']", function(e) {
|
|
e.preventDefault();
|
|
var id = $(this).data('id');
|
|
var api_url = $(this).data('api-url');
|
|
var api_attr = $(this).data('api-attr');
|
|
if (typeof $(this).data('api-reload-window') !== 'undefined') {
|
|
api_reload_window = $(this).data('api-reload-window');
|
|
} else {
|
|
api_reload_window = true;
|
|
}
|
|
if (typeof $(this).data('api-reload-location') !== 'undefined') {
|
|
api_reload_location = $(this).data('api-reload-location');
|
|
} else {
|
|
api_reload_location = '#';
|
|
}
|
|
// If clicked element #edit_selected is in a form with the same data-id as the button,
|
|
// we merge all input fields by {"name":"value"} into api-attr
|
|
if ($(this).closest("form").data('id') == id) {
|
|
var invalid = false;
|
|
$(this).closest("form").find('select, textarea, input').each(function() {
|
|
if ($(this).prop('required')) {
|
|
if (!$(this).val() && $(this).prop('disabled') === false) {
|
|
invalid = true;
|
|
if ($(this).is("select")) {
|
|
$(this).selectpicker('setStyle', 'btn-input-missing', 'add');
|
|
}
|
|
$(this).addClass('inputMissingAttr');
|
|
} else {
|
|
if ($(this).is("select")) {
|
|
$(this).selectpicker('setStyle', 'btn-input-missing', 'remove');
|
|
}
|
|
$(this).removeClass('inputMissingAttr');
|
|
}
|
|
}
|
|
if ($(this).val() && $(this).attr("type") == 'email') {
|
|
if (!validateEmail($(this).val())) {
|
|
invalid = true;
|
|
$(this).addClass('inputMissingAttr');
|
|
} else {
|
|
$(this).removeClass('inputMissingAttr');
|
|
}
|
|
}
|
|
if ($(this).attr("max")) {
|
|
if (Number($(this).val()) > Number($(this).attr("max"))) {
|
|
invalid = true;
|
|
$(this).addClass('inputMissingAttr');
|
|
} else {
|
|
if ($(this).attr("min")) {
|
|
if (Number($(this).val()) < Number($(this).attr("min"))) {
|
|
invalid = true;
|
|
$(this).addClass('inputMissingAttr');
|
|
} else {
|
|
$(this).removeClass('inputMissingAttr');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ($(this).val() && $(this).attr("regex")) {
|
|
var regex_content = $(this).val();
|
|
$(this).removeClass('inputMissingAttr');
|
|
if(!validateRegex(regex_content)) {
|
|
invalid = true;
|
|
$(this).addClass('inputMissingAttr');
|
|
}
|
|
if(!regex_content.startsWith('/') || !/\/[ims]?$/.test(regex_content)){
|
|
invalid = true;
|
|
$(this).addClass('inputMissingAttr');
|
|
}
|
|
}
|
|
});
|
|
if (!invalid) {
|
|
var attr_to_merge = $(this).closest("form").serializeObject();
|
|
// parse possible JSON Strings
|
|
for (var [key, value] of Object.entries(attr_to_merge)) {
|
|
try {
|
|
attr_to_merge[key] = JSON.parse(attr_to_merge[key]);
|
|
} catch {}
|
|
}
|
|
var api_attr = $.extend(api_attr, attr_to_merge)
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
// alert(JSON.stringify(api_attr));
|
|
// If clicked element #edit_selected has data-item attribute, it is added to "items"
|
|
if (typeof $(this).data('item') !== 'undefined') {
|
|
var id = $(this).data('id');
|
|
if (typeof multi_data[id] == "undefined") {
|
|
multi_data[id] = [];
|
|
}
|
|
multi_data[id].splice($.inArray($(this).data('item'), multi_data[id]), 1);
|
|
multi_data[id].push($(this).data('item'));
|
|
}
|
|
if (typeof multi_data[id] == "undefined") return;
|
|
api_items = multi_data[id];
|
|
for (var i in api_items) {
|
|
api_items[i] = decodeURIComponent(api_items[i]);
|
|
}
|
|
// alert(JSON.stringify(api_attr));
|
|
if (Object.keys(api_items).length !== 0) {
|
|
if (is_active($(this))) { return false; }
|
|
$.ajax({
|
|
type: "POST",
|
|
dataType: "json",
|
|
data: {
|
|
"items": JSON.stringify(api_items),
|
|
"attr": JSON.stringify(api_attr),
|
|
"csrf_token": csrf_token
|
|
},
|
|
url: '/api/v1/' + api_url,
|
|
jsonp: false,
|
|
complete: function(data) {
|
|
var response = (data.responseText);
|
|
if (typeof response !== 'undefined' && response.length !== 0) {
|
|
response_obj = JSON.parse(response);
|
|
}
|
|
if (api_reload_window === true) {
|
|
if (api_reload_location != '#') {
|
|
window.location.replace(api_reload_location)
|
|
} else {
|
|
window.location = window.location.href.split("#")[0];
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// General API add actions
|
|
$(document).on('click', "[data-action='add_item']", function(e) {
|
|
e.preventDefault();
|
|
var id = $(this).data('id');
|
|
var api_url = $(this).data('api-url');
|
|
var api_attr = $(this).data('api-attr');
|
|
if (typeof $(this).data('api-reload-window') !== 'undefined') {
|
|
api_reload_window = $(this).data('api-reload-window');
|
|
} else {
|
|
api_reload_window = true;
|
|
}
|
|
// If clicked button is in a form with the same data-id as the button,
|
|
// we merge all input fields by {"name":"value"} into api-attr
|
|
if ($(this).closest("form").data('id') == id) {
|
|
var invalid = false;
|
|
$(this).closest("form").find('select, textarea, input').each(function() {
|
|
if ($(this).prop('required')) {
|
|
if (!$(this).val() && $(this).prop('disabled') === false) {
|
|
invalid = true;
|
|
if ($(this).is("select")) {
|
|
$(this).selectpicker('setStyle', 'btn-input-missing', 'add');
|
|
}
|
|
$(this).addClass('inputMissingAttr');
|
|
} else {
|
|
if ($(this).is("select")) {
|
|
$(this).selectpicker('setStyle', 'btn-input-missing', 'remove');
|
|
}
|
|
$(this).removeClass('inputMissingAttr');
|
|
}
|
|
}
|
|
if ($(this).attr("type") == 'email') {
|
|
var emailReg = /^([\w-\.]+@([\w-]+\.)+[\w-]{2,4})?$/;
|
|
if (!emailReg.test($(this).val())) {
|
|
invalid = true;
|
|
$(this).addClass('inputMissingAttr');
|
|
} else {
|
|
$(this).removeClass('inputMissingAttr');
|
|
}
|
|
}
|
|
if ($(this).attr("max")) {
|
|
if (Number($(this).val()) > Number($(this).attr("max"))) {
|
|
invalid = true;
|
|
$(this).addClass('inputMissingAttr');
|
|
} else {
|
|
if ($(this).attr("min")) {
|
|
if (Number($(this).val()) < Number($(this).attr("min"))) {
|
|
invalid = true;
|
|
$(this).addClass('inputMissingAttr');
|
|
} else {
|
|
$(this).removeClass('inputMissingAttr');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
if (!invalid) {
|
|
var attr_to_merge = $(this).closest("form").serializeObject();
|
|
// parse possible JSON Strings
|
|
for (var [key, value] of Object.entries(attr_to_merge)) {
|
|
try {
|
|
attr_to_merge[key] = JSON.parse(attr_to_merge[key]);
|
|
} catch {}
|
|
}
|
|
var api_attr = $.extend(api_attr, attr_to_merge)
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
if (is_active($(this))) { return false; }
|
|
// alert(JSON.stringify(api_attr));
|
|
$.ajax({
|
|
type: "POST",
|
|
dataType: "json",
|
|
data: {
|
|
"attr": JSON.stringify(api_attr),
|
|
"csrf_token": csrf_token
|
|
},
|
|
url: '/api/v1/' + api_url,
|
|
jsonp: false,
|
|
complete: function(data) {
|
|
var response = (data.responseText);
|
|
if (typeof response !== 'undefined' && response.length !== 0) {
|
|
response_obj = JSON.parse(response);
|
|
unset = true;
|
|
$.each(response_obj, function(i, v) {
|
|
if (v.type == "danger") {
|
|
unset = false;
|
|
}
|
|
});
|
|
if (unset === true) {
|
|
unset = null;
|
|
// Keep form data for sync jobs
|
|
if (id != "add_syncjob") {
|
|
$('form').formcache('clear');
|
|
$('form').formcache('destroy');
|
|
var i = localStorage.length;
|
|
while(i--) {
|
|
var key = localStorage.key(i);
|
|
if(/formcache/.test(key)) {
|
|
localStorage.removeItem(key);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
var add_modal = $('.modal.in').attr('id');
|
|
localStorage.setItem("add_modal", add_modal);
|
|
}
|
|
}
|
|
if (api_reload_window === true) {
|
|
window.location = window.location.href.split("#")[0];
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
// General API delete actions
|
|
$(document).on('click', "[data-action='delete_selected']", function(e) {
|
|
e.preventDefault();
|
|
var id = $(this).data('id');
|
|
// If clicked element #delete_selected has data-item attribute, it is added to "items"
|
|
if (typeof $(this).data('item') !== 'undefined') {
|
|
var id = $(this).data('id');
|
|
if (typeof multi_data[id] == "undefined") {
|
|
multi_data[id] = [];
|
|
}
|
|
multi_data[id].splice($.inArray($(this).data('item'), multi_data[id]), 1);
|
|
multi_data[id].push($(this).data('item'));
|
|
}
|
|
|
|
if (typeof $(this).data('text') !== 'undefined') {
|
|
$("#DeleteText").empty();
|
|
$("#DeleteText").text($(this).data('text'));
|
|
}
|
|
if (typeof multi_data[id] == "undefined" || multi_data[id] == "") return;
|
|
data_array = multi_data[id];
|
|
api_url = $(this).data('api-url');
|
|
$(document).on('show.bs.modal', '#ConfirmDeleteModal', function() {
|
|
$("#ItemsToDelete").empty();
|
|
for (var i in data_array) {
|
|
data_array[i] = decodeURIComponent(data_array[i]);
|
|
$("#ItemsToDelete").append("<li>" + escapeHtml(data_array[i]) + "</li>");
|
|
}
|
|
})
|
|
$('#ConfirmDeleteModal').modal('show')
|
|
.one('click', '#IsConfirmed', function(e) {
|
|
if (is_active($('#IsConfirmed'))) { return false; }
|
|
$.ajax({
|
|
type: "POST",
|
|
dataType: "json",
|
|
cache: false,
|
|
data: {
|
|
"items": JSON.stringify(data_array),
|
|
"csrf_token": csrf_token
|
|
},
|
|
url: '/api/v1/' + api_url,
|
|
jsonp: false,
|
|
complete: function(data) {
|
|
window.location = window.location.href.split("#")[0];
|
|
}
|
|
});
|
|
})
|
|
.one('click', '#isCanceled', function(e) {
|
|
// Remove event handler to allow to close modal and restart dialog without multiple submits
|
|
$('#ConfirmDeleteModal').off();
|
|
$('#ConfirmDeleteModal').modal('hide');
|
|
});
|
|
});
|
|
|
|
// toggle jquery datatables child rows
|
|
$('button[data-datatables-expand], a[data-datatables-expand]').on('click', function (e) {
|
|
e.preventDefault();
|
|
var tableId = e.target.getAttribute("data-datatables-expand");
|
|
var table = $("#" + tableId).DataTable();
|
|
table.rows(':not(.parent)').nodes().to$().find('td:first-child').trigger('click');
|
|
});
|
|
$('button[data-datatables-collapse], a[data-datatables-collapse]').on('click', function (e) {
|
|
e.preventDefault();
|
|
var tableId = e.target.getAttribute("data-datatables-collapse");
|
|
var table = $("#" + tableId).DataTable();
|
|
table.rows('.parent').nodes().to$().find('td:first-child').trigger('click');
|
|
});
|
|
});
|