'use strict'; /* global angular:false */ /* global $:false */ angular.module('Application').controller('EmailController', ['$scope', '$location', '$timeout', '$rootScope', 'Client', function ($scope, $location, $timeout, $rootScope, Client) { Client.onReady(function () { if (!Client.getUserInfo().admin) $location.path('/'); }); $scope.ready = false; $scope.refreshBusy = true; $scope.client = Client; $scope.user = Client.getUserInfo(); $scope.config = Client.getConfig(); $scope.apps = Client.getInstalledApps(); $scope.domains = []; $scope.users = []; $scope.selectedDomain = null; $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.showView = function (view) { // wait for dialog to be fully closed to avoid modal behavior breakage when moving to a different view already $('.modal').on('hidden.bs.modal', function () { $('.modal').off('hidden.bs.modal'); $location.path(view); }); $('.modal').modal('hide'); }; $scope.usesTokenAuth = function (provider) { return provider === 'postmark-smtp' || provider === 'sendgrid-smtp'; }; $scope.usesExternalServer = function (provider) { return provider !== 'cloudron-smtp' && provider !== 'noop'; }; $scope.usesPasswordAuth = function (provider) { return provider !== 'postmark-smtp' && provider !== 'sendgrid-smtp'; }; $scope.catchall = { mailboxes: [], busy: false, submit: function () { $scope.catchall.busy = true; var addresses = $scope.catchall.mailboxes.map(function (m) { return m.name; }); Client.setCatchallAddresses($scope.selectedDomain.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 () { $scope.catchall.mailboxes = $scope.selectedDomain.mailConfig.catchAll.map(function (name) { return $scope.mailboxes.mailboxes.find(function (m) { return m.name === name; }); }).filter(function (m) { return !!m; }); } }; $scope.mailinglists = { busy: false, mailinglists: [], add: { busy: false, error: null, name: '', members: [], reset: function () { $scope.mailinglists.add.busy = false; $scope.mailinglists.add.error = null; $scope.mailinglists.add.name = ''; $scope.mailinglists.add.members = []; }, show: function () { $scope.mailinglists.add.reset(); $('#mailinglistAddModal').modal('show'); }, submit: function () { $scope.mailinglists.add.busy = true; var members = $scope.mailinglists.add.members.map(function (m) { return m.name; }); Client.addMailingList($scope.selectedDomain.domain, $scope.mailinglists.add.name, members, function (error) { if (error) { $scope.mailinglists.add.busy = false; $scope.mailinglists.add.error = error; return; } $scope.mailinglists.add.reset(); $scope.mailinglists.refresh(); $('#mailinglistAddModal').modal('hide'); }); } }, edit: { busy: false, error: null, name: '', members: [], show: function (list) { $scope.mailinglists.edit.name = list.name; var members = list.members.map(function (name) { return $scope.mailboxes.mailboxes.find(function (m) { return m.name === name; }); }); // A mailinglist may contain mailbox names, which do not exist, so remove them here $scope.mailinglists.edit.members = members.filter(function (m) { return !!m; }); $('#mailinglistEditModal').modal('show'); }, submit: function () { $scope.mailinglists.edit.busy = true; var members = $scope.mailinglists.edit.members.map(function (m) { return m.name; }); Client.updateMailingList($scope.selectedDomain.domain, $scope.mailinglists.edit.name, members, function (error) { $scope.mailinglists.edit.busy = false; if (error) return console.error(error); $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.selectedDomain.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.selectedDomain.domain, function (error, result) { if (error) return callback(error); $scope.mailinglists.mailinglists = result; callback(); }); } }; $scope.toggleMailFromValidation = function () { Client.setMailFromValidation($scope.selectedDomain.domain, !$scope.selectedDomain.mailConfig.mailFromValidation, function (error) { if (error) return console.error(error); $scope.refreshDomain(); }); }; $scope.incomingEmail = { busy: false, toggleEmailEnabled: function () { if ($scope.selectedDomain.mailConfig.enabled) { $('#disableEmailModal').modal('show'); } else { $('#enableEmailModal').modal('show'); } }, enable: function () { $('#enableEmailModal').modal('hide'); $scope.incomingEmail.busy = true; Client.enableMailForDomain($scope.selectedDomain.domain, true , function (error) { if (error) return console.error(error); $scope.reconfigureEmailApps(); Client.setDnsRecords($scope.selectedDomain.domain, function (error) { if (error) return console.error(error); $scope.refreshDomain(); $scope.incomingEmail.busy = false; }); }); }, disable: function () { $('#disableEmailModal').modal('hide'); $scope.incomingEmail.busy = true; Client.enableMailForDomain($scope.selectedDomain.domain, false , function (error) { if (error) return console.error(error); $scope.reconfigureEmailApps(); $scope.refreshDomain(); $scope.incomingEmail.busy = false; }); } }; $scope.mailboxes = { mailboxes: [], 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.selectedDomain.domain, $scope.mailboxes.add.name, $scope.mailboxes.add.owner.id, function (error) { if (error) { $scope.mailboxes.add.busy = false; $scope.mailboxes.add.error = error; return; } $scope.mailboxes.add.reset(); $scope.mailboxes.refresh(function (error) { if (error) return console.error(error); $scope.catchall.refresh(); $('#mailboxAddModal').modal('hide'); }); }); } }, edit: { busy: false, error: null, name: '', owner: null, aliases: '', 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 = mailbox.aliases; $('#mailboxEditModal').modal('show'); }, submit: function () { $scope.mailboxes.edit.busy = true; // $scope.mailboxes.edit.owner is expected to be validated by the UI Client.updateMailbox($scope.selectedDomain.domain, $scope.mailboxes.edit.name, $scope.mailboxes.edit.owner.id, function (error) { if (error) { $scope.mailboxes.edit.error = error; $scope.mailboxes.edit.busy = false; return; } var aliases = $scope.mailboxes.edit.aliases.split(',').map(function (a) { return a.trim(); }).filter(function (a) { return !!a; }); Client.setAliases($scope.selectedDomain.domain, $scope.mailboxes.edit.name, aliases, function (error) { if (error) { $scope.mailboxes.edit.error = error; $scope.mailboxes.edit.busy = false; return; } $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.refresh(); $('#mailboxEditModal').modal('hide'); }); }); } }, remove: { busy: false, mailbox: null, show: function (mailbox) { $scope.mailboxes.remove.mailbox = mailbox; $('#mailboxRemoveModal').modal('show'); }, submit: function () { $scope.mailboxes.remove.busy = true; Client.removeMailbox($scope.selectedDomain.domain, $scope.mailboxes.remove.mailbox.name, 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.getMailboxes($scope.selectedDomain.domain, function (error, mailboxes) { if (error) return callback(error); Client.listAliases($scope.selectedDomain.domain, function (error, aliases) { if (error) return callback(error); $scope.mailboxes.mailboxes = mailboxes.map(function (m) { m.aliases = aliases.filter(function (a) { return a.aliasTarget === m.name; }).map(function (a) { return a.name; }).join(','); m.owner = $scope.users.find(function (u) { return u.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 return m; }); callback(); }); }); } }; $scope.mailRelayPresets = [ { provider: 'cloudron-smtp', name: 'Built-in SMTP server' }, { provider: 'external-smtp', name: 'External SMTP server', host: '', port: 587 }, { provider: 'ses-smtp', name: 'Amazon SES', host: 'email-smtp.us-east-1.amazonaws.com', port: 587 }, { provider: 'google-smtp', name: 'Google', host: 'smtp.gmail.com', port: 587 }, { provider: 'mailgun-smtp', name: 'Mailgun', host: 'smtp.mailgun.org', port: 587 }, { provider: 'mailjet-smtp', name: 'Mailjet', host: '', port: 587 }, { provider: 'postmark-smtp', name: 'Postmark', host: 'smtp.postmarkapp.com', port: 587 }, { provider: 'sendgrid-smtp', name: 'SendGrid', host: 'smtp.sendgrid.net', port: 587, username: 'apikey' }, { provider: 'noop', name: 'Disable' }, ]; $scope.mailRelay = { error: null, success: false, busy: false, preset: $scope.mailRelayPresets[0], 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 = ''; }, // form data to be set on load relay: { provider: 'cloudron-smtp', host: '', port: 25, username: '', password: '', serverApiToken: '' }, 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 }; // fill in provider specific username/password usage if (data.provider === 'postmark-smtp') { data.username = $scope.mailRelay.relay.serverApiToken; data.password = $scope.mailRelay.relay.serverApiToken; } else if (data.provider === 'sendgrid-smtp') { data.username = 'apikey'; data.password = $scope.mailRelay.relay.serverApiToken; } else { data.username = $scope.mailRelay.relay.username; data.password = $scope.mailRelay.relay.password; } Client.setMailRelay($scope.selectedDomain.domain, data, function (error) { $scope.mailRelay.busy = false; if (error) { $scope.mailRelay.error = error.message; return; } $scope.selectedDomain.relay = data; $scope.mailRelay.success = true; $scope.refreshDomain(); // clear success indicator after 3sec $timeout(function () { $scope.mailRelay.success = false; }, 3000); }); } }; $scope.testEmail = { busy: false, error: {}, mailTo: '', domain: null, clearForm: function () { $scope.testEmail.mailTo = ''; }, show: function () { $scope.testEmail.error = {}; $scope.testEmail.busy = false; $scope.testEmail.domain = $scope.selectedDomain; $scope.testEmail.mailTo = $scope.user.email; $('#testEmailModal').modal('show'); }, submit: function () { $scope.testEmail.error = {}; $scope.testEmail.busy = true; Client.sendTestMail($scope.selectedDomain.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 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.selectedDomain.mailStatus.dns[type] || {}; if (!$scope.expectedDnsRecords[type].status) { $('#collapse_dns_' + type).collapse('show'); } }); if (!$scope.selectedDomain.mailStatus.relay.status) { $('#collapse_outbound_smtp').collapse('show'); } if (!$scope.selectedDomain.mailStatus.rbl.status) { $('#collapse_rbl').collapse('show'); } } $scope.selectDomain = function () { $location.path('/email/' + $scope.selectedDomain.domain, false); }; // this is required because we need to rewrite the MAIL_DOMAINS 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.configureApp(installedApps[i].id, { }, function (error) { if (error) console.error(error); }); } }; $scope.refreshDomain = function () { $scope.refreshBusy = true; resetDnsRecords(); Client.getMailConfigForDomain($scope.selectedDomain.domain, function (error, mailConfig) { 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.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 { $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; } } // amend to selected domain to be available for the UI $scope.selectedDomain.mailConfig = mailConfig; $scope.selectedDomain.mailStatus = {}; $scope.mailboxes.refresh(function (error) { if (error) console.error(error); $scope.mailinglists.refresh(); $scope.catchall.refresh(); }); // we will fetch the status without blocking the ui Client.getMailStatusForDomain($scope.selectedDomain.domain, function (error, mailStatus) { $scope.refreshBusy = false; if (error) return console.error(error); $scope.selectedDomain.mailStatus = mailStatus; showExpectedDnsRecords(); }); }); }; $scope.refreshStatus = function () { $scope.refreshBusy = true; Client.getMailStatusForDomain($scope.selectedDomain.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.selectedDomain.mailStatus = mailStatus; showExpectedDnsRecords(); $scope.refreshBusy = false; }); }; Client.onReady(function () { var domain = $location.path().slice('/email/'.length); Client.getUsers(function (error, users) { if (error) return console.error('Unable to get user listing.', error); // ensure we have a display value available $scope.users = users.map(function (u) { u.display = u.username || u.email; return u; }); $scope.users = users; Client.getDomains(function (error, domains) { if (error) return console.error('Unable to get domain listing.', error); $scope.domains = domains; $scope.selectedDomain = domains.find(function (d) { return d.domain === domain; }); if (!$scope.selectedDomain) { $location.path('/email/' + domains[0].domain, false); } else { $scope.refreshDomain(); } $scope.ready = true; }); }); }); function hashChangeListener() { // prevent the change listener to handle the change if it does not belong to this view. The eventhandler will be subsequently removed if (!$scope.ready || !$location.path().startsWith('/email/')) return; // event listener is called from DOM not angular, need to use $apply $scope.$apply(function () { var domain = $location.path().slice('/email/'.length); $scope.selectedDomain = $scope.domains.find(function (d) { return d.domain === domain; }); if (!$scope.selectedDomain) { $location.path('/email/' + $scope.domains[0].domain, false); } else { $scope.refreshDomain(); } }); } window.addEventListener('hashchange', hashChangeListener); $scope.$on('$destroy', function handler() { window.removeEventListener('hashchange', hashChangeListener); }); // setup all the dialog focus handling ['testEmailModal', 'mailboxAddModal', 'mailboxEditModal', 'mailinglistEditModal', 'mailinglistAddModal'].forEach(function (id) { $('#' + id).on('shown.bs.modal', function () { $(this).find("[autofocus]:first").focus(); }); }); $('.modal-backdrop').remove(); }]);