// IMAP sync jobs and sync sources (admin/domainadmin mailbox page, user page, edit page).
// Loaded via $js_minifier only on pages that show sync jobs.
var mailcowImapsync = (function($) {
'use strict';
// Lang strings: lang.syncjobs (mailbox/user page) or lang_syncjobs (edit page), '' if missing
function L(key) {
var src = (typeof lang_syncjobs !== 'undefined' && lang_syncjobs) ? lang_syncjobs :
((typeof lang !== 'undefined' && lang && lang.syncjobs) ? lang.syncjobs : {});
return (src[key] !== undefined && src[key] !== null) ? src[key] : '';
}
function esc(s) {
return escapeHtml(s === null || s === undefined ? '' : s);
}
// same format as "last run" (YYYY-MM-DD HH:mm:ss), independent of the browser locale
function formatDate(ts) {
if (!ts) return '';
var d = new Date(ts * 1000);
var p = function(n) { return (n < 10 ? '0' : '') + n; };
return d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate()) + ' ' +
p(d.getHours()) + ':' + p(d.getMinutes()) + ':' + p(d.getSeconds());
}
function isAdmin() {
return typeof mailcow_cc_role !== 'undefined' && mailcow_cc_role === 'admin';
}
// bootstrap-select: refresh an existing instance, or init with the mailcow defaults
function refreshSelect($sel) {
if (typeof $sel.selectpicker !== 'function') return;
$sel.each(function() {
var $s = $(this);
if ($s.data('selectpicker')) {
$s.selectpicker('refresh');
} else {
$s.selectpicker({
'styleBase': 'btn btn-xs-lg',
'noneSelectedText': (typeof lang_footer !== 'undefined') ? lang_footer.nothing_selected : ''
});
}
});
}
// Long source labels must not overflow the modal on small screens
function injectStyle() {
if ($('#imapsync-style').length) return;
$('head').append('');
}
// ===== Display helpers (tables) =====
// Visibility label of a source row
function scopeDisplay(item) {
if (item.scope === 'all') return ' ' + esc(L('source_scope_all'));
if (item.scope === 'domain') return esc(L('source_scope_domain') + ': ' + (item.domains || []).join(', '));
var us = item.users || [];
if (us.length === 0) return esc(item.created_by || '');
return esc(L('source_scope_user') + ': ' + us.join(', '));
}
// Token badge of a client_credentials source: based on token existence/error, expiry in title
function sourceTokenBadge(item) {
if (item.oauth_last_refresh_error) {
return '' + esc(L('token_state_failed')) + '';
}
if (item.has_access_token) {
var title = item.oauth_token_expires ? L('source_token_status') + ': ' + formatDate(item.oauth_token_expires) : '';
return '' + esc(L('token_state_issued')) + '';
}
return '' + esc(L('token_state_none')) + '';
}
// Auth column of a source row: auth type, and for XOAUTH2 the flow (+ token badge for client credentials)
function authDisplay(item) {
var out = esc(item.auth_type);
if (item.auth_type !== 'XOAUTH2') return out;
var flow = item.oauth_flow || 'client_credentials';
out += ' (' + esc(flow === 'authorization_code' ? L('source_flow_badge_user') : L('source_flow_badge_app')) + ')';
// authorization_code tokens live per job (local mailbox + remote user), not on the source
if (flow === 'authorization_code') return out;
return out + '
' + sourceTokenBadge(item) + '
';
}
// Per-job token badge for authorization_code sources ('' for everything else)
function jobTokenBadge(item) {
if (item.source_auth_type !== 'XOAUTH2' || item.source_oauth_flow !== 'authorization_code') return '';
if (item.user_token_error) {
return '' + esc(L('token_state_failed')) + '';
}
if (parseInt(item.user_token_exists) === 1) {
var title = item.user_token_expires ? L('source_token_status') + ': ' + formatDate(item.user_token_expires) : '';
return '' + esc(L('token_state_issued')) + '';
}
return '' + esc(L('token_state_pending')) + '';
}
// Warning badge for jobs with not migrated legacy custom parameters
function legacyBadge(item) {
if (!item.custom_params_legacy) return '';
return '' +
' ' + esc(L('custom_params_legacy_badge')) + '';
}
// Wrappable table cell content (long hosts, mailboxes, legacy source names)
function wrapCell(html) {
return '
' + html + '
';
}
// Server column: "remote user @ host:port", source name below, badges after
function serverDisplay(item) {
var out = esc(item.user1 || '') + ' @ ' + esc(item.source_host || '?') + ':' + esc(item.source_port || '?');
if (item.source_name) out += '
' + esc(item.source_name) + '
';
var badges = [jobTokenBadge(item), legacyBadge(item)].filter(Boolean);
if (badges.length) out += '
' + badges.join(' ') + '
';
return wrapCell(out);
}
// Prepares the shared fields of a job row (run state, last run, result)
function prepareJobRow(item) {
item.server_w_port = serverDisplay(item);
if (parseInt(item.is_running) === 1) {
item.is_running = '' + esc(L('status_running')) + '';
} else if (parseInt(item.active) !== 1) {
item.is_running = '' + esc(L('status_inactive')) + '';
} else {
item.is_running = '' + esc(L('status_waiting')) + '';
}
if (!item.last_run) {
item.last_run = '' + esc(L('never_run')) + '';
}
if (item.success == null) {
item.success = '-';
item.exit_status = '';
} else {
item.success = '';
}
if (L('syncjob_' + item.exit_status)) {
item.exit_status = L('syncjob_' + item.exit_status);
} else if (item.success != '-') {
item.exit_status = L('syncjob_check_log');
}
item.exit_status = item.success + ' ' + esc(item.exit_status);
return item;
}
// Job tables: ignore DataTables state saved before the rework (old ID sort, column visibility)
var JOB_TABLE_STATE_VERSION = 2;
function jobTableStateSave(settings, data) {
data.imapsync_state = JOB_TABLE_STATE_VERSION;
}
function jobTableStateLoad(settings, data) {
if (!data || data.imapsync_state !== JOB_TABLE_STATE_VERSION) return false;
// column visibility depends on the role, never restore it
if (Array.isArray(data.columns)) {
data.columns.forEach(function(c) { if (c) delete c.visible; });
}
}
// Sync source table (mailbox + user page). canManage: session may add/edit sources at all.
function drawSourceTable(canManage) {
if ($.fn.DataTable.isDataTable('#imapsync_source_table')) {
$('#imapsync_source_table').DataTable().columns.adjust().responsive.recalc();
return;
}
$('#imapsync_source_table').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
pageLength: 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: [[2, 'asc']],
ajax: {
type: "GET",
url: "/api/v1/get/syncjob_source/all",
dataSrc: function(json) {
if (!Array.isArray(json)) return [];
$.each(json, function(i, item) {
item.endpoint = wrapCell(esc((item.host || item.host1) + ':' + (item.port || item.port1) + ' (' + (item.enc || item.enc1) + ')'));
item.name_display = esc(item.name);
if (item.description) item.name_display += '