diff --git a/CHANGES b/CHANGES index e590ba0f6..6a8590856 100644 --- a/CHANGES +++ b/CHANGES @@ -2730,4 +2730,5 @@ * ldap: fix error messages to show proper error messages in the external LDAP connector * dashboard: fix various UI elements hidden for admin user * directoryserver: fix totp validation +* email: improve loading of the mail usage to not block other views from loading diff --git a/dashboard/src/views/emails.html b/dashboard/src/views/emails.html index bd111598d..637528acb 100644 --- a/dashboard/src/views/emails.html +++ b/dashboard/src/views/emails.html @@ -195,10 +195,20 @@ - {{ 'main.loadingPlaceholder' | tr }} ... - {{ 'emails.domains.stats' | tr:{ mailboxCount: domain.mailboxCount, usage: (domain.usage | prettyDecimalSize) } }} - {{ 'emails.domains.outbound' | tr }} - {{ 'emails.domains.disabled' | tr }} + + {{ 'main.loadingPlaceholder' | tr }} ... + + + + {{ 'emails.domains.stats' | tr:{ mailboxCount: domain.mailboxCount } }} {{ 'main.loadingPlaceholder' | tr }} ... + {{ 'emails.domains.stats' | tr:{ mailboxCount: domain.mailboxCount, usage: (domain.usage | prettyDecimalSize) } }} + + + {{ 'emails.domains.outbound' | tr }} + {{ 'emails.domains.disabled' | tr }} + + + diff --git a/dashboard/src/views/emails.js b/dashboard/src/views/emails.js index ff507efb5..f9238463c 100644 --- a/dashboard/src/views/emails.js +++ b/dashboard/src/views/emails.js @@ -1,6 +1,7 @@ '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('/'); }); @@ -404,44 +405,83 @@ angular.module('Application').controller('EmailsController', ['$scope', '$locati } }; - function refreshDomainStatuses() { - $scope.domains.forEach(function (domain) { - domain.usage = null; // used by ui to show 'loading' + 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(); + } - Client.getMailStatusForDomain(domain.domain, function (error, result) { - if (error) return console.error('Failed to fetch mail status for domain', domain.domain, error); + domain.status = result; - 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; }); - 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 - if (!('status' in result[k])) return true; // if status is not present, the test was not run - - return result[k].status; - }); + return result[k].status; }); - Client.getMailConfigForDomain(domain.domain, function (error, mailConfig) { - if (error) return console.error('Failed to fetch mail config for domain', domain.domain, error); + done(); + }); + } - domain.inbound = mailConfig.enabled; - domain.outbound = mailConfig.relay.provider !== 'noop'; + 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(); + } - // do this even if no outbound since people forget to remove mailboxes - Client.getMailboxCount(domain.domain, function (error, count) { - if (error) return console.error('Failed to fetch mailboxes for domain', domain.domain, error); + domain.inbound = mailConfig.enabled; + domain.outbound = mailConfig.relay.provider !== 'noop'; - domain.mailboxCount = count; + // 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(); + } - Client.getMailUsage(domain.domain, function (error, usage) { - if (error) return console.error('Failed to fetch usage for domain', domain.domain, error); + domain.mailboxCount = count; - 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 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(); }); }); }); @@ -451,7 +491,9 @@ angular.module('Application').controller('EmailsController', ['$scope', '$locati 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) {