Files
cloudron-box/dashboard/src/views/emails.js
T
Girish Ramakrishnan 7a5e990ad4 email: rewrite loading of email status using async
we start a bunch of requests in the background for each domain. when
we switch views immediately, to say the eventlog, these requests are
still active in the background.

canceling the requests will require a much bigger refactor.

https://forum.cloudron.io/topic/10434/email-event-log-loading-very-slowly-seems-tied-to-overall-email-domain-list-health-checks
2024-01-09 17:34:54 +01:00

522 lines
17 KiB
JavaScript

'use strict';
/* global $, angular, TASK_TYPES */
/* global async */
angular.module('Application').controller('EmailsController', ['$scope', '$location', '$translate', '$timeout', 'Client', function ($scope, $location, $translate, $timeout, Client) {
Client.onReady(function () { if (!Client.getUserInfo().isAtLeastMailManager) $location.path('/'); });
$scope.ready = false;
$scope.config = Client.getConfig();
$scope.user = Client.getUserInfo();
$scope.domains = [];
$scope.mailLocation = {
busy: false,
percent: 0,
message: '',
errorMessage: '',
currentLocation: { domain: null, subdomain: '' },
domain: null,
subdomain: '',
tasks: [],
refreshTasks: function () {
Client.getTasksByType(TASK_TYPES.TASK_CHANGE_MAIL_LOCATION, function (error, tasks) {
if (error) return console.error(error);
$scope.mailLocation.tasks = tasks.slice(0, 10);
if ($scope.mailLocation.tasks.length && $scope.mailLocation.tasks[0].active) $scope.mailLocation.updateStatus();
});
},
stop: function () {
Client.stopTask($scope.mailLocation.tasks[0].id, function (error) {
if (error) console.error(error);
$scope.mailLocation.busy = false;
$scope.mailLocation.refreshTasks();
});
},
refresh: function () {
Client.getMailLocation(function (error, location) {
if (error) return console.error('Failed to get max email location', error);
$scope.mailLocation.currentLocation.subdomain = $scope.mailLocation.subdomain = location.subdomain;
$scope.mailLocation.currentLocation.domain = $scope.mailLocation.domain = $scope.domains.find(function (d) { return location.domain === d.domain; });
$scope.mailLocation.refreshTasks();
});
},
updateStatus: function () {
var taskId = $scope.mailLocation.tasks[0].id;
Client.getTask(taskId, function (error, data) {
if (error) return window.setTimeout($scope.mailLocation.updateStatus, 5000);
if (!data.active) {
$scope.mailLocation.busy = false;
$scope.mailLocation.message = '';
$scope.mailLocation.percent = 100;
$scope.mailLocation.errorMessage = data.success ? '' : data.error.message;
$scope.mailLocation.refreshTasks(); // update the tasks list dropdown
return;
}
$scope.mailLocation.busy = true;
$scope.mailLocation.percent = data.percent;
$scope.mailLocation.message = data.message;
window.setTimeout($scope.mailLocation.updateStatus, 1000);
});
},
change: function () {
$scope.mailLocation.busy = true;
$scope.mailLocation.percent = 0;
$scope.mailLocation.message = '';
$scope.mailLocation.errorMessage = '';
Client.setMailLocation($scope.mailLocation.subdomain, $scope.mailLocation.domain.domain, function (error, result) {
if (error) {
console.error(error);
$scope.mailLocation.errorMessage = error.message;
$scope.mailLocation.busy = false;
} else {
$scope.mailLocation.refreshTasks();
}
Client.refreshConfig(); // update config.mailFqdn
});
}
};
$scope.maxEmailSize = {
busy: false,
error: null,
size: 0,
currentSize: 0,
refresh: function () {
Client.getMaxEmailSize(function (error, size) {
if (error) return console.error('Failed to get max email size', error);
$scope.maxEmailSize.currentSize = size;
});
},
show: function() {
$scope.maxEmailSize.busy = false;
$scope.maxEmailSize.error = null;
$scope.maxEmailSize.size = $scope.maxEmailSize.currentSize;
$scope.maxEmailSizeChangeForm.$setUntouched();
$scope.maxEmailSizeChangeForm.$setPristine();
$('#maxEmailSizeChangeModal').modal('show');
},
submit: function () {
$scope.maxEmailSize.busy = true;
Client.setMaxEmailSize($scope.maxEmailSize.size, function (error) {
$scope.maxEmailSize.busy = false;
if (error) return console.error(error);
$scope.maxEmailSize.currentSize = $scope.maxEmailSize.size;
$('#maxEmailSizeChangeModal').modal('hide');
});
}
};
$scope.virtualAllMail = {
busy: false,
error: null,
enabled: false,
refresh: function () {
Client.getVirtualAllMail(function (error, enabled) {
if (error) return console.error('Failed to get max email size', error);
$scope.virtualAllMail.enabled = enabled;
});
},
show: function() {
$scope.virtualAllMail.busy = false;
$scope.virtualAllMail.error = null;
$('#virtualAllMailChangeModal').modal('show');
},
submit: function (enable) {
$scope.virtualAllMail.busy = true;
Client.setVirtualAllMail(enable, function (error) {
$scope.virtualAllMail.busy = false;
if (error) return console.error(error);
$scope.virtualAllMail.enabled = enable;
$('#virtualAllMailChangeModal').modal('hide');
});
}
};
$scope.mailboxSharing = {
busy: false,
error: null,
enabled: null, // null means we have not refreshed yet
refresh: function () {
Client.getMailboxSharing(function (error, enabled) {
if (error) return console.error('Failed to get mailbox sharing', error);
$scope.mailboxSharing.enabled = enabled;
});
},
submit: function () {
$scope.mailboxSharing.busy = true;
Client.setMailboxSharing(!$scope.mailboxSharing.enabled, function (error) {
// give sometime for mail server to restart
$timeout(function () {
$scope.mailboxSharing.busy = false;
if (error) return console.error(error);
$scope.mailboxSharing.enabled = !$scope.mailboxSharing.enabled;
}, 3000);
});
}
};
$scope.solrConfig = {
busy: false,
error: {},
currentConfig: null, // null means not loaded yet
enabled: false,
running: false,
enoughMemory: false,
refresh: function () {
Client.getService('mail', function (error, result) {
if (error) return console.log('Error getting status of mail conatiner', error);
$scope.solrConfig.enoughMemory = result.config.memoryLimit > (1024*1024*1024*2);
$scope.solrConfig.running = result.healthcheck && result.healthcheck.solr.status;
Client.getSolrConfig(function (error, config) {
if (error) return console.error('Failed to get solr config', error);
$scope.solrConfig.currentConfig = config;
});
});
},
show: function() {
$scope.solrConfig.busy = false;
$scope.solrConfig.error = null;
$scope.solrConfig.enabled = $scope.solrConfig.currentConfig.enabled;
$('#solrConfigModal').modal('show');
},
submit: function (newState) {
$scope.solrConfig.busy = true;
Client.setSolrConfig(newState, function (error) {
if (error) return console.error(error);
$timeout(function () {
$scope.solrConfig.busy = false;
// FIXME: these values are fake. but cannot get current status from mail server since it might be restarting
$scope.solrConfig.currentConfig.enabled = newState;
$scope.solrConfig.running = newState;
$timeout(function () { $scope.solrConfig.refresh(); }, 20000); // get real values after 20 seconds
$('#solrConfigModal').modal('hide');
}, 5000);
});
}
};
$scope.spamConfig = {
busy: false,
error: {},
acl: { whitelist: [], blacklist: [] },
customConfig: '',
config: '',
blacklist: '', // currently, we don't support whitelist because it requires user to understand a bit more of what he is doing
refresh: function () {
Client.getSpamCustomConfig(function (error, config) {
if (error) return console.error('Failed to get custom spam config', error);
$scope.spamConfig.customConfig = config;
});
Client.getSpamAcl(function (error, acl) {
if (error) return console.error('Failed to get spam acl', error);
$scope.spamConfig.acl = acl;
});
},
show: function() {
$scope.spamConfig.busy = false;
$scope.spamConfig.error = {};
$scope.spamConfig.blacklist = $scope.spamConfig.acl.blacklist.join('\n');
$scope.spamConfig.config = $scope.spamConfig.customConfig;
$scope.spamConfigChangeForm.$setUntouched();
$scope.spamConfigChangeForm.$setPristine();
$('#spamConfigChangeModal').modal('show');
},
submit: function () {
$scope.spamConfig.busy = true;
$scope.spamConfig.error = {};
var blacklist = $scope.spamConfig.blacklist.split('\n').filter(function (l) { return l !== ''; });
Client.setSpamAcl({ blacklist: blacklist, whitelist: [] }, function (error) {
if (error) {
$scope.spamConfig.busy = false;
$scope.spamConfig.error.blacklist = error.message;
$scope.spamConfigChangeForm.blacklist.$setPristine();
return;
}
Client.setSpamCustomConfig($scope.spamConfig.config, function (error) {
if (error) {
$scope.spamConfig.busy = false;
$scope.spamConfig.error.config = error.message;
$scope.spamConfigChangeForm.config.$setPristine();
return;
}
$scope.spamConfig.busy = false;
$scope.spamConfig.refresh();
$('#spamConfigChangeModal').modal('hide');
});
});
}
},
$scope.acl = {
busy: false,
error: {},
dnsblZones: '',
dnsblZonesCount: 0,
refresh: function () {
Client.getDnsblConfig(function (error, result) {
if (error) return console.error('Failed to get email acl', error);
$scope.acl.dnsblZones = result.zones.join('\n');
$scope.acl.dnsblZonesCount = result.zones.length;
});
},
show: function() {
$scope.acl.busy = false;
$scope.acl.error = {};
$scope.aclChangeForm.$setUntouched();
$scope.aclChangeForm.$setPristine();
$('#aclChangeModal').modal('show');
},
submit: function () {
$scope.acl.busy = true;
$scope.acl.error = {};
var zones = $scope.acl.dnsblZones.split('\n').filter(function (l) { return l !== ''; });
Client.setDnsblConfig(zones, function (error) {
if (error) {
$scope.acl.busy = false;
$scope.acl.error.dnsblZones = error.message;
$scope.aclChangeForm.dnsblZones.$setPristine();
return;
}
$scope.acl.busy = false;
$scope.acl.refresh();
$('#aclChangeModal').modal('hide');
});
}
},
$scope.testEmail = {
busy: false,
error: {},
mailTo: '',
domain: null,
clearForm: function () {
$scope.testEmail.mailTo = '';
},
show: function (domain) {
$scope.testEmail.error = {};
$scope.testEmail.busy = false;
$scope.testEmail.domain = domain;
$scope.testEmail.mailTo = $scope.user.email;
$('#testEmailModal').modal('show');
},
submit: function () {
$scope.testEmail.error = {};
$scope.testEmail.busy = true;
Client.sendTestMail($scope.testEmail.domain.domain, $scope.testEmail.mailTo, function (error) {
$scope.testEmail.busy = false;
if (error) {
$scope.testEmail.error.generic = error.message;
console.error(error);
$('#inputTestMailTo').focus();
return;
}
$('#testEmailModal').modal('hide');
});
}
};
function refreshMailStatus(domain, done) {
Client.getMailStatusForDomain(domain.domain, function (error, result) {
if (error) {
console.error('Failed to fetch mail status for domain', domain.domain, error);
return done();
}
domain.status = result;
domain.statusOk = Object.keys(result).every(function (k) {
if (k === 'dns') return Object.keys(result.dns).every(function (k) { return result.dns[k].status; });
if (!('status' in result[k])) return true; // if status is not present, the test was not run
return result[k].status;
});
done();
});
}
function refreshMailConfig(domain, done) {
Client.getMailConfigForDomain(domain.domain, function (error, mailConfig) {
if (error) {
console.error('Failed to fetch mail config for domain', domain.domain, error);
return done();
}
domain.inbound = mailConfig.enabled;
domain.outbound = mailConfig.relay.provider !== 'noop';
// do this even if no outbound since people forget to remove mailboxes
Client.getMailboxCount(domain.domain, function (error, count) {
if (error) {
console.error('Failed to fetch mailboxes for domain', domain.domain, error);
return done();
}
domain.mailboxCount = count;
done();
});
});
}
function refreshMailUsage(domain, done) {
Client.getMailUsage(domain.domain, function (error, usage) {
if (error) {
console.error('Failed to fetch usage for domain', domain.domain, error);
return done();
}
domain.usage = 0;
// we used to use quotaValue here but it's quite different wrt diskSize. so choose diskSize consistently
// also, quotaValue can be missing for deleted mailboxes that are on disk but removed from dovecot/ldap itself
Object.keys(usage).forEach(function (m) { domain.usage += usage[m].diskSize; });
done();
});
}
function refreshDomainStatuses() {
async.each($scope.domains, function (domain, iteratorDone) {
async.series([
refreshMailStatus.bind(null, domain),
refreshMailConfig.bind(null, domain),
], function () {
domain.loading = false;
iteratorDone();
});
}, function () {
// mail usage is loaded separately with a cancellation check. when there are a lot of domains, it runs a long time in background and slows down loading of new views
async.eachLimit($scope.domains, 5, function (domain, itemDone) {
if ($scope.$$destroyed) return itemDone(); // abort!
refreshMailUsage(domain, function () {
domain.loadingUsage = false;
itemDone();
});
});
});
}
Client.onReady(function () {
Client.getDomains(function (error, domains) {
if (error) return console.error('Unable to get domain listing.', error);
domains.forEach(function (domain) { domain.loading = true; domain.loadingUsage = true; }); // used by ui to show 'loading'
$scope.domains = domains;
$scope.ready = true;
if ($scope.user.isAtLeastAdmin) {
$scope.mailLocation.refresh();
$scope.maxEmailSize.refresh();
$scope.virtualAllMail.refresh();
$scope.mailboxSharing.refresh();
$scope.spamConfig.refresh();
$scope.solrConfig.refresh();
$scope.acl.refresh();
}
refreshDomainStatuses();
});
});
// setup all the dialog focus handling
['testEmailModal'].forEach(function (id) {
$('#' + id).on('shown.bs.modal', function () {
$(this).find('[autofocus]:first').focus();
});
});
$('.modal-backdrop').remove();
}]);