'use strict'; /* global angular */ /* global $ */ /* global async */ angular.module('Application').controller('EmailController', ['$scope', '$location', '$translate', '$timeout', '$route', '$routeParams', 'Client', function ($scope, $location, $translate, $timeout, $route, $routeParams, Client) { Client.onReady(function () { if (!Client.getUserInfo().isAtLeastMailManager) $location.path('/'); }); $scope.user = Client.getUserInfo(); // Avoid full reload on path change // https://stackoverflow.com/a/22614334 // reloadOnUrl: false in $routeProvider did not work! var lastRoute = $route.current; $scope.$on('$locationChangeSuccess', function (/* event */) { if (lastRoute.$$route.originalPath === $route.current.$$route.originalPath) { $route.current = lastRoute; } }); var domainName = $routeParams.domain; if (!domainName) return $location.path('/email'); $scope.setView = function (view, setAlways) { if (!setAlways && !$scope.ready) return; if ($scope.view === view) return; $route.updateParams({ view: view }); $scope.view = view; $scope.activeTab = view; }; $scope.ready = false; $scope.refreshBusy = true; $scope.client = Client; $scope.user = Client.getUserInfo(); $scope.config = Client.getConfig(); $scope.apps = Client.getInstalledApps(); $scope.owners = []; // users + groups $scope.incomingDomains = []; $scope.domain = null; $scope.adminDomain = null; $scope.diskUsage = {}; $scope.storageQuotaTicks = [ 1*1024*1024*1024, 15*1024*1024*1024, 50*1024*1024*1024, 100*1024*1024*1024 ]; $scope.expectedDnsRecords = { mx: { }, dkim: { }, spf: { }, dmarc: { }, ptr: { } }; $scope.expectedDnsRecordsTypes = [ { name: 'MX', value: 'mx' }, { name: 'DKIM', value: 'dkim' }, { name: 'SPF', value: 'spf' }, { name: 'DMARC', value: 'dmarc' }, { name: 'PTR', value: 'ptr' } ]; $scope.openSubscriptionSetup = function () { Client.openSubscriptionSetup($scope.$parent.subscription); }; $scope.catchall = { mailboxes: [], availableMailboxes: [], busy: false, submit: function () { $scope.catchall.busy = true; var addresses = $scope.catchall.mailboxes.map(function (m) { return m.name; }); Client.setCatchallAddresses($scope.domain.domain, addresses, function (error) { if (error) console.error('Unable to add catchall address.', error); $timeout(function () { $scope.catchall.busy = false; }, 2000); // otherwise, it's too fast }); }, refresh: function () { Client.listMailboxes($scope.domain.domain, '', 1, 1000, function (error, result) { if (error) return console.error(error); $scope.catchall.availableMailboxes = result; $scope.catchall.mailboxes = $scope.domain.mailConfig.catchAll.map(function (name) { return $scope.catchall.availableMailboxes.find(function (m) { return m.name === name; }); }).filter(function (m) { return !!m; }); }); } }; $scope.mailinglists = { busy: false, mailinglists: [], search: '', currentPage: 1, perPage: 10, add: { busy: false, error: {}, name: '', membersTxt: '', membersOnly: false, reset: function () { $scope.mailinglists.add.busy = false; $scope.mailinglists.add.error = {}; $scope.mailinglists.add.name = ''; $scope.mailinglists.add.membersTxt = ''; }, show: function () { $scope.mailinglists.add.reset(); $('#mailinglistAddModal').modal('show'); }, submit: function () { $scope.mailinglists.add.busy = true; var members = $scope.mailinglists.add.membersTxt .split(/[\n,]/) .map(function (m) { return m.trim(); }) .filter(function (m) { return m.length !== 0; }); Client.addMailingList($scope.domain.domain, $scope.mailinglists.add.name, members, $scope.mailinglists.add.membersOnly, function (error) { $scope.mailinglists.add.busy = false; $scope.mailinglists.add.error = {}; if (error) { if (error.statusCode === 400 && error.message.indexOf('member') !== -1) { $scope.mailinglists.add.error.members = error.message; } else { $scope.mailinglists.add.error.name = error.message; } return; } $scope.mailinglists.add.reset(); $scope.mailinglists.refresh(); $('#mailinglistAddModal').modal('hide'); }); } }, edit: { busy: false, error: {}, name: '', membersTxt: '', membersOnly: false, active: true, show: function (list) { $scope.mailinglists.edit.name = list.name; $scope.mailinglists.edit.membersTxt = list.members.sort().join('\n'); $scope.mailinglists.edit.membersOnly = list.membersOnly; $scope.mailinglists.edit.active = list.active; $('#mailinglistEditModal').modal('show'); }, submit: function () { $scope.mailinglists.edit.busy = true; var members = $scope.mailinglists.edit.membersTxt.split(/[\n,]/) .map(function (m) { return m.trim(); }) .filter(function (m) { return m.length !== 0; }); Client.updateMailingList($scope.domain.domain, $scope.mailinglists.edit.name, members, $scope.mailinglists.edit.membersOnly, $scope.mailinglists.edit.active, function (error) { $scope.mailinglists.edit.busy = false; $scope.mailinglists.edit.error = {}; if (error) { $scope.mailinglists.edit.error.members = error.message; return; } $scope.mailinglists.refresh(); $('#mailinglistEditModal').modal('hide'); }); } }, remove: { busy: false, list: null, show: function (list) { $scope.mailinglists.remove.list = list; $('#mailinglistRemoveModal').modal('show'); }, submit: function () { $scope.mailinglists.remove.busy = true; Client.removeMailingList($scope.domain.domain, $scope.mailinglists.remove.list.name, function (error) { $scope.mailinglists.remove.busy = false; if (error) return console.error(error); $scope.mailinglists.remove.list = null; $scope.mailinglists.refresh(); $('#mailinglistRemoveModal').modal('hide'); }); } }, refresh: function (callback) { callback = typeof callback === 'function' ? callback : function (error) { if (error) return console.error(error); }; Client.listMailingLists($scope.domain.domain, $scope.mailinglists.search, $scope.mailinglists.currentPage, $scope.mailinglists.perPage, function (error, result) { if (error) return callback(error); $scope.mailinglists.mailinglists = result; callback(); }); }, showNextPage: function () { $scope.mailinglists.currentPage++; $scope.mailinglists.refresh(); }, showPrevPage: function () { if ($scope.mailinglists.currentPage > 1) $scope.mailinglists.currentPage--; else $scope.mailinglists.currentPage = 1; $scope.mailinglists.refresh(); }, updateFilter: function (fresh) { if (fresh) $scope.mailinglists.currentPage = 1; $scope.mailinglists.refresh(); } }; $scope.mailFromValidation = { busy: false, submit: function () { $scope.mailFromValidation.busy = true; Client.setMailFromValidation($scope.domain.domain, !$scope.domain.mailConfig.mailFromValidation, function (error) { if (error) { $scope.mailFromValidation.busy = false; return console.error(error); } // give sometime for the mail container to restart $timeout(function () { $scope.mailFromValidation.busy = false; $scope.refreshDomain(); }, 5000); }); } }; $scope.banner = { busy: false, text: '', html: '', submit: function () { $scope.banner.busy = true; Client.setMailBanner($scope.domain.domain, { text: $scope.banner.text, html: $scope.banner.html }, function (error) { if (error) { $scope.banner.busy = false; return console.error(error); } // give sometime for the mail container to restart $timeout(function () { $scope.banner.busy = false; $scope.refreshDomain(); }, 5000); }); } }; $scope.incomingEmail = { busy: false, setupDns: true, setupDnsBusy: false, toggleEmailEnabled: function () { if ($scope.domain.mailConfig.enabled) { $('#disableEmailModal').modal('show'); } else { $('#enableEmailModal').modal('show'); } }, setDnsRecords: function (callback) { $scope.incomingEmail.setupDnsBusy = true; Client.setDnsRecords({ domain: $scope.domain.domain, type: 'mail' }, function (error) { if (error) console.error(error); $timeout(function () { $scope.incomingEmail.setupDnsBusy = false; }, 2000); // otherwise, it's too fast if (callback) callback(); }); }, enable: function () { $('#enableEmailModal').modal('hide'); $scope.incomingEmail.busy = true; Client.enableMailForDomain($scope.domain.domain, true , function (error) { if (error) return console.error(error); $scope.reconfigureEmailApps(); let maybeSetupDns = $scope.incomingEmail.setupDns ? $scope.incomingEmail.setDnsRecords : function (next) { next(); }; maybeSetupDns(function (error) { if (error) return console.error(error); $timeout(function () { $scope.refreshDomain(); $scope.incomingEmail.busy = false; }, 5000); // wait for mail container to restart. it cannot get IP otherwise while refreshing }); }); }, disable: function () { $('#disableEmailModal').modal('hide'); $scope.incomingEmail.busy = true; Client.enableMailForDomain($scope.domain.domain, false , function (error) { if (error) return console.error(error); $scope.reconfigureEmailApps(); $timeout(function () { $scope.refreshDomain(); $scope.incomingEmail.busy = false; }, 5000); // wait for mail container to restart. it cannot get IP otherwise while refreshing }); } }; $scope.mailboxImport = { busy: false, done: false, error: null, percent: 0, success: 0, mailboxes: [], reset: function () { $scope.mailboxImport.busy = false; $scope.mailboxImport.error = null; $scope.mailboxImport.mailboxes = []; $scope.mailboxImport.percent = 0; $scope.mailboxImport.success = 0; $scope.mailboxImport.done = false; }, handleFileChanged: function () { $scope.mailboxImport.reset(); var fileInput = document.getElementById('mailboxImportFileInput'); if (!fileInput.files || !fileInput.files[0]) return; var file = fileInput.files[0]; if (file.type !== 'application/json' && file.type !== 'text/csv') return console.log('Unsupported file type.'); const reader = new FileReader(); reader.addEventListener('load', function () { $scope.$apply(function () { $scope.mailboxImport.mailboxes = []; var mailboxes = []; if (file.type === 'text/csv') { var lines = reader.result.split('\n'); if (lines.length === 0) return $scope.mailboxImport.error = { file: 'Imported file has no lines' }; for (var i = 0; i < lines.length; i++) { var line = lines[i].trim(); if (!line) continue; var items = line.split(','); if (items.length !== 4) { $scope.mailboxImport.error = { file: 'Line ' + (i+1) + ' has wrong column count. Expecting 4' }; return; } mailboxes.push({ name: items[0].trim(), domain: items[1].trim(), owner: items[2].trim(), ownerType: items[3].trim(), }); } } else { try { mailboxes = JSON.parse(reader.result).map(function (mailbox) { return { name: mailbox.name, domain: mailbox.domain, owner: mailbox.owner, ownerType: mailbox.ownerType }; }); } catch (e) { console.error('Failed to parse mailboxes.', e); $scope.mailboxImport.error = { file: 'Imported file is not valid JSON' }; } } $scope.mailboxImport.mailboxes = mailboxes; }); }, false); reader.readAsText(file); }, show: function () { $scope.mailboxImport.reset(); // named so no duplactes document.getElementById('mailboxImportFileInput').addEventListener('change', $scope.mailboxImport.handleFileChanged); $('#mailboxImportModal').modal('show'); }, openFileInput: function () { $('#mailboxImportFileInput').click(); }, import: function () { $scope.mailboxImport.percent = 0; $scope.mailboxImport.success = 0; $scope.mailboxImport.done = false; $scope.mailboxImport.error = { import: [] }; $scope.mailboxImport.busy = true; var processed = 0; async.eachSeries($scope.mailboxImport.mailboxes, function (mailbox, callback) { var owner = $scope.owners.find(function (o) { return o.display === mailbox.owner && o.type === mailbox.ownerType; }); // owner may not exist if (!owner) { $scope.mailboxImport.error.import.push({ error: new Error('Could not detect owner'), mailbox: mailbox }); ++processed; $scope.mailboxImport.percent = 100 * processed / $scope.mailboxImport.mailboxes.length; return callback(); } Client.addMailbox(mailbox.domain, mailbox.name, owner.id, mailbox.ownerType, function (error) { if (error) $scope.mailboxImport.error.import.push({ error: error, mailbox: mailbox }); else ++$scope.mailboxImport.success; ++processed; $scope.mailboxImport.percent = 100 * processed / $scope.mailboxImport.mailboxes.length; callback(); }); }, function (error) { if (error) return console.error(error); $scope.mailboxImport.busy = false; $scope.mailboxImport.done = true; if ($scope.mailboxImport.success) $scope.mailboxes.refresh(); }); } }; $scope.mailboxExport = function (type) { // FIXME only does first 10k mailboxes Client.listMailboxes($scope.domain.domain, '', 1, 10000, function (error, result) { if (error) { Client.error('Failed to list mailboxes. Full error in the webinspector.'); return console.error('Failed to list mailboxes.', error); } var content = ''; if (type === 'json') { content = JSON.stringify(result.map(function (mailbox) { var owner = $scope.owners.find(function (o) { return o.id === mailbox.ownerId; }); // owner may not exist return { name: mailbox.name, domain: mailbox.domain, owner: owner ? owner.display : '', // this meta property is set when we get the user list ownerType: owner ? owner.type : '', active: mailbox.active, aliases: mailbox.aliases }; }), null, 2); } else if (type === 'csv') { content = result.map(function (mailbox) { var owner = $scope.owners.find(function (o) { return o.id === mailbox.ownerId; }); // owner may not exist var aliases = mailbox.aliases.map(function (a) { return a.name + '@' + a.domain; }).join(' '); return [ mailbox.name, mailbox.domain, owner ? owner.display : '', owner ? owner.type : '', aliases, mailbox.active ].join(','); }).join('\n'); } else { return; } var file = new Blob([ content ], { type: type === 'json' ? 'application/json' : 'text/csv' }); var a = document.createElement('a'); a.href = URL.createObjectURL(file); a.download = $scope.domain.domain.replaceAll('.','_') + '-mailboxes.' + type; document.body.appendChild(a); a.click(); }); }; $scope.mailboxes = { mailboxes: [], search: '', currentPage: 1, perPage: 10, add: { error: null, busy: false, name: '', owner: null, reset: function () { $scope.mailboxes.add.busy = false; $scope.mailboxes.add.error = null; $scope.mailboxes.add.name = ''; $scope.mailboxes.add.owner = null; }, show: function () { $scope.mailboxes.add.reset(); $('#mailboxAddModal').modal('show'); }, submit: function () { $scope.mailboxes.add.busy = true; Client.addMailbox($scope.domain.domain, $scope.mailboxes.add.name, $scope.mailboxes.add.owner.id, $scope.mailboxes.add.owner.type, function (error) { if (error) { $scope.mailboxes.add.busy = false; $scope.mailboxes.add.error = error; return; } $scope.mailboxes.refresh(); $scope.catchall.refresh(); $('#mailboxAddModal').modal('hide'); }); } }, edit: { busy: false, error: null, name: '', owner: null, incomingDomains: [], aliases: [], aliasesOriginal: [], active: true, enablePop3: false, storageQuota: 0, storageQuotaEnabled: false, addAlias: function (event) { event.preventDefault(); $scope.mailboxes.edit.aliases.push({ name: '', domain: domainName, reversedSortingNotation: 'z'.repeat(100) // quick and dirty to ensure newly added are on bottom }); }, delAlias: function (event, alias) { event.preventDefault(); var index = $scope.mailboxes.edit.aliases.findIndex(function (a) { return (a.name+a.domain) === (alias.name+alias.domain); }); if (index === -1) return; $scope.mailboxes.edit.aliases.splice(index, 1); }, show: function (mailbox) { $scope.mailboxes.edit.name = mailbox.name; $scope.mailboxes.edit.owner = mailbox.owner; // this can be null if mailbox had no owner $scope.mailboxes.edit.aliases = angular.copy(mailbox.aliases, []).map(function (a) { a.reversedSortingNotation = a.domain + '@' + a.name; return a; }); $scope.mailboxes.edit.active = mailbox.active; $scope.mailboxes.edit.enablePop3 = mailbox.enablePop3; $scope.mailboxes.edit.storageQuotaEnabled = !!mailbox.storageQuota; $scope.mailboxes.edit.storageQuota = mailbox.storageQuota || $scope.storageQuotaTicks[0]; // make a copy for later comparison angular.copy($scope.mailboxes.edit.aliases, $scope.mailboxes.edit.aliasesOriginal); $('#mailboxEditModal').modal('show'); }, submit: function () { $scope.mailboxes.edit.busy = true; var data = { ownerId: $scope.mailboxes.edit.owner.id, ownerType: $scope.mailboxes.edit.owner.type, active: $scope.mailboxes.edit.active, enablePop3: $scope.mailboxes.edit.enablePop3, storageQuota: $scope.mailboxes.edit.storageQuotaEnabled ? $scope.mailboxes.edit.storageQuota : 0, messagesQuota: 0 }; // $scope.mailboxes.edit.owner is expected to be validated by the UI Client.updateMailbox($scope.domain.domain, $scope.mailboxes.edit.name, data, function (error) { if (error) { $scope.mailboxes.edit.error = error; $scope.mailboxes.edit.busy = false; return; } function done() { $scope.diskUsage[$scope.mailboxes.edit.name + '@' + $scope.domain.domain].quotaLimit = $scope.mailboxes.edit.storageQuotaEnabled ? $scope.mailboxes.edit.storageQuota/1024 : 0; // hack to avoid refresh $scope.mailboxes.edit.busy = false; $scope.mailboxes.edit.error = null; $scope.mailboxes.edit.name = ''; $scope.mailboxes.edit.owner = null; $scope.mailboxes.edit.aliases = []; $scope.mailboxes.edit.storageQuota = 0; $scope.mailboxes.edit.storageQuotaEnabled = false; $scope.mailboxes.refresh(); $('#mailboxEditModal').modal('hide'); } // skip if nothing has changed if (angular.equals($scope.mailboxes.edit.aliases, $scope.mailboxes.edit.aliasesOriginal)) return done(); Client.setAliases($scope.mailboxes.edit.name, $scope.domain.domain, $scope.mailboxes.edit.aliases, function (error) { if (error) { $scope.mailboxes.edit.error = error; $scope.mailboxes.edit.busy = false; return; } done(); }); }); } }, remove: { busy: false, mailbox: null, deleteMails: true, show: function (mailbox) { $scope.mailboxes.remove.mailbox = mailbox; $('#mailboxRemoveModal').modal('show'); }, submit: function () { $scope.mailboxes.remove.busy = true; Client.removeMailbox($scope.domain.domain, $scope.mailboxes.remove.mailbox.name, $scope.mailboxes.remove.deleteMails, function (error) { $scope.mailboxes.remove.busy = false; if (error) return console.error(error); $scope.mailboxes.remove.mailbox = null; $scope.mailboxes.refresh(function (error) { if (error) return console.error(error); $scope.catchall.refresh(); $('#mailboxRemoveModal').modal('hide'); }); }); } }, refresh: function (callback) { callback = typeof callback === 'function' ? callback : function (error) { if (error) return console.error(error); }; Client.listMailboxes($scope.domain.domain, $scope.mailboxes.search, $scope.mailboxes.currentPage, $scope.mailboxes.perPage, function (error, mailboxes) { if (error) return callback(error); mailboxes.forEach(function (m) { m.owner = $scope.owners.find(function (o) { return o.id === m.ownerId; }); // owner may not exist m.ownerDisplayName = m.owner ? m.owner.display : ''; // this meta property is set when we get the user list var u = $scope.diskUsage[m.name + '@' + m.domain]; // this is unset when no emails have been received yet m.usage = (u && u.quotaValue * 1024) || 0; m.limit = (u && u.quotaLimit * 1024) || 0; }); $scope.mailboxes.mailboxes = mailboxes; callback(); }); }, showNextPage: function () { $scope.mailboxes.currentPage++; $scope.mailboxes.refresh(); }, showPrevPage: function () { if ($scope.mailboxes.currentPage > 1) $scope.mailboxes.currentPage--; else $scope.mailboxes.currentPage = 1; $scope.mailboxes.refresh(); }, updateFilter: function (fresh) { if (fresh) $scope.mailboxes.currentPage = 1; $scope.mailboxes.refresh(); } }; $scope.mailRelayPresets = [ { provider: 'cloudron-smtp', name: 'Built-in SMTP server' }, { provider: 'external-smtp', name: 'External SMTP server', host: '', port: 587 }, { provider: 'external-smtp-noauth', name: 'External SMTP server (No Authentication)', host: '', port: 587 }, { provider: 'ses-smtp', name: 'Amazon SES', host: 'email-smtp.us-east-1.amazonaws.com', port: 587, spfDoc: 'https://docs.aws.amazon.com/ses/latest/DeveloperGuide/spf.html' }, { provider: 'elasticemail-smtp', name: 'Elastic Email', host: 'smtp.elasticemail.com', port: 587, spfDoc: 'https://elasticemail.com/blog/marketing_tips/common-spf-errors' }, { provider: 'google-smtp', name: 'Google', host: 'smtp.gmail.com', port: 587, spfDoc: 'https://support.google.com/a/answer/33786?hl=en' }, { provider: 'mailgun-smtp', name: 'Mailgun', host: 'smtp.mailgun.org', port: 587, spfDoc: 'https://www.mailgun.com/blog/white-labeling-dns-records-your-customers-tips-tricks' }, { provider: 'mailjet-smtp', name: 'Mailjet', host: '', port: 587, spfDoc: 'https://app.mailjet.com/docs/spf-dkim-guide' }, { provider: 'postmark-smtp', name: 'Postmark', host: 'smtp.postmarkapp.com', port: 587, spfDoc: 'https://postmarkapp.com/support/article/1092-how-do-i-set-up-spf-for-postmark' }, { provider: 'sendgrid-smtp', name: 'SendGrid', host: 'smtp.sendgrid.net', port: 587, username: 'apikey', spfDoc: 'https://sendgrid.com/docs/ui/account-and-settings/spf-records/' }, { provider: 'sparkpost-smtp', name: 'SparkPost', host: 'smtp.sparkpostmail.com', port: 587, username: 'SMTP_Injection', spfDoc: 'https://www.sparkpost.com/resources/email-explained/spf-sender-policy-framework/' }, { provider: 'noop', name: 'Disable' }, ]; $scope.usesTokenAuth = function (provider) { return provider === 'postmark-smtp' || provider === 'sendgrid-smtp' || provider === 'sparkpost-smtp'; }; $scope.usesExternalServer = function (provider) { return provider !== 'cloudron-smtp' && provider !== 'noop'; }; $scope.usesPasswordAuth = function (provider) { return provider === 'external-smtp' || provider === 'ses-smtp' || provider === 'google-smtp' || provider === 'mailgun-smtp' || provider === 'elasticemail-smtp' || provider === 'mailjet-smtp'; }; $scope.mailRelay = { error: null, success: false, busy: false, preset: $scope.mailRelayPresets[0], // form data to be set on load relay: { provider: 'cloudron-smtp', host: '', port: 25, username: '', password: '', serverApiToken: '', acceptSelfSignedCerts: false }, presetChanged: function () { $scope.mailRelay.error = null; $scope.mailRelay.relay.provider = $scope.mailRelay.preset.provider; $scope.mailRelay.relay.host = $scope.mailRelay.preset.host; $scope.mailRelay.relay.port = $scope.mailRelay.preset.port; $scope.mailRelay.relay.username = ''; $scope.mailRelay.relay.password = ''; $scope.mailRelay.relay.serverApiToken = ''; $scope.mailRelay.relay.acceptSelfSignedCerts = false; }, submit: function () { $scope.mailRelay.error = null; $scope.mailRelay.busy = true; $scope.mailRelay.success = false; var data = { provider: $scope.mailRelay.relay.provider, host: $scope.mailRelay.relay.host, port: $scope.mailRelay.relay.port, acceptSelfSignedCerts: $scope.mailRelay.relay.acceptSelfSignedCerts, forceFromAddress: false }; // fill in provider specific username/password usage if (data.provider === 'postmark-smtp') { data.username = $scope.mailRelay.relay.serverApiToken; data.password = $scope.mailRelay.relay.serverApiToken; data.forceFromAddress = true; // postmark requires the "From:" in mail to be a Sender Signature } else if (data.provider === 'sendgrid-smtp') { data.username = 'apikey'; data.password = $scope.mailRelay.relay.serverApiToken; } else if (data.provider === 'sparkpost-smtp') { data.username = 'SMTP_Injection'; data.password = $scope.mailRelay.relay.serverApiToken; } else { data.username = $scope.mailRelay.relay.username; data.password = $scope.mailRelay.relay.password; } Client.setMailRelay($scope.domain.domain, data, function (error) { if (error) { $scope.mailRelay.error = error.message; $scope.mailRelay.busy = false; return; } $scope.domain.relay = data; // let the mail server restart, otherwise we get "Error getting IP" errors during refresh $timeout(function () { $scope.mailRelay.busy = false; $scope.mailRelay.success = true; $scope.refreshDomain(); // clear success indicator after 5sec $timeout(function () { $scope.mailRelay.success = false; }, 5000); }, 5000); }); } }; function resetDnsRecords() { $scope.expectedDnsRecordsTypes.forEach(function (record) { var type = record.value; $scope.expectedDnsRecords[type] = {}; $('#collapse_dns_' + type).collapse('hide'); }); $('#collapse_outbound_smtp').collapse('hide'); $('#collapse_rbl').collapse('hide'); } function showExpectedDnsRecords() { // open the record details if they are not correct $scope.expectedDnsRecordsTypes.forEach(function (record) { var type = record.value; $scope.expectedDnsRecords[type] = $scope.domain.mailStatus.dns[type] || {}; if (!$scope.expectedDnsRecords[type].status) { $('#collapse_dns_' + type).collapse('show'); } }); if (!$scope.domain.mailStatus.relay.status) { $('#collapse_outbound_smtp').collapse('show'); } if (!$scope.domain.mailStatus.rbl.status) { $('#collapse_rbl').collapse('show'); } } $scope.selectDomain = function () { $location.path('/email/' + $scope.domain.domain, false); }; // this is required because we need to rewrite the CLOUDRON_MAIL_SERVER_HOST env var $scope.reconfigureEmailApps = function () { var installedApps = Client.getInstalledApps(); for (var i = 0; i < installedApps.length; i++) { if (!installedApps[i].manifest.addons.email) continue; Client.repairApp(installedApps[i].id, { }, function (error) { if (error) console.error(error); }); } }; $scope.refreshDomain = function () { $scope.refreshBusy = true; resetDnsRecords(); Client.getMailConfigForDomain(domainName, function (error, mailConfig) { if (error && error.statusCode === 404) return $location.path('/email'); if (error) { $scope.refreshBusy = false; return console.error(error); } // pre-fill the form $scope.mailRelay.relay.provider = mailConfig.relay.provider; $scope.mailRelay.relay.host = mailConfig.relay.host; $scope.mailRelay.relay.port = mailConfig.relay.port; $scope.mailRelay.relay.acceptSelfSignedCerts = !!mailConfig.relay.acceptSelfSignedCerts; $scope.mailRelay.relay.username = ''; $scope.mailRelay.relay.password = ''; $scope.mailRelay.relay.serverApiToken = ''; if (mailConfig.relay.provider === 'postmark-smtp') { $scope.mailRelay.relay.serverApiToken = mailConfig.relay.username; } else if (mailConfig.relay.provider === 'sendgrid-smtp') { $scope.mailRelay.relay.serverApiToken = mailConfig.relay.password; } else if (mailConfig.relay.provider === 'sparkpost-smtp') { $scope.mailRelay.relay.serverApiToken = mailConfig.relay.password; } else { $scope.mailRelay.relay.username = mailConfig.relay.username; $scope.mailRelay.relay.password = mailConfig.relay.password; } for (var i = 0; i < $scope.mailRelayPresets.length; i++) { if ($scope.mailRelayPresets[i].provider === mailConfig.relay.provider) { $scope.mailRelay.preset = $scope.mailRelayPresets[i]; break; } } $scope.banner.text = mailConfig.banner.text || ''; $scope.banner.html = mailConfig.banner.html || ''; // amend to selected domain to be available for the UI $scope.domain.mailConfig = mailConfig; $scope.domain.mailStatus = {}; Client.getMailUsage($scope.domain.domain, function (error, usage) { if (error) console.error(error); $scope.diskUsage = usage || {}; // if mail server is down, don't stop the listing $scope.mailboxes.refresh(); // relies on disk usage $scope.mailinglists.refresh(); $scope.catchall.refresh(); }); // we will fetch the status without blocking the ui Client.getMailStatusForDomain($scope.domain.domain, function (error, mailStatus) { $scope.refreshBusy = false; if (error) return console.error(error); $scope.domain.mailStatus = mailStatus; showExpectedDnsRecords(); }); }); }; $scope.refreshStatus = function () { $scope.refreshBusy = true; Client.getMailStatusForDomain($scope.domain.domain, function (error, mailStatus) { if (error) { $scope.refreshBusy = false; return console.error(error); } // overwrite the selected domain status to be available for the UI $scope.domain.mailStatus = mailStatus; showExpectedDnsRecords(); $scope.refreshBusy = false; }); }; $scope.howToConnectInfo = { show: function () { $('#howToConnectInfoModal').modal('show'); } }; Client.onReady(function () { $scope.isAdminDomain = $scope.config.adminDomain === domainName; Client.getAllUsers(function (error, users) { if (error) return console.error('Unable to get user listing.', error); // ensure we have a display value available $scope.owners.push({ header: true, display: $translate.instant('email.mailboxboxDialog.usersHeader') }); users.forEach(function (u) { $scope.owners.push({ display: u.username || u.email, id: u.id, type: 'user' }); }); Client.getGroups(function (error, groups) { if (error) return console.error('Unable to get group listing.', error); $scope.owners.push({ header: true, display: $translate.instant('email.mailboxboxDialog.groupsHeader') }); groups.forEach(function (g) { $scope.owners.push({ display: g.name, id: g.id, type: 'group' }); }); $scope.owners.push({ header: true, display: $translate.instant('email.mailboxboxDialog.appsHeader') }); Client.getInstalledApps().forEach(function (a) { if (a.manifest.addons && a.manifest.addons.recvmail) $scope.owners.push({ display: a.label || a.fqdn, id: a.id, type: 'app' }); }); Client.getDomains(function (error, result) { if (error) return console.error('Unable to list domains.', error); $scope.domain = result.filter(function (d) { return d.domain === domainName; })[0]; $scope.adminDomain = result.filter(function (d) { return d.domain === $scope.config.adminDomain; })[0]; $scope.refreshDomain(); async.eachSeries(result, function (domain, iteratorDone) { Client.getMailConfigForDomain(domain.domain, function (error, mailConfig) { if (error) return console.error('Failed to fetch mail config for domain', domain.domain, error); if (mailConfig.enabled) $scope.incomingDomains.push(domain); iteratorDone(); }); }, function iteratorDone(error) { if (error) return console.error(error); $scope.setView($routeParams.view || 'mailboxes', true /* always set */); $scope.ready = true; }); }); }); }); }); // setup all the dialog focus handling ['mailboxAddModal', 'mailboxEditModal', 'mailinglistEditModal', 'mailinglistAddModal'].forEach(function (id) { $('#' + id).on('shown.bs.modal', function () { $(this).find('[autofocus]:first').focus(); }); }); $('.modal-backdrop').remove(); }]);