mirror of
https://github.com/mailcow/mailcow-dockerized.git
synced 2026-11-08 10:13:37 +00:00
[Web] Archive settings, domain and mailbox switches, archive log and API
This commit is contained in:
25 files changed
+834
-12
No files matched your search
@@ -0,0 +1,92 @@
|
||||
// Archive tab of the admin system page: header profile fields and, while archiving is enabled, the state of the archive
|
||||
// under the switch (one line per problem; the details are in the archive log of the dashboard and in the queue manager).
|
||||
var mailcowArchive = (function($) {
|
||||
'use strict';
|
||||
|
||||
function L(key) {
|
||||
return (lang[key] !== undefined) ? lang[key] : key;
|
||||
}
|
||||
function esc(s) {
|
||||
return escapeHtml(s === null || s === undefined ? '' : String(s));
|
||||
}
|
||||
function time(ts) {
|
||||
return ts ? new Date(ts * 1000).toLocaleString(LOCALE, DATETIME_FORMAT) : '-';
|
||||
}
|
||||
// lang string with %s placeholders, values escaped
|
||||
function text(key, values) {
|
||||
var i = 0;
|
||||
return esc(L(key)).replace(/%s/g, function() { return esc(values[i++]); });
|
||||
}
|
||||
function notice(cls, html) {
|
||||
return '<div class="alert alert-' + cls + ' py-2 mb-2">' + html + '</div>';
|
||||
}
|
||||
var loaded = 0;
|
||||
|
||||
function renderStatus(s) {
|
||||
var html = '';
|
||||
if (s.enabled && !s.configured) {
|
||||
html += notice('warning', esc(L('archive_state_unconfigured')));
|
||||
}
|
||||
if (s.dropped_24h) {
|
||||
html += notice('danger', text('archive_state_dropped', [s.dropped_24h, time(s.last_dropped.time), s.last_dropped.reason]));
|
||||
}
|
||||
if (s.queued) {
|
||||
var reasons = $.map(s.delay_reasons || [], function(r) { return '<li class="text-break">' + esc(r) + '</li>'; });
|
||||
html += notice('warning', text('archive_state_queued', [s.queued, time(s.oldest_queued)]) +
|
||||
(reasons.length ? '<ul class="mb-0 mt-1 small">' + reasons.join('') + '</ul>' : ''));
|
||||
}
|
||||
if (!s.enabled) {
|
||||
html += notice('secondary', esc(L('archive_state_off')));
|
||||
} else if (!html) {
|
||||
html += notice('success', s.last_delivery ? text('archive_state_ok', [time(s.last_delivery)]) : esc(L('archive_state_ok_none')));
|
||||
}
|
||||
$('#archive_state').html(html);
|
||||
}
|
||||
|
||||
function load() {
|
||||
loaded = Date.now();
|
||||
$.get('/api/v1/get/archive/status', function(s) { renderStatus(s || {}); }, 'json').fail(function() {
|
||||
$('#archive_state').html(notice('danger', esc(lang_danger.generic_server_error)));
|
||||
});
|
||||
}
|
||||
|
||||
// fields of the selected header profile only; hidden fields are not posted, so their stored values are kept
|
||||
function showProfile() {
|
||||
var profile = $('#archive_profile').val();
|
||||
$('.archive-profile').each(function() {
|
||||
var on = $(this).hasClass('archive-profile-' + profile);
|
||||
$(this).toggleClass('d-none', !on).find(':input').prop('disabled', !on);
|
||||
});
|
||||
$('.archive-profile-info').each(function() {
|
||||
$(this).toggleClass('d-none', !$(this).hasClass('archive-profile-info-' + profile));
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
$('#archive_profile').on('change', showProfile);
|
||||
// the tests use the saved settings: not with unsaved changes in the form
|
||||
$('form[data-id="archive"]').on('input change', ':input', function() {
|
||||
$('.archive-test').prop('disabled', true);
|
||||
$('#archive_test_info').text(L('archive_save_first')).removeClass('text-muted').addClass('text-warning');
|
||||
});
|
||||
// the state line is only shown while archiving is enabled
|
||||
var tab = document.getElementById('collapse-tab-archive');
|
||||
if (!tab || !document.getElementById('archive_state')) return;
|
||||
// the tab body is at the top: visible as soon as the tab (or on mobile its card) is opened; load again when shown later
|
||||
new IntersectionObserver(function(entries) {
|
||||
if (entries.some(function(e) { return e.intersectionRatio > 0; }) && Date.now() - loaded > 10000) load();
|
||||
}).observe(tab);
|
||||
$('#archive_refresh').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
load();
|
||||
});
|
||||
// open the dashboard with the archive log (the dashboard restores the last tab)
|
||||
$('#archive_log_link').on('click', function() {
|
||||
try {
|
||||
localStorage.setItem('lastTag', 'tab-archive-logs');
|
||||
} catch (e) {}
|
||||
});
|
||||
});
|
||||
|
||||
return {load: load};
|
||||
})(jQuery);
|
||||
@@ -875,6 +875,81 @@ jQuery(function($){
|
||||
hideTableExpandCollapseBtn('#tab-cron-logs', '#cron_log');
|
||||
});
|
||||
}
|
||||
// archive copies from the Postfix log and mails not archived; the Message-ID finds a mail
|
||||
function draw_archive_logs() {
|
||||
// just recalc width if instance already exists
|
||||
if ($.fn.DataTable.isDataTable('#archive_log') ) {
|
||||
$('#archive_log').DataTable().columns.adjust().responsive.recalc();
|
||||
return;
|
||||
}
|
||||
// softbounce: the archive refused the copy, Postfix keeps it and tries again
|
||||
var status_class = {sent: 'bg-success', deferred: 'bg-warning text-dark', softbounce: 'bg-danger', bounced: 'bg-danger', expired: 'bg-danger', dropped: 'bg-danger'};
|
||||
|
||||
$('#archive_log').DataTable({
|
||||
responsive: true,
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
stateSave: true,
|
||||
pageLength: log_pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
"tr" +
|
||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||
language: lang_datatables,
|
||||
order: [[0, 'desc']],
|
||||
ajax: {
|
||||
type: "GET",
|
||||
url: "/api/v1/get/logs/archive",
|
||||
dataSrc: function(data){
|
||||
return Array.isArray(data) ? data : [];
|
||||
}
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
title: lang.time,
|
||||
data: 'time',
|
||||
defaultContent: '',
|
||||
createdCell: function(td, cellData) {
|
||||
createSortableDate(td, cellData)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: lang.archive_delivery,
|
||||
data: 'status',
|
||||
defaultContent: '',
|
||||
render: function(data) {
|
||||
var label = lang['archive_delivery_' + data] || data;
|
||||
return '<span class="badge ' + (status_class[data] || 'bg-secondary') + '">' + escapeHtml(label) + '</span>';
|
||||
}
|
||||
},
|
||||
{
|
||||
title: lang.archive_message_id,
|
||||
data: 'message_id',
|
||||
defaultContent: '',
|
||||
className: 'text-break',
|
||||
render: function(data) {
|
||||
return escapeHtml(data);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: lang.archive_queue_id,
|
||||
data: 'qid',
|
||||
defaultContent: '',
|
||||
render: function(data) {
|
||||
return escapeHtml(data);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: lang.archive_details,
|
||||
data: 'text',
|
||||
defaultContent: '',
|
||||
className: 'dtr-col-md text-break',
|
||||
render: function(data) {
|
||||
return escapeHtml(data);
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
function rspamd_pie_graph() {
|
||||
$.ajax({
|
||||
url: '/api/v1/get/rspamd/actions',
|
||||
@@ -1295,6 +1370,7 @@ jQuery(function($){
|
||||
onVisible("[id^=sasl_logs]", () => draw_sasl_logs());
|
||||
onVisible("[id^=netfilter_log]", () => draw_netfilter_logs());
|
||||
onVisible("[id^=cron_log]", () => draw_cron_logs());
|
||||
onVisible("[id^=archive_log]", () => draw_archive_logs());
|
||||
onVisible("[id^=rspamd_history]", () => draw_rspamd_history());
|
||||
onVisible("[id^=rspamd_donut]", () => rspamd_pie_graph());
|
||||
|
||||
|
||||
@@ -232,6 +232,7 @@ $(document).ready(function() {
|
||||
} else {
|
||||
$('#addDomain_gal').prop('checked', false);
|
||||
}
|
||||
$('#addDomain_archive').prop('checked', template.archive == 1);
|
||||
|
||||
if (template.active == 1){
|
||||
$('#addDomain_active').prop('checked', true);
|
||||
@@ -433,6 +434,7 @@ $(document).ready(function() {
|
||||
} else {
|
||||
$('#force_tfa').prop('checked', false);
|
||||
}
|
||||
$('#addMailbox_archive').selectpicker('val', ['0', '1'].includes(String(template.archive)) ? String(template.archive) : 'inherit');
|
||||
if (template.sogo_access == 1){
|
||||
$('#sogo_access').prop('checked', true);
|
||||
} else {
|
||||
@@ -1187,6 +1189,18 @@ jQuery(function($){
|
||||
return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':(0==data?'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>':2==data&&'—');
|
||||
}
|
||||
},
|
||||
{
|
||||
title: lang.archive,
|
||||
data: 'archive',
|
||||
searchable: false,
|
||||
defaultContent: '',
|
||||
responsivePriority: 5,
|
||||
// not shown while archiving is switched off
|
||||
className: archive_enabled ? '' : 'never',
|
||||
render: function (data, type) {
|
||||
return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
|
||||
}
|
||||
},
|
||||
{
|
||||
title: lang.action,
|
||||
data: 'action',
|
||||
|
||||
Reference in new issue
Block a user