diff --git a/webadmin/src/index.html b/webadmin/src/index.html index 01b3a0387..0baf2fc77 100644 --- a/webadmin/src/index.html +++ b/webadmin/src/index.html @@ -215,7 +215,7 @@
  • Account
  • Activity
  • API Access
  • -
  • Domain & Certs
  • +
  • Domains
  • Email
  • Graphs
  • Settings
  • diff --git a/webadmin/src/js/index.js b/webadmin/src/js/index.js index 6e9d7f5b3..dd1470158 100644 --- a/webadmin/src/js/index.js +++ b/webadmin/src/js/index.js @@ -55,9 +55,9 @@ app.config(['$routeProvider', function ($routeProvider) { }).when('/debug', { controller: 'DebugController', templateUrl: 'views/debug.html' - }).when('/certs', { - controller: 'CertsController', - templateUrl: 'views/certs.html' + }).when('/domains', { + controller: 'DomainsController', + templateUrl: 'views/domains.html' }).when('/email', { controller: 'EmailController', templateUrl: 'views/email.html' diff --git a/webadmin/src/js/setupdns.js b/webadmin/src/js/setupdns.js index ebc48c9bd..6d555a4fa 100644 --- a/webadmin/src/js/setupdns.js +++ b/webadmin/src/js/setupdns.js @@ -38,7 +38,7 @@ app.controller('SetupDNSController', ['$scope', '$http', 'Client', function ($sc } }); - // keep in sync with certs.js + // keep in sync with domains.js $scope.dnsProvider = [ { name: 'AWS Route53', value: 'route53' }, { name: 'Cloudflare (DNS only)', value: 'cloudflare' }, diff --git a/webadmin/src/views/certs.html b/webadmin/src/views/certs.html deleted file mode 100644 index 3f6376f66..000000000 --- a/webadmin/src/views/certs.html +++ /dev/null @@ -1,260 +0,0 @@ - - -
    -
    -

    Domain & Certificates

    -
    - -
    -

    Domain

    -
    - -
    -
    -
    -

    To use a custom domain, configure your domain to use Route53. Moving to a custom domain will retain all your apps and data and will take around 15 minutes.

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Domain name{{ config.fqdn }}
    DNS provider{{ dnsConfig.provider }}
    -
    - No DNS provider is configured. All DNS records need to be setup manually. - To avoid manual setup for each installed app, set a DNS API provider. -
    -
    - Wildcard DNS provider is configured. Always ensure there is a wildcard DNS record for this server's IP. -
    -
    - No DNS provider configured. All DNS records need to be setup manually and all DNS checks are skipped. -
    Access key id{{ dnsConfig.accessKeyId || 'unset' }}
    Secret access keyhidden
    DigitalOcean tokenhidden

    -
    -
    -
    - -
    -

    SSL Certificates

    -
    - -
    -
    -
    - Certificates can only by set for custom domains. -
    -
    - -
    -
    -
    -
    -

    Certificates are automatically obtained and renewed from Let’s Encrypt. See the current rate limit here.

    -
    - -

    This wildcard certificate will be used for apps, should getting a Let’s Encrypt certificate fail.

    -
    {{ defaultCert.error }}
    -
    Upload successful
    -
    -
    - - - - - -
    -
    -
    -
    - - - - - -
    -
    - -
    -
    -
    -
    -
    -
    -
    -
    - -

    This certificate will be used for this Settings application.

    -
    {{ adminCert.error }}
    -
    Upload successful
    -
    -
    - - - - - -
    -
    -
    -
    - - - - - -
    -
    - -
    -
    -
    -
    -
    -
    diff --git a/webadmin/src/views/certs.js b/webadmin/src/views/certs.js deleted file mode 100644 index af76d8a7b..000000000 --- a/webadmin/src/views/certs.js +++ /dev/null @@ -1,258 +0,0 @@ -'use strict'; - -angular.module('Application').controller('CertsController', ['$scope', '$location', 'Client', 'ngTld', function ($scope, $location, Client, ngTld) { - Client.onReady(function () { if (!Client.getUserInfo().admin) $location.path('/'); }); - - $scope.config = Client.getConfig(); - $scope.dnsConfig = null; - - // keep in sync with setupdns.js - $scope.dnsProvider = [ - { name: 'AWS Route53', value: 'route53' }, - { name: 'Cloudflare (DNS only)', value: 'cloudflare' }, - { name: 'Digital Ocean', value: 'digitalocean' }, - { name: 'Google Cloud DNS', value: 'gcdns' }, - { name: 'Wildcard', value: 'wildcard' }, - { name: 'Manual (not recommended)', value: 'manual' }, - { name: 'No-op (only for development)', value: 'noop' } - ]; - - $scope.defaultCert = { - error: null, - success: false, - busy: false, - certificateFile: null, - certificateFileName: '', - keyFile: null, - keyFileName: '' - }; - - $scope.adminCert = { - error: null, - success: false, - busy: false, - certificateFile: null, - certificateFileName: '', - keyFile: null, - keyFileName: '' - }; - - $scope.dnsCredentials = { - error: null, - success: false, - busy: false, - customDomain: '', - accessKeyId: '', - secretAccessKey: '', - gcdnsKey: { keyFileName: '', content: '' }, - digitalOceanToken: '', - cloudflareToken: '', - cloudflareEmail: '', - provider: 'route53', - password: '' - }; - - function readFileLocally(obj, file, fileName) { - return function (event) { - $scope.$apply(function () { - obj[file] = null; - obj[fileName] = event.target.files[0].name; - - var reader = new FileReader(); - reader.onload = function (result) { - if (!result.target || !result.target.result) return console.error('Unable to read local file'); - obj[file] = result.target.result; - }; - reader.readAsText(event.target.files[0]); - }); - }; - } - - document.getElementById('defaultCertFileInput').onchange = readFileLocally($scope.defaultCert, 'certificateFile', 'certificateFileName'); - document.getElementById('defaultKeyFileInput').onchange = readFileLocally($scope.defaultCert, 'keyFile', 'keyFileName'); - document.getElementById('adminCertFileInput').onchange = readFileLocally($scope.adminCert, 'certificateFile', 'certificateFileName'); - document.getElementById('adminKeyFileInput').onchange = readFileLocally($scope.adminCert, 'keyFile', 'keyFileName'); - - document.getElementById('gcdnsKeyFileInput').onchange = readFileLocally($scope.dnsCredentials.gcdnsKey, 'content', 'keyFileName'); - - $scope.setDefaultCert = function () { - $scope.defaultCert.busy = true; - $scope.defaultCert.error = null; - $scope.defaultCert.success = false; - - Client.setCertificate($scope.defaultCert.certificateFile, $scope.defaultCert.keyFile, function (error) { - if (error) { - $scope.defaultCert.error = error.message; - } else { - $scope.defaultCert.success = true; - $scope.defaultCert.certificateFileName = ''; - $scope.defaultCert.keyFileName = ''; - } - - $scope.defaultCert.busy = false; - }); - }; - - $scope.setAdminCert = function () { - $scope.adminCert.busy = true; - $scope.adminCert.error = null; - $scope.adminCert.success = false; - - Client.setAdminCertificate($scope.adminCert.certificateFile, $scope.adminCert.keyFile, function (error) { - if (error) { - $scope.adminCert.error = error.message; - } else { - $scope.adminCert.success = true; - $scope.adminCert.certificateFileName = ''; - $scope.adminCert.keyFileName = ''; - } - - $scope.adminCert.busy = false; - - // attempt to reload to make the browser get the new certs - window.location.reload(true); - }); - }; - - $scope.setDnsCredentials = function () { - $scope.dnsCredentials.busy = true; - $scope.dnsCredentials.error = null; - $scope.dnsCredentials.success = false; - - var migrateDomain = $scope.dnsCredentials.customDomain !== $scope.config.fqdn; - - var data = { - provider: $scope.dnsCredentials.provider - }; - - // special case the wildcard provider - if (data.provider === 'wildcard') { - data.provider = 'manual'; - data.wildcard = true; - } - - if (data.provider === 'route53') { - data.accessKeyId = $scope.dnsCredentials.accessKeyId; - data.secretAccessKey = $scope.dnsCredentials.secretAccessKey; - } else if (data.provider === 'gcdns'){ - try { - var serviceAccountKey = JSON.parse($scope.dnsCredentials.gcdnsKey.content); - data.projectId = serviceAccountKey.project_id; - data.credentials = { - client_email: serviceAccountKey.client_email, - private_key: serviceAccountKey.private_key - }; - - if (!data.projectId || !data.credentials || !data.credentials.client_email || !data.credentials.private_key) { - throw 'fields_missing'; - } - } catch (e) { - $scope.dnsCredentials.error = 'Cannot parse Google Service Account Key: ' + e.message; - $scope.dnsCredentials.busy = false; - return; - } - } else if (data.provider === 'digitalocean') { - data.token = $scope.dnsCredentials.digitalOceanToken; - } else if (data.provider === 'cloudflare') { - data.token = $scope.dnsCredentials.cloudflareToken; - data.email = $scope.dnsCredentials.cloudflareEmail; - } - - var func; - if (migrateDomain) { - data.domain = $scope.dnsCredentials.customDomain; - func = Client.migrate.bind(Client, data, $scope.dnsCredentials.password); - } else { - func = Client.setDnsConfig.bind(Client, data); - } - - func(function (error) { - if (error) { - $scope.dnsCredentials.error = error.message; - } else { - $scope.dnsCredentials.success = true; - - $('#dnsCredentialsModal').modal('hide'); - - dnsCredentialsReset(); - - if (migrateDomain) window.location.href = '/update.html'; - } - - $scope.dnsCredentials.busy = false; - - // reload the dns config - Client.getDnsConfig(function (error, result) { - if (error) return console.error(error); - - $scope.dnsConfig = result; - }); - }); - }; - - function dnsCredentialsReset() { - $scope.dnsCredentials.busy = false; - $scope.dnsCredentials.success = false; - $scope.dnsCredentials.error = null; - - $scope.dnsCredentials.provider = ''; - $scope.dnsCredentials.customDomain = ''; - $scope.dnsCredentials.accessKeyId = ''; - $scope.dnsCredentials.secretAccessKey = ''; - $scope.dnsCredentials.gcdnsKey.keyFileName = ''; - $scope.dnsCredentials.gcdnsKey.content = ''; - $scope.dnsCredentials.digitalOceanToken = ''; - $scope.dnsCredentials.cloudflareToken = ''; - $scope.dnsCredentials.cloudflareEmail = ''; - $scope.dnsCredentials.password = ''; - - $scope.dnsCredentialsForm.$setPristine(); - $scope.dnsCredentialsForm.$setUntouched(); - - $('#customDomainId').focus(); - } - - $scope.showChangeDnsCredentials = function () { - dnsCredentialsReset(); - - // clear the input box for non-custom domain - $scope.dnsCredentials.customDomain = $scope.config.isCustomDomain ? $scope.config.fqdn : ''; - $scope.dnsCredentials.accessKeyId = $scope.dnsConfig.accessKeyId; - $scope.dnsCredentials.secretAccessKey = $scope.dnsConfig.secretAccessKey; - - $scope.dnsCredentials.gcdnsKey.keyFileName = ''; - $scope.dnsCredentials.gcdnsKey.content = ''; - if ($scope.dnsConfig.provider === 'gcdns') { - $scope.dnsCredentials.gcdnsKey.keyFileName = $scope.dnsConfig.credentials.client_email; - $scope.dnsCredentials.gcdnsKey.content = JSON.stringify({ - "project_id": $scope.dnsConfig.projectId, - "credentials": $scope.dnsConfig.credentials - }); - } - $scope.dnsCredentials.digitalOceanToken = $scope.dnsConfig.provider === 'digitalocean' ? $scope.dnsConfig.token : ''; - $scope.dnsCredentials.cloudflareToken = $scope.dnsConfig.provider === 'cloudflare' ? $scope.dnsConfig.token : ''; - $scope.dnsCredentials.cloudflareEmail = $scope.dnsConfig.email; - - $scope.dnsCredentials.provider = $scope.dnsConfig.provider === 'caas' ? 'route53' : $scope.dnsConfig.provider; - $scope.dnsCredentials.provider = ($scope.dnsCredentials.provider === 'manual' && $scope.dnsConfig.wildcard) ? 'wildcard' : $scope.dnsCredentials.provider; - - $('#dnsCredentialsModal').modal('show'); - }; - - Client.onReady(function () { - Client.getDnsConfig(function (error, result) { - if (error) return console.error(error); - - $scope.dnsConfig = result; - }); - }); - - // setup all the dialog focus handling - ['dnsCredentialsModal'].forEach(function (id) { - $('#' + id).on('shown.bs.modal', function () { - $(this).find("[autofocus]:first").focus(); - }); - }); - - $('.modal-backdrop').remove(); -}]); diff --git a/webadmin/src/views/domains.html b/webadmin/src/views/domains.html new file mode 100644 index 000000000..9ba753688 --- /dev/null +++ b/webadmin/src/views/domains.html @@ -0,0 +1,170 @@ + + + + + +
    +
    +

    Domains

    +
    + +
    +
    +
    +
    +

    +
    +
    +
    +
    + + + + + + + + + + + + + + + +
    DomainActions
    + {{ domain.domain }} + + + +
    +
    +
    +
    +
    +
    diff --git a/webadmin/src/views/domains.js b/webadmin/src/views/domains.js new file mode 100644 index 000000000..e8a89d084 --- /dev/null +++ b/webadmin/src/views/domains.js @@ -0,0 +1,243 @@ +'use strict'; + +angular.module('Application').controller('DomainsController', ['$scope', '$location', 'Client', 'ngTld', function ($scope, $location, Client, ngTld) { + Client.onReady(function () { if (!Client.getUserInfo().admin) $location.path('/'); }); + + $scope.config = Client.getConfig(); + $scope.dnsConfig = null; + $scope.domains = []; + $scope.ready = false; + + // keep in sync with setupdns.js + $scope.dnsProvider = [ + { name: 'AWS Route53', value: 'route53' }, + { name: 'Cloudflare (DNS only)', value: 'cloudflare' }, + { name: 'Digital Ocean', value: 'digitalocean' }, + { name: 'Google Cloud DNS', value: 'gcdns' }, + { name: 'Wildcard', value: 'wildcard' }, + { name: 'Manual (not recommended)', value: 'manual' }, + { name: 'No-op (only for development)', value: 'noop' } + ]; + + function readFileLocally(obj, file, fileName) { + return function (event) { + $scope.$apply(function () { + obj[file] = null; + obj[fileName] = event.target.files[0].name; + + var reader = new FileReader(); + reader.onload = function (result) { + if (!result.target || !result.target.result) return console.error('Unable to read local file'); + obj[file] = result.target.result; + }; + reader.readAsText(event.target.files[0]); + }); + }; + } + + // We reused configure also for adding domains to avoid much code duplication + $scope.domainConfigure = { + adding: false, + error: null, + busy: false, + domain: null, + + // form model + newDomain: '', + accessKeyId: '', + secretAccessKey: '', + gcdnsKey: { keyFileName: '', content: '' }, + digitalOceanToken: '', + cloudflareToken: '', + cloudflareEmail: '', + provider: 'route53', + + show: function (domain) { + $scope.domainConfigure.reset(); + + if (domain) { + $scope.domainConfigure.domain = domain; + $scope.domainConfigure.accessKeyId = domain.config.accessKeyId; + $scope.domainConfigure.secretAccessKey = domain.config.secretAccessKey; + + $scope.domainConfigure.gcdnsKey.keyFileName = ''; + $scope.domainConfigure.gcdnsKey.content = ''; + if ($scope.domainConfigure.provider === 'gcdns') { + $scope.domainConfigure.gcdnsKey.keyFileName = domain.config.credentials.client_email; + $scope.domainConfigure.gcdnsKey.content = JSON.stringify({ + "project_id": domain.config.projectId, + "credentials": domain.config.credentials + }); + } + $scope.domainConfigure.digitalOceanToken = domain.config.provider === 'digitalocean' ? domain.config.token : ''; + $scope.domainConfigure.cloudflareToken = domain.config.provider === 'cloudflare' ? domain.config.token : ''; + $scope.domainConfigure.cloudflareEmail = domain.config.email; + + $scope.domainConfigure.provider = domain.config.provider === 'caas' ? 'route53' : domain.config.provider; + $scope.domainConfigure.provider = ($scope.domainConfigure.provider === 'manual' && domain.config.wildcard) ? 'wildcard' : domain.config.provider; + } else { + $scope.domainConfigure.adding = true; + } + + $('#domainConfigureModal').modal('show'); + }, + + submit: function () { + $scope.domainConfigure.busy = true; + $scope.domainConfigure.error = null; + + var data = { + provider: $scope.domainConfigure.provider + }; + + // special case the wildcard provider + if (data.provider === 'wildcard') { + data.provider = 'manual'; + data.wildcard = true; + } + + if (data.provider === 'route53') { + data.accessKeyId = $scope.domainConfigure.accessKeyId; + data.secretAccessKey = $scope.domainConfigure.secretAccessKey; + } else if (data.provider === 'gcdns'){ + try { + var serviceAccountKey = JSON.parse($scope.domainConfigure.gcdnsKey.content); + data.projectId = serviceAccountKey.project_id; + data.credentials = { + client_email: serviceAccountKey.client_email, + private_key: serviceAccountKey.private_key + }; + + if (!data.projectId || !data.credentials || !data.credentials.client_email || !data.credentials.private_key) { + throw 'fields_missing'; + } + } catch (e) { + $scope.domainConfigure.error = 'Cannot parse Google Service Account Key: ' + e.message; + $scope.domainConfigure.busy = false; + return; + } + } else if (data.provider === 'digitalocean') { + data.token = $scope.domainConfigure.digitalOceanToken; + } else if (data.provider === 'cloudflare') { + data.token = $scope.domainConfigure.cloudflareToken; + data.email = $scope.domainConfigure.cloudflareEmail; + } + + // choose the right api, since we reuse this for adding and configuring domains + var func; + if ($scope.domainConfigure.adding) func = Client.addDomain.bind(Client, $scope.domainConfigure.newDomain, data); + else func = Client.updateDomain.bind(Client, $scope.domainConfigure.domain.domain, data) ; + + func(function (error) { + $scope.domainConfigure.busy = false; + if (error) { + $scope.domainConfigure.error = error.message; + return; + } + + $('#domainConfigureModal').modal('hide'); + $scope.domainConfigure.reset(); + + // reload the domains + Client.getDomains(function (error, result) { + if (error) return console.error(error); + + $scope.domains = result; + }); + }); + }, + + reset: function () { + $scope.domainConfigure.adding = false; + $scope.domainConfigure.newDomain = ''; + + $scope.domainConfigure.busy = false; + $scope.domainConfigure.error = null; + + $scope.domainConfigure.provider = ''; + $scope.domainConfigure.accessKeyId = ''; + $scope.domainConfigure.secretAccessKey = ''; + $scope.domainConfigure.gcdnsKey.keyFileName = ''; + $scope.domainConfigure.gcdnsKey.content = ''; + $scope.domainConfigure.digitalOceanToken = ''; + $scope.domainConfigure.cloudflareToken = ''; + $scope.domainConfigure.cloudflareEmail = ''; + + $scope.domainConfigureForm.$setPristine(); + $scope.domainConfigureForm.$setUntouched(); + } + }; + + $scope.domainRemove = { + busy: false, + error: null, + domain: null, + password: '', + + show: function (domain) { + $scope.domainRemove.reset(); + + $scope.domainRemove.domain = domain; + + $('#domainRemoveModal').modal('show'); + }, + + submit: function () { + $scope.domainRemove.busy = true; + $scope.domainRemove.error = null; + + Client.removeDomain($scope.domainRemove.domain.domain, $scope.domainRemove.password, function (error) { + if (error && error.statusCode === 403) { + $scope.domainRemove.password = ''; + $scope.domainRemove.error = error.message; + $scope.domainRemoveForm.password.$setPristine(); + $('#domainRemovePasswordInput').focus(); + } else if (error) { + Client.error(error); + } else { + $('#domainRemoveModal').modal('hide'); + $scope.domainRemove.reset(); + + // reload the domains + Client.getDomains(function (error, result) { + if (error) return console.error(error); + + $scope.domains = result; + }); + } + + $scope.domainRemove.busy = false; + }); + }, + + reset: function () { + $scope.domainRemove.busy = false; + $scope.domainRemove.error = null; + $scope.domainRemove.domain = null; + $scope.domainRemove.password = ''; + + $scope.domainRemoveForm.$setPristine(); + $scope.domainRemoveForm.$setUntouched(); + } + }; + + Client.onReady(function () { + Client.getDomains(function (error, result) { + if (error) return console.error(error); + + $scope.domains = result; + $scope.ready = true; + }); + }); + + document.getElementById('gcdnsKeyFileInput').onchange = readFileLocally($scope.domainConfigure.gcdnsKey, 'content', 'keyFileName'); + + // setup all the dialog focus handling + ['domainConfigureModal', 'domainRemoveModal'].forEach(function (id) { + $('#' + id).on('shown.bs.modal', function () { + $(this).find("[autofocus]:first").focus(); + }); + }); + + $('.modal-backdrop').remove(); +}]);