'use strict'; /* global $, angular, async */ // keep in sync with box/src/notfications.js const NOTIFICATION_TYPES = { CLOUDRON_INSTALLED: 'cloudronInstalled', CLOUDRON_UPDATED: 'cloudronUpdated', CLOUDRON_UPDATE_FAILED: 'cloudronUpdateFailed', CERTIFICATE_RENEWAL_FAILED: 'certificateRenewalFailed', BACKUP_CONFIG: 'backupConfig', DISK_SPACE: 'diskSpace', MAIL_STATUS: 'mailStatus', REBOOT: 'reboot', BOX_UPDATE: 'boxUpdate', UPDATE_UBUNTU: 'ubuntuUpdate', MANUAL_APP_UPDATE: 'manualAppUpdate', APP_OOM: 'appOutOfMemory', APP_UPDATED: 'appUpdated', BACKUP_FAILED: 'backupFailed', }; // keep in sync with box/src/apps.js var ISTATES = { PENDING_INSTALL: 'pending_install', PENDING_CLONE: 'pending_clone', PENDING_CONFIGURE: 'pending_configure', PENDING_UNINSTALL: 'pending_uninstall', PENDING_RESTORE: 'pending_restore', PENDING_IMPORT: 'pending_import', PENDING_UPDATE: 'pending_update', PENDING_BACKUP: 'pending_backup', PENDING_RECREATE_CONTAINER: 'pending_recreate_container', // env change or addon change PENDING_LOCATION_CHANGE: 'pending_location_change', PENDING_DATA_DIR_MIGRATION: 'pending_data_dir_migration', PENDING_SERVICES_CHANGE: 'pending_services_change', PENDING_RESIZE: 'pending_resize', PENDING_DEBUG: 'pending_debug', PENDING_START: 'pending_start', PENDING_STOP: 'pending_stop', PENDING_RESTART: 'pending_restart', ERROR: 'error', INSTALLED: 'installed' }; var HSTATES = { HEALTHY: 'healthy', UNHEALTHY: 'unhealthy', ERROR: 'error', DEAD: 'dead' }; var RSTATES ={ RUNNING: 'running', STOPPED: 'stopped' }; var ERROR = { ACCESS_DENIED: 'Access Denied', ALREADY_EXISTS: 'Already Exists', BAD_FIELD: 'Bad Field', COLLECTD_ERROR: 'Collectd Error', CONFLICT: 'Conflict', DATABASE_ERROR: 'Database Error', DNS_ERROR: 'DNS Error', DOCKER_ERROR: 'Docker Error', EXTERNAL_ERROR: 'External Error', FS_ERROR: 'FileSystem Error', INTERNAL_ERROR: 'Internal Error', LOGROTATE_ERROR: 'Logrotate Error', NETWORK_ERROR: 'Network Error', NOT_FOUND: 'Not found', REVERSEPROXY_ERROR: 'ReverseProxy Error', TASK_ERROR: 'Task Error', UNKNOWN_ERROR: 'Unknown Error' // only used for portin, }; var ROLES = { OWNER: 'owner', ADMIN: 'admin', MAIL_MANAGER: 'mailmanager', USER_MANAGER: 'usermanager', USER: 'user' }; // sync up with tokens.js const TOKEN_TYPES = { ID_WEBADMIN: 'cid-webadmin', // dashboard ID_DEVELOPMENT: 'cid-development', // dashboard development ID_CLI: 'cid-cli', // cloudron cli ID_SDK: 'cid-sdk', // created by user via dashboard }; // sync up with tasks.js var TASK_TYPES = { TASK_APP: 'app', TASK_BACKUP: 'backup', TASK_UPDATE: 'update', TASK_CHECK_CERTS: 'checkCerts', TASK_PREPARE_DASHBOARD_LOCATION: 'prepareDashboardLocation', TASK_CLEAN_BACKUPS: 'cleanBackups', TASK_SYNC_EXTERNAL_LDAP: 'syncExternalLdap', TASK_CHANGE_MAIL_LOCATION: 'changeMailLocation', TASK_SYNC_DNS_RECORDS: 'syncDnsRecords', TASK_UPDATE_DISK_USAGE: 'updateDiskUsage', TASK_SYNC_DYNDNS: 'syncDyndns', }; const APP_TYPES = { APP: 'app', //default LINK: 'link', PROXIED: 'proxied' }; var SECRET_PLACEHOLDER = String.fromCharCode(0x25CF).repeat(8); // List is from http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region const REGIONS_S3 = [ { name: 'Africa (Cape Town)', value: 'af-south-1' }, { name: 'Asia Pacific (Hong Kong)', value: 'ap-east-1' }, { name: 'Asia Pacific (Hyderabad)', value: 'ap-south-2' }, { name: 'Asia Pacific (Jakarta)', value: 'ap-southeast-3' }, { name: 'Asia Pacific (Melbourne)', value: 'ap-southeast-4' }, { name: 'Asia Pacific (Mumbai)', value: 'ap-south-1' }, { name: 'Asia Pacific (Osaka)', value: 'ap-northeast-3' }, { name: 'Asia Pacific (Seoul)', value: 'ap-northeast-2' }, { name: 'Asia Pacific (Singapore)', value: 'ap-southeast-1' }, { name: 'Asia Pacific (Sydney)', value: 'ap-southeast-2' }, { name: 'Asia Pacific (Tokyo)', value: 'ap-northeast-1' }, { name: 'Canada (Central)', value: 'ca-central-1' }, { name: 'China (Beijing)', value: 'cn-north-1' }, { name: 'China (Ningxia)', value: 'cn-northwest-1' }, { name: 'Middle East (Bahrain)', value: 'me-south-1' }, { name: 'Middle East (UAE)', value: 'me-central-1' }, { name: 'EU (Frankfurt)', value: 'eu-central-1' }, { name: 'EU (Ireland)', value: 'eu-west-1' }, { name: 'EU (London)', value: 'eu-west-2' }, { name: 'EU (Milan)', value: 'eu-south-1' }, { name: 'EU (Paris)', value: 'eu-west-3' }, { name: 'EU (Spain)', value: 'eu-south-2' }, { name: 'EU (Stockholm)', value: 'eu-north-1' }, { name: 'EU (Zurich)', value: 'eu-central-2' }, { name: 'South America (São Paulo)', value: 'sa-east-1' }, { name: 'US East (N. Virginia)', value: 'us-east-1' }, { name: 'US East (Ohio)', value: 'us-east-2' }, { name: 'US West (N. California)', value: 'us-west-1' }, { name: 'US West (Oregon)', value: 'us-west-2' }, ]; // https://wasabi.com/locations/ const REGIONS_WASABI = [ { name: 'Amsterdam (EU Central 1)', value: 'https://s3.eu-central-1.wasabisys.com' }, { name: 'Frankfurt (EU Central 2)', value: 'https://s3.eu-central-2.wasabisys.com' }, { name: 'London (EU West 1)', value: 'https://s3.eu-west-1.wasabisys.com' }, { name: 'Oregon (US West 1)', value: 'https://s3.us-west-1.wasabisys.com' }, { name: 'Osaka (AP Northeast 2)', value: 'https://s3.ap-northeast-2.wasabisys.com' }, { name: 'Paris (EU West 2)', value: 'https://s3.eu-west-2.wasabisys.com' }, { name: 'Plano (US Central 1)', value: 'https://s3.us-central-1.wasabisys.com' }, { name: 'Singapore (AP Southeast 1)', value: 'https://s3.ap-southeast-1.wasabisys.com' }, { name: 'Sydney (AP Southeast 2)', value: 'https://s3.ap-southeast-2.wasabisys.com' }, { name: 'Tokyo (AP Northeast 1)', value: 'https://s3.ap-northeast-1.wasabisys.com' }, { name: 'Toronto (CA Central 1)', value: 'https://s3.ca-central-1.wasabisys.com' }, { name: 'Virginia (US East 1)', value: 'https://s3.us-east-1.wasabisys.com' }, { name: 'Virginia (US East 2)', value: 'https://s3.us-east-2.wasabisys.com' } ]; const REGIONS_HETZNER = [ { name: 'Falkenstein (FSN1)', value: 'https://fsn1.your-objectstorage.com' }, { name: 'Helsinki (HEL1)', value: 'https://hel1.your-objectstorage.com' }, { name: 'Nuremberg (NBG1)', value: 'https://nbg1.your-objectstorage.com' } ]; // https://docs.digitalocean.com/products/platform/availability-matrix/ const REGIONS_DIGITALOCEAN = [ { name: 'AMS3', value: 'https://ams3.digitaloceanspaces.com' }, { name: 'FRA1', value: 'https://fra1.digitaloceanspaces.com' }, { name: 'LON1', value: 'https://lon1.digitaloceanspaces.com' }, { name: 'NYC3', value: 'https://nyc3.digitaloceanspaces.com' }, { name: 'SFO2', value: 'https://sfo2.digitaloceanspaces.com' }, { name: 'SFO3', value: 'https://sfo3.digitaloceanspaces.com' }, { name: 'SGP1', value: 'https://sgp1.digitaloceanspaces.com' }, { name: 'SYD1', value: 'https://syd1.digitaloceanspaces.com' } ]; // https://www.exoscale.com/datacenters/ const REGIONS_EXOSCALE = [ { name: 'Vienna (AT-VIE-1)', value: 'https://sos-at-vie-1.exo.io' }, { name: 'Vienna (AT-VIE-2)', value: 'https://sos-at-vie-2.exo.io' }, { name: 'Sofia (BG-SOF-1)', value: 'https://sos-bg-sof-1.exo.io' }, { name: 'Zurich (CH-DK-2)', value: 'https://sos-ch-dk-2.exo.io' }, { name: 'Geneva (CH-GVA-2)', value: 'https://sos-ch-gva-2.exo.io' }, { name: 'Frankfurt (DE-FRA-1)', value: 'https://sos-de-fra-1.exo.io' }, { name: 'Munich (DE-MUC-1)', value: 'https://sos-de-muc-1.exo.io' }, ]; // https://www.scaleway.com/docs/object-storage-feature/ const REGIONS_SCALEWAY = [ { name: 'Paris (FR-PAR)', value: 'https://s3.fr-par.scw.cloud', region: 'fr-par' }, // default { name: 'Amsterdam (NL-AMS)', value: 'https://s3.nl-ams.scw.cloud', region: 'nl-ams' }, { name: 'Warsaw (PL-WAW)', value: 'https://s3.pl-waw.scw.cloud', region: 'nl-ams' } ]; // https://www.linode.com/docs/products/storage/object-storage/guides/urls/ const REGIONS_LINODE = [ { name: 'Amsterdam', value: 'https://nl-ams-1.linodeobjects.com', region: 'nl-ams-1' }, { name: 'Atlanta', value: 'https://us-southeast-1.linodeobjects.com', region: 'us-southeast-1' }, { name: 'Chennai', value: 'https://in-maa-1.linodeobjects.com', region: 'in-maa-1' }, { name: 'Chicago', value: 'https://us-ord-1.linodeobjects.com', region: 'us-ord-1' }, { name: 'Frankfurt', value: 'https://eu-central-1.linodeobjects.com', region: 'eu-central-1' }, { name: 'Jakarta', value: 'https://id-cgk-1.linodeobjects.com', region: 'id-cgk-1' }, { name: 'Los Angeles, CA (USA)', value: 'https://us-lax-1.linodeobjects.com', region: 'us-lax-1' }, { name: 'Miami', value: 'https://us-mia-1.linodeobjects.com', region: 'us-mia-1' }, { name: 'Milan', value: 'https://it-mil-1.linodeobjects.com', region: 'it-mil-1' }, { name: 'Newark', value: 'https://us-east-1.linodeobjects.com', region: 'us-east-1' }, // default { name: 'Osaka', value: 'https://jp-osa-1.linodeobjects.com', region: 'jp-osa-1' }, { name: 'Paris', value: 'https://fr-par-1.linodeobjects.com', region: 'fr-par-1' }, { name: 'Sao Paulo', value: 'https://br-gru-1.linodeobjects.com', region: 'br-gru-1' }, { name: 'Seattle', value: 'https://us-sea-1.linodeobjects.com', region: 'us-sea-1' }, { name: 'Singapore', value: 'https://ap-south-1.linodeobjects.com', region: 'ap-south-1' }, { name: 'Stockholm', value: 'https://se-sto-1.linodeobjects.com', region: 'se-sto-1' }, { name: 'Washington', value: 'https://us-iad-1.linodeobjects.com', region: 'us-iad-1' }, ]; // note: ovh also has a storage endpoint but that only supports path style access (https://docs.ovh.com/au/en/storage/object-storage/s3/location/) const REGIONS_OVH = [ { name: 'Beauharnois (BHS)', value: 'https://s3.bhs.io.cloud.ovh.net', region: 'bhs' }, // default { name: 'Frankfurt (DE)', value: 'https://s3.de.io.cloud.ovh.net', region: 'de' }, { name: 'Gravelines (GRA)', value: 'https://s3.gra.io.cloud.ovh.net', region: 'gra' }, { name: 'Roubaix (RBX)', value: 'https://s3.rbx.io.cloud.ovh.net', region: 'rbx' }, { name: 'Strasbourg (SBG)', value: 'https://s3.sbg.io.cloud.ovh.net', region: 'sbg' }, { name: 'London (UK)', value: 'https://s3.uk.io.cloud.ovh.net', region: 'uk' }, { name: 'Sydney (SYD)', value: 'https://s3.syd.io.cloud.ovh.net', region: 'syd' }, { name: 'Warsaw (WAW)', value: 'https://s3.waw.io.cloud.ovh.net', region: 'waw' }, ]; const ENDPOINTS_OVH = [ { name: 'OVH Europe', value: 'ovh-eu' }, { name: 'OVH US', value: 'ovh-us' }, { name: 'OVH North-America', value: 'ovh-ca' }, { name: 'SoYouStart Europe', value: 'soyoustart-eu' }, { name: 'SoYouStart North-America', value: 'soyoustart-ca' }, { name: 'Kimsufi Europe', value: 'kimsufi-eu' }, { name: 'Kimsufi North-America', value: 'kimsufi-ca' }, ]; // https://docs.ionos.com/cloud/managed-services/s3-object-storage/endpoints const REGIONS_IONOS = [ { name: 'Berlin (eu-central-3)', value: 'https://s3.eu-central-3.ionoscloud.com', region: 'de' }, // default. contract-owned { name: 'Frankfurt (DE)', value: 'https://s3.eu-central-1.ionoscloud.com', region: 'de' }, { name: 'Berlin (eu-central-2)', value: 'https://s3-eu-central-2.ionoscloud.com', region: 'eu-central-2' }, { name: 'Logrono (eu-south-2)', value: 'https://s3-eu-south-2.ionoscloud.com', region: 'eu-south-2' }, ]; // this is not used anywhere because upcloud needs endpoint URL. we detect region from the URL (https://upcloud.com/data-centres) const REGIONS_UPCLOUD = [ { name: 'AU-SYD1 (Australia)', value: 'https://au-syd1.upcloudobjects.com', region: 'au-syd1' }, // default { name: 'DE-FRA1 (Germany)', value: 'https://de-fra1.upcloudobjects.com', region: 'de-fra1' }, { name: 'ES-MAD1 (Spain)', value: 'https://es-mad1.upcloudobjects.com', region: 'es-mad1' }, { name: 'FI-HEL2 (Finland)', value: 'https://fi-hel2.upcloudobjects.com', region: 'fi-hel2' }, { name: 'NL-AMS1 (Netherlands)', value: 'https://nl-ams1.upcloudobjects.com', region: 'nl-ams1' }, { name: 'PL-WAW1 (Poland)', value: 'https://pl-waw1.upcloudobjects.com', region: 'pl-waw1' }, { name: 'SG-SIN1 (Singapore)', value: 'https://sg-sin1.upcloudobjects.com', region: 'sg-sin1' }, { name: 'UK-LON1 (United Kingdom)', value: 'https://uk-lon1.upcloudobjects.com', region: 'uk-lon1' }, { name: 'US-CHI1 (USA)', value: 'https://us-chi1.upcloudobjects.com', region: 'us-chi1' }, { name: 'US-NYC1 (USA)', value: 'https://us-nyc1.upcloudobjects.com', region: 'us-nyc1' }, { name: 'US-SJO1 (USA)', value: 'https://us-sjo1.upcloudobjects.com', region: 'us-sjo1' }, ]; // region is ignored in vultr (https://www.vultr.com/docs/vultr-object-storage/) const REGIONS_VULTR = [ { name: 'Amsterdam', value: 'https://ams1.vultrobjects.com', region: 'eu-central-1' }, { name: 'Bangalore', value: 'https://blr1.vultrobjects.com', region: 'ap-south-1' }, { name: 'New Jersey', value: 'https://ewr1.vultrobjects.com', region: 'us-east-1' }, { name: 'Silicon Valley', value: 'https://sjc1.vultrobjects.com', region: 'us-west-1' }, { name: 'Singapore', value: 'https://sgp1.vultrobjects.com', region: 'ap-southeast-1' }, ]; // https://docs.contabo.com/docs/products/Object-Storage/s3-connection-settings const REGIONS_CONTABO = [ { name: 'European Union (Germany)', value: 'https://eu2.contabostorage.com' , region: 'us-east-1' }, { name: 'Singapore', value: 'https://sin1.contabostorage.com' , region: 'us-east-1' }, { name: 'United States', value: 'https://usc1.contabostorage.com' , region: 'us-east-1' } ]; const STORAGE_PROVIDERS = [ { name: 'Amazon S3', value: 's3' }, { name: 'Backblaze B2 (S3 API)', value: 'backblaze-b2' }, { name: 'CIFS Mount', value: 'cifs' }, { name: 'Cloudflare R2', value: 'cloudflare-r2' }, { name: 'Contabo Object Storage', value: 'contabo-objectstorage' }, { name: 'DigitalOcean Spaces', value: 'digitalocean-spaces' }, { name: 'External/Local Disk (EXT4 or XFS)', value: 'disk' }, { name: 'EXT4 Disk', value: 'ext4' }, { name: 'Exoscale SOS', value: 'exoscale-sos' }, { name: 'Filesystem', value: 'filesystem' }, { name: 'Filesystem (Mountpoint)', value: 'mountpoint' }, // legacy { name: 'Google Cloud Storage', value: 'gcs' }, { name: 'Hetzner Object Storage', value: 'hetzner-objectstorage' }, { name: 'IDrive e2', value: 'idrive-e2' }, { name: 'IONOS (Profitbricks)', value: 'ionos-objectstorage' }, { name: 'Linode Object Storage', value: 'linode-objectstorage' }, { name: 'Minio', value: 'minio' }, { name: 'NFS Mount', value: 'nfs' }, { name: 'OVH Object Storage', value: 'ovh-objectstorage' }, { name: 'S3 API Compatible (v4)', value: 's3-v4-compat' }, { name: 'Scaleway Object Storage', value: 'scaleway-objectstorage' }, { name: 'SSHFS Mount', value: 'sshfs' }, { name: 'UpCloud Object Storage', value: 'upcloud-objectstorage' }, { name: 'Vultr Object Storage', value: 'vultr-objectstorage' }, { name: 'Wasabi', value: 'wasabi' }, { name: 'XFS Disk', value: 'xfs' }, ]; const BACKUP_FORMATS = [ { name: 'Tarball (zipped)', value: 'tgz' }, { name: 'rsync', value: 'rsync' } ]; // ---------------------------------------------- // Helper to ensure loading a fallback app icon on first load failure // ---------------------------------------------- function imageErrorHandler(elem) { elem.src = elem.getAttribute('fallback-icon'); } // ---------------------------------------------- // Shared Angular Filters // ---------------------------------------------- // https://en.wikipedia.org/wiki/Binary_prefix // binary units (IEC) 1024 based function prettyBinarySize(size, fallback) { if (!size) return fallback || 0; if (size === -1) return 'Unlimited'; // we can also use KB here (JEDEC) var i = Math.floor(Math.log(size) / Math.log(1024)); return (size / Math.pow(1024, i)).toFixed(3) * 1 + ' ' + ['B', 'KiB', 'MiB', 'GiB', 'TiB'][i]; } // decimal units (SI) 1000 based function prettyDecimalSize(size, fallback) { if (!size) return fallback || 0; var i = Math.floor(Math.log(size) / Math.log(1000)); return (size / Math.pow(1000, i)).toFixed(2) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i]; } angular.module('Application').filter('prettyDecimalSize', function () { return function (size, fallback) { return prettyDecimalSize(size, fallback) || '0 kb'; }; }); angular.module('Application').filter('prettyBinarySize', function () { return function (size, fallback) { return prettyBinarySize(size, fallback) || '0 KiB'; }; }); angular.module('Application').filter('prettyDiskSize', function () { return function (size, fallback) { return prettyDecimalSize(size, fallback) || 'Not available yet'; }; }); angular.module('Application').filter('trKeyFromPeriod', function () { return function (period) { if (period === 6) return 'app.graphs.period.6h'; if (period === 12) return 'app.graphs.period.12h'; if (period === 24) return 'app.graphs.period.24h'; if (period === 24*7) return 'app.graphs.period.7d'; if (period === 24*30) return 'app.graphs.period.30d'; return ''; }; }); angular.module('Application').filter('prettyDate', function ($translate) { // http://ejohn.org/files/pretty.js return function prettyDate(utc) { if (utc === null) return $translate.instant('main.prettyDate.never', {}); var date = new Date(utc), // this converts utc into browser timezone and not cloudron timezone! diff = (((new Date()).getTime() - date.getTime()) / 1000) + 30, // add 30seconds for clock skew day_diff = Math.floor(diff / 86400); if (isNaN(day_diff) || day_diff < 0) return $translate.instant('main.prettyDate.justNow', {}); return day_diff === 0 && (diff < 60 && $translate.instant('main.prettyDate.justNow', {}) || moment(utc).locale(navigator.language).format('LT')) || day_diff === 1 && $translate.instant('main.prettyDate.yeserday', {}) || moment(utc).locale(navigator.language).format('L'); }; }); angular.module('Application').filter('prettyFutureDate', function () { return function prettyFutureDate(utc) { return moment(utc).fromNow(); // this converts utc into browser timezone and not cloudron timezone! }; }); angular.module('Application').filter('prettyLongDate', function () { return function prettyLongDate(utc) { return moment(utc).locale(navigator.language).format('LLL'); // this converts utc into browser timezone and not cloudron timezone! }; }); angular.module('Application').filter('prettyShortDate', function () { return function prettyShortDate(utc) { return moment(utc).locale(navigator.language).format('L'); // this converts utc into browser timezone and not cloudron timezone! }; }); angular.module('Application').filter('markdown2html', function () { var converter = new showdown.Converter({ simplifiedAutoLink: true, strikethrough: true, tables: true, openLinksInNewWindow: true }); // without this cache, the code runs into some infinite loop (https://github.com/angular/angular.js/issues/3980) var cache = {}; return function (text) { if (cache[text]) return cache[text]; cache[text] = converter.makeHtml(text); return cache[text]; }; }); angular.module('Application').config(['$translateProvider', function ($translateProvider) { $translateProvider.useStaticFilesLoader({ prefix: 'translation/', suffix: '.json' }); $translateProvider.useLocalStorage(); $translateProvider.preferredLanguage('en'); $translateProvider.fallbackLanguage('en'); }]); // Add shorthand "tr" filter to avoid having ot use "translate" // This is a copy of the code at https://github.com/angular-translate/angular-translate/blob/master/src/filter/translate.js // If we find out how to get that function handle somehow dynamically we can use that, otherwise the copy is required function translateFilterFactory($parse, $translate) { var translateFilter = function (translationId, interpolateParams, interpolation, forceLanguage) { if (!angular.isObject(interpolateParams)) { var ctx = this || { '__SCOPE_IS_NOT_AVAILABLE': 'More info at https://github.com/angular/angular.js/commit/8863b9d04c722b278fa93c5d66ad1e578ad6eb1f' }; interpolateParams = $parse(interpolateParams)(ctx); } return $translate.instant(translationId, interpolateParams, interpolation, forceLanguage); }; if ($translate.statefulFilter()) { translateFilter.$stateful = true; } return translateFilter; } translateFilterFactory.displayName = 'translateFilterFactory'; angular.module('Application').filter('tr', translateFilterFactory); // checks provision status and redirects to correct view // { // setup: { active, message, errorMessage } // restore { active, message, errorMessage } // activated // adminFqn // } // returns true if redirected . currentView is one of dashboard/restore/setup/activation function redirectIfNeeded(status, currentView) { var search = decodeURIComponent(window.location.search).slice(1).split('&').map(function (item) { return item.split('='); }).reduce(function (o, k) { o[k[0]] = k[1]; return o; }, {}); if ('develop' in search || localStorage.getItem('develop')) { console.warn('Cloudron develop mode on. To disable run localStorage.removeItem(\'develop\')'); localStorage.setItem('develop', true); return false; } if (status.activated) { console.log('Already activated'); if (currentView === 'dashboard') { // support local development with localhost check if (window.location.hostname !== status.adminFqdn && window.location.hostname !== 'localhost' && !window.location.hostname.startsWith('192.')) { // user is accessing by IP or by the old admin location (pre-migration) window.location.href = '/setup.html' + window.location.search; return true; } return false; } window.location.href = 'https://' + status.adminFqdn + '/'; return true; } if (status.setup.active) { console.log('Setup is active'); if (currentView === 'setup') return false; window.location.href = '/setup.html' + window.location.search; return true; } if (status.restore.active) { console.log('Restore is active'); if (currentView === 'restore') return; window.location.href = '/restore.html' + window.location.search; return true; } if (status.adminFqdn) { console.log('adminFqdn is set'); // if we are here from https://ip/activation.html ,go to https://admin/activation.html if (status.adminFqdn !== window.location.hostname) { window.location.href = 'https://' + status.adminFqdn + '/activation.html' + (window.location.search); return true; } if (currentView === 'activation') return false; window.location.href = 'https://' + status.adminFqdn + '/activation.html' + (window.location.search); return true; } if (currentView === 'dashboard') { window.location.href = '/setup.html' + window.location.search; return true; } // if we are here, proceed with current view return false; } // ---------------------------------------------- // Cloudron REST API wrapper // ---------------------------------------------- angular.module('Application').service('Client', ['$http', '$interval', '$timeout', 'md5', 'Notification', '$translate', function ($http, $interval, $timeout, md5, Notification, $translate) { var client = null; // variable available only here to avoid this._property pattern var token = null; function ClientError(statusCode, messageOrObject) { Error.call(this); this.name = this.constructor.name; this.statusCode = statusCode; if (messageOrObject === null || typeof messageOrObject === 'undefined') { this.message = 'Empty message or object'; } else if (typeof messageOrObject === 'string') { this.message = messageOrObject; } else if (messageOrObject) { angular.extend(this, messageOrObject); // status, message, reason and other properties } } function defaultErrorHandler(callback) { function handleServerOffline() { if (client.offline) return; (function onlineCheck() { $http.get(client.apiOrigin + '/api/v1/cloudron/status', {}).success(function (data, status) { client.offline = false; client._reconnectListener.forEach(function (handler) { handler(); }); }).error(function (data, status) { client.offline = true; $timeout(onlineCheck, 5000); }); })(); } return function (data, status) { // handle request killed by browser (eg. cors issue) if (data === null && status === -1) { handleServerOffline(); return callback(new ClientError('Request cancelled by browser')); } // re-login will make the code get a new token if (status === 401) return client.login(); if (status === 500 || status === 501) { // actual internal server error, most likely a bug or timeout log to console only to not alert the user if (!client.offline) { console.error(status, data); console.log('------\nCloudron Internal Error\n\nIf you see this, please send a mail with above log to support@cloudron.io\n------\n'); } } else if (status === 502 || status === 503 || status === 504) { // This means the box service is not reachable. We just show offline banner for now } if (status >= 502) { handleServerOffline(); return callback(new ClientError(status, data)); } var obj = data; try { obj = JSON.parse(data); } catch (e) {} callback(new ClientError(status, obj)); }; } function defaultSuccessHandler(callback) { return function (data, status) { return callback(null, data, status); }; } // XHR wrapper to set the auth header function get(url, config, callback) { if (arguments.length !== 3) { console.error('GET', arguments); throw('Wrong number of arguments'); } config = config || {}; config.headers = config.headers || {}; config.headers.Authorization = 'Bearer ' + token; return $http.get(client.apiOrigin + url, config) .success(defaultSuccessHandler(callback)) .error(defaultErrorHandler(callback)); } function post(url, data, config, callback) { if (arguments.length !== 4) { console.error('POST', arguments); throw('Wrong number of arguments'); } data = data || {}; config = config || {}; config.headers = config.headers || {}; config.headers.Authorization = 'Bearer ' + token; return $http.post(client.apiOrigin + url, data, config) .success(defaultSuccessHandler(callback)) .error(defaultErrorHandler(callback)); } function put(url, data, config, callback) { if (arguments.length !== 4) { console.error('PUT', arguments); throw('Wrong number of arguments'); } data = data || {}; config = config || {}; config.headers = config.headers || {}; config.headers.Authorization = 'Bearer ' + token; return $http.put(client.apiOrigin + url, data, config) .success(defaultSuccessHandler(callback)) .error(defaultErrorHandler(callback)); } function del(url, config, callback) { if (arguments.length !== 3) { console.error('DEL', arguments); throw('Wrong number of arguments'); } config = config || {}; config.headers = config.headers || {}; config.headers.Authorization = 'Bearer ' + token; return $http.delete(client.apiOrigin + url, config) .success(defaultSuccessHandler(callback)) .error(defaultErrorHandler(callback)); } function Client() { this.offline = false; this._ready = false; this._configListener = []; this._readyListener = []; this._reconnectListener = []; this._userInfo = { id: null, username: null, email: null, twoFactorAuthenticationEnabled: false, source: null, avatarUrl: null, avatarType: null, hasBackgroundImage: false, notificationConfig: [] }; this._config = { consoleServerOrigin: null, fqdn: null, ip: null, revision: null, update: { box: null, apps: null }, progress: {}, region: null, size: null }; this._installedApps = []; this._installedAppsById = {}; this._appTags = []; // window.location fallback for websocket connections which do not have relative uris this.apiOrigin = window.cloudronApiOrigin || window.location.origin; this.avatar = ''; this.background = ''; this._availableLanguages = ['en']; this._appstoreAppCache = []; this.resetAvatar(); this.setToken(localStorage.token); } Client.prototype.error = function (error, action) { var message = ''; console.error(error); if (typeof error === 'object') { message = error.message || error; } else { message = error; } // give more info in case the error was a request which failed with empty response body, // this happens mostly if the box crashes if (message === 'Empty message or object') { message = 'Got empty response. Click to check the server logs.'; action = action || '/logs.html?id=box'; } this.notify('Cloudron Error', message, true, 'error', action); }; // handles application startup errors, mostly only when dashboard is loaded and api endpoint is down Client.prototype.initError = function (error, initFunction) { console.error('Application startup error', error); // $timeout(initFunction, 5000); // we will try to re-init the app }; Client.prototype.clearNotifications = function () { Notification.clearAll(); }; /* If `action` is a non-empty string, it will be treated as a url, if it is a function, that function will be exectued on click */ Client.prototype.notify = function (title, message, persistent, type, action) { var options = { title: title, message: message}; if (persistent) options.delay = 'never'; // any non Number means never timeout if (action) { options.onClick = function (/* params */) { // if action is a string, we assume it is a link if (typeof action === 'string' && action !== '') window.location = action; else if (typeof action === 'function') action(); else console.warn('Notification action is not supported.', action); }; } if (type === 'error') Notification.error(options); else if (type === 'success') Notification.success(options); else if (type === 'info') Notification.info(options); else if (type === 'warning') Notification.warning(options); else throw('Invalid notification type "' + type + '"'); }; Client.prototype.setReady = function () { if (this._ready) return; this._ready = true; this._readyListener.forEach(function (callback) { callback(); }); // clear the listeners, we only callback once! this._readyListener = []; }; Client.prototype.onReady = function (callback) { if (this._ready) callback(); else this._readyListener.push(callback); }; Client.prototype.onConfig = function (callback) { this._configListener.push(callback); if (this._config) callback(this._config); }; Client.prototype.onReconnect = function (callback) { if (this._ready) callback(); else this._reconnectListener.push(callback); }; Client.prototype.resetAvatar = function () { this.avatar = this.apiOrigin + '/api/v1/cloudron/avatar?' + String(Math.random()).slice(2); var favicon = $('#favicon'); if (favicon) favicon.attr('href', this.avatar); }; Client.prototype.setUserInfo = function (userInfo) { // In order to keep the angular bindings alive, set each property individually this._userInfo.id = userInfo.id; this._userInfo.username = userInfo.username; this._userInfo.email = userInfo.email; this._userInfo.fallbackEmail = userInfo.fallbackEmail; this._userInfo.displayName = userInfo.displayName; this._userInfo.twoFactorAuthenticationEnabled = userInfo.twoFactorAuthenticationEnabled; this._userInfo.role = userInfo.role; this._userInfo.source = userInfo.source; this._userInfo.avatarUrl = userInfo.avatarUrl + '?ts=' + Date.now(); // we add the timestamp to avoid caching this._userInfo.avatarType = userInfo.avatarType; this._userInfo.hasBackgroundImage = userInfo.hasBackgroundImage; this._userInfo.notificationConfig = userInfo.notificationConfig; this._userInfo.isAtLeastOwner = [ ROLES.OWNER ].indexOf(userInfo.role) !== -1; this._userInfo.isAtLeastAdmin = [ ROLES.OWNER, ROLES.ADMIN ].indexOf(userInfo.role) !== -1; this._userInfo.isAtLeastMailManager = [ ROLES.OWNER, ROLES.ADMIN, ROLES.MAIL_MANAGER ].indexOf(userInfo.role) !== -1; this._userInfo.isAtLeastUserManager = [ ROLES.OWNER, ROLES.ADMIN, ROLES.MAIL_MANAGER, ROLES.USER_MANAGER ].indexOf(userInfo.role) !== -1; }; Client.prototype.setConfig = function (config) { var that = this; angular.copy(config, this._config); // <% if (appstore.consoleOrigin) { -%> // this._config.consoleServerOrigin = '<%= appstore.consoleOrigin %>'; // <% } -%> this._configListener.forEach(function (callback) { callback(that._config); }); }; Client.prototype.getInstalledApps = function () { return this._installedApps; }; Client.prototype.getInstalledAppsByAppId = function () { return this._installedAppsById; }; Client.prototype.getAppTags = function () { return this._appTags; }; Client.prototype.getUserInfo = function () { return this._userInfo; }; Client.prototype.getConfig = function () { return this._config; }; Client.prototype.getAvailableLanguages = function () { return this._availableLanguages; }; Client.prototype.setToken = function (accessToken) { if (!accessToken) localStorage.removeItem('token'); else localStorage.token = accessToken; // set the token closure token = accessToken; }; Client.prototype.getToken = function () { return token; }; /* * Rest API wrappers */ Client.prototype.config = function (callback) { get('/api/v1/dashboard/config', null, function (error, data, status) { if (error) return callback(error); if (status !== 200 || typeof data !== 'object') return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.getProfile = function (callback) { get('/api/v1/profile', null, function (error, data, status) { if (error) return callback(error); if (status !== 200 || typeof data !== 'object') return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.hasCloudronBackground = function (callback) { get('/api/v1/branding/cloudron_background', null, function (error, data, status) { if (error && error.statusCode !== 404) callback(error); else if (error) callback(null, false); else callback(null, status === 200); }); }; Client.prototype.changeCloudronBackground = function (background, callback) { var fd = new FormData(); if (background) fd.append('background', background); var config = { headers: { 'Content-Type': undefined }, transformRequest: angular.identity }; post('/api/v1/branding/cloudron_background', fd, config, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.changeCloudronAvatar = function (avatarFile, callback) { var fd = new FormData(); fd.append('avatar', avatarFile); var config = { headers: { 'Content-Type': undefined }, transformRequest: angular.identity }; post('/api/v1/branding/cloudron_avatar', fd, config, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.changeCloudronName = function (name, callback) { var data = { name: name }; post('/api/v1/branding/cloudron_name', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.installApp = function (id, manifest, config, callback) { var data = { appStoreId: id + '@' + manifest.version, subdomain: config.subdomain, domain: config.domain, secondaryDomains: config.secondaryDomains, ports: config.ports, accessRestriction: config.accessRestriction, cert: config.cert, key: config.key, sso: config.sso, overwriteDns: config.overwriteDns, upstreamUri: config.upstreamUri, backupId: config.backupId // when restoring from archive }; post('/api/v1/apps', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null, data.id); }); }; Client.prototype.cloneApp = function (appId, config, callback) { var data = { subdomain: config.subdomain, domain: config.domain, secondaryDomains: config.secondaryDomains, ports: config.ports, backupId: config.backupId, overwriteDns: !!config.overwriteDns }; post('/api/v1/apps/' + appId + '/clone', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 201) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.restoreApp = function (appId, backupId, callback) { var data = { backupId: backupId }; post('/api/v1/apps/' + appId + '/restore', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.backupApp = function (appId, callback) { var data = {}; post('/api/v1/apps/' + appId + '/backup', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.archiveApp = function (appId, backupId, callback) { var data = { backupId: backupId }; post('/api/v1/apps/' + appId + '/archive', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.uninstallApp = function (appId, callback) { var data = {}; post('/api/v1/apps/' + appId + '/uninstall', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.configureApp = function (id, setting, data, callback) { post('/api/v1/apps/' + id + '/configure/' + setting, data, null, function (error, data, status) { if (error) return callback(error); if (status !== 200 && status !== 202) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.repairApp = function (id, data, callback) { post('/api/v1/apps/' + id + '/repair', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 200 && status !== 202) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.editAppBackup = function (id, backupId, label, preserveSecs, callback) { post('/api/v1/apps/' + id + '/backups/' + backupId, { label: label, preserveSecs: preserveSecs }, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.ackAppChecklistItem = function (appId, key, acknowledged, callback) { put('/api/v1/apps/' + appId + '/checklist/' + key, { done: acknowledged }, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.updateApp = function (id, manifest, options, callback) { var data = { appStoreId: manifest.id + '@' + manifest.version, skipBackup: !!options.skipBackup }; post('/api/v1/apps/' + id + '/update', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.startApp = function (id, callback) { post('/api/v1/apps/' + id + '/start', {}, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.stopApp = function (id, callback) { post('/api/v1/apps/' + id + '/stop', {}, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.restartApp = function (id, callback) { post('/api/v1/apps/' + id + '/restart', {}, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.debugApp = function (id, state, callback) { var data = { debugMode: state ? { readonlyRootfs: false, cmd: [ '/bin/bash', '-c', 'echo "Repair mode. Use the webterminal or cloudron exec to repair. Sleeping" && sleep infinity' ] } : null }; post('/api/v1/apps/' + id + '/configure/debug_mode', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.getProvisionStatus = function (callback) { get('/api/v1/provision/status', null, function (error, data, status) { if (error) return callback(error); if (status !== 200 || typeof data !== 'object') return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.getStatus = function (callback) { get('/api/v1/cloudron/status', null, function (error, data, status) { if (error) return callback(error); if (status !== 200 || typeof data !== 'object') return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.getPlatformStatus = function (callback) { get('/api/v1/services/platform_status', null, function (error, data, status) { if (error) return callback(error); if (status !== 200 || typeof data !== 'object') return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.setBackupConfig = function (backupConfig, callback) { const storageConfig = Object.assign({}, backupConfig); delete storageConfig.limits; post('/api/v1/backups/config/storage', storageConfig, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); post('/api/v1/backups/config/limits', backupConfig.limits, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }); }; Client.prototype.getBackupConfig = function (callback) { get('/api/v1/backups/config', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.setBackupPolicy = function (backupPolicy, callback) { post('/api/v1/backups/policy', backupPolicy, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.getBackupPolicy = function (callback) { get('/api/v1/backups/policy', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.policy); }); }; Client.prototype.getBackupMountStatus = function (callback) { get('/api/v1/backups/mount_status', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.remountBackupStorage = function (callback) { post('/api/v1/backups/remount', {}, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.setExternalLdapConfig = function (config, callback) { post('/api/v1/external_ldap/config', config, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.getExternalLdapConfig = function (callback) { get('/api/v1/external_ldap/config', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.setProfileConfig = function (config, callback) { post('/api/v1/user_directory/profile_config', config, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.getProfileConfig = function (callback) { get('/api/v1/user_directory/profile_config', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.setUserDirectoryConfig = function (config, callback) { post('/api/v1/directory_server/config', config, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.getUserDirectoryConfig = function (callback) { get('/api/v1/directory_server/config', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; // network Client.prototype.setIPv4Config = function (config, callback) { post('/api/v1/network/ipv4_config', config, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.getIPv4Config = function (callback) { get('/api/v1/network/ipv4_config', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.getServerIpv4 = function (callback) { get('/api/v1/network/ipv4', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.getServerIpv6 = function (callback) { get('/api/v1/network/ipv6', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.getBlocklist = function (callback) { get('/api/v1/network/blocklist', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.blocklist); }); }; Client.prototype.setBlocklist = function (blocklist, callback) { post('/api/v1/network/blocklist', { blocklist: blocklist }, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.getTrustedIps = function (callback) { get('/api/v1/reverseproxy/trusted_ips', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.trustedIps); }); }; Client.prototype.setTrustedIps = function (trustedIps, callback) { post('/api/v1/reverseproxy/trusted_ips', { trustedIps: trustedIps }, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.setDynamicDnsConfig = function (enabled, callback) { post('/api/v1/network/dynamic_dns', { enabled: enabled }, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.getDynamicDnsConfig = function (callback) { get('/api/v1/network/dynamic_dns', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.enabled); }); }; Client.prototype.setIPv6Config = function (config, callback) { post('/api/v1/network/ipv6_config', config, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.getIPv6Config = function (callback) { get('/api/v1/network/ipv6_config', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; // branding Client.prototype.setFooter = function (footer, callback) { post('/api/v1/branding/footer', { footer: footer }, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.getFooter = function (callback) { get('/api/v1/branding/footer', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.footer); }); }; Client.prototype.setRegistryConfig = function (rc, callback) { post('/api/v1/docker/registry_config', rc, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.getRegistryConfig = function (callback) { get('/api/v1/docker/registry_config', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.getUpdateInfo = function (callback) { get('/api/v1/updater/updates', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.checkForUpdates = function (callback) { post('/api/v1/updater/check_for_updates', {}, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); client.refreshConfig(callback); }); }; Client.prototype.checkForAppUpdates = function (appId, callback) { post('/api/v1/apps/' + appId + '/check_for_updates', {}, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); client.refreshConfig(callback); }); }; Client.prototype.setAutoupdatePattern = function (pattern, callback) { post('/api/v1/updater/autoupdate_pattern', { pattern: pattern }, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.getAutoupdatePattern = function (callback) { get('/api/v1/updater/autoupdate_pattern', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.setTimeZone = function (timeZone, callback) { post('/api/v1/cloudron/time_zone', { timeZone: timeZone }, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.getTimeZone = function (callback) { get('/api/v1/cloudron/time_zone', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.timeZone); }); }; Client.prototype.setLanguage = function (language, callback) { post('/api/v1/cloudron/language', { language: language }, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.getLanguage = function (callback) { get('/api/v1/cloudron/language', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.language); }); }; Client.prototype.getRemoteSupport = function (callback) { get('/api/v1/support/remote_support', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.enabled); }); }; Client.prototype.enableRemoteSupport = function (enable, callback) { post('/api/v1/support/remote_support', { enable: enable }, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.listArchives = function (callback) { var config = { params: { page: 1, per_page: 100 } }; get('/api/v1/archives', config, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.archives); }); }; Client.prototype.deleteArchive = function (id, callback) { del('/api/v1/archives/' + id, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.unarchiveApp = function (archiveId, data, callback) { post('/api/v1/archives/' + archiveId + '/unarchive', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.getBackups = function (callback) { var page = 1; var perPage = 100; var backups = []; function fetchMore() { var config = { params: { page: page, per_page: perPage } }; get('/api/v1/backups', config, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); backups = backups.concat(data.backups); if (data.backups.length < perPage) return callback(null, backups); page++; fetchMore(); }); } fetchMore(); }; Client.prototype.getLatestTaskByType = function (type, callback) { get('/api/v1/tasks?page=1&per_page=1&type=' + type, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.tasks.length ? data.tasks[0] : null); }); }; Client.prototype.getTasksByType = function (type, callback) { get('/api/v1/tasks?type=' + type, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.tasks); }); }; Client.prototype.getTask = function (taskId, callback) { get('/api/v1/tasks/' + taskId, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.editBackup = function (backupId, label, preserveSecs, callback) { post('/api/v1/backups/' + backupId, { label: label, preserveSecs: preserveSecs }, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.startBackup = function (callback) { post('/api/v1/backups/create', {}, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null, data.taskId); }); }; Client.prototype.cleanupBackups = function (callback) { post('/api/v1/backups/cleanup', {}, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null, data.taskId); }); }; Client.prototype.stopTask = function (taskId, callback) { post('/api/v1/tasks/' + taskId + '/stop', {}, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.restore = function (data, callback) { post('/api/v1/provision/restore', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status)); callback(null); }); }; Client.prototype.importBackup = function (appId, remotePath, backupFormat, backupConfig, callback) { var data = { remotePath: remotePath, backupFormat: backupFormat, backupConfig: backupConfig, }; post('/api/v1/apps/' + appId + '/import', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status)); callback(null); }); }; Client.prototype.getNotifications = function (options, page, perPage, callback) { var config = { params: { page: page, per_page: perPage } }; if (typeof options.acknowledged === 'boolean') config.params.acknowledged = options.acknowledged; get('/api/v1/notifications', config, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.notifications); }); }; Client.prototype.ackNotification = function (notificationId, acknowledged, callback) { post('/api/v1/notifications/' + notificationId, { acknowledged: acknowledged }, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status)); callback(null); }); }; Client.prototype.getEvent = function (eventId, callback) { get('/api/v1/eventlog/' + eventId, {}, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.event); }); }; Client.prototype.getEventLogs = function (actions, search, page, perPage, callback) { var config = { params: { actions: actions, search: search, page: page, per_page: perPage } }; get('/api/v1/eventlog', config, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.eventlogs); }); }; Client.prototype.getApps = function (callback) { var that = this; get('/api/v1/apps', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); var apps = data.apps; for (var i = 0; i < apps.length; i++) { that._appPostProcess(apps[i]); // this will also set the correct iconUrl } callback(null, apps); }); }; Client.prototype.getAppBackups = function (appId, callback) { var page = 1; var perPage = 100; var backups = []; function fetchMore() { var config = { params: { page: page, per_page: perPage } }; get('/api/v1/apps/' + appId + '/backups', config, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); backups = backups.concat(data.backups); if (data.backups.length < perPage) return callback(null, backups); page++; fetchMore(); }); } fetchMore(); }; Client.prototype.getAppBackupDownloadLink = function (appId, backupId) { return client.apiOrigin + '/api/v1/apps/' + appId + '/backups/' + backupId + '/download?access_token=' + token; }; Client.prototype.getServices = function (callback) { get('/api/v1/services', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.services); }); }; Client.prototype.getService = function (serviceName, callback) { get('/api/v1/services/' + serviceName, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.service); }); }; Client.prototype.configureService = function (serviceName, data, callback) { post('/api/v1/services/' + serviceName, data, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.restartService = function (serviceName, callback) { post('/api/v1/services/' + serviceName + '/restart', {}, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.rebuildService = function (serviceName, callback) { post('/api/v1/services/' + serviceName + '/rebuild', {}, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.getAllUsers = function (callback) { var page = 1; var perPage = 5000; var users = []; function fetchMore() { var config = { params: { page: page, per_page: perPage } }; get('/api/v1/users', config, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); users = users.concat(data.users); if (data.users.length < perPage) return callback(null, users); page++; fetchMore(); }); } fetchMore(); }; Client.prototype.getUsers = function (search, active, page, perPage, callback) { var config = { params: { page: page, per_page: perPage } }; if (search) config.params.search = search; if (active !== null) config.params.active = active ? 'true' : 'false'; get('/api/v1/users', config, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.users); }); }; Client.prototype.getUser = function (userId, callback) { get('/api/v1/users/' + userId, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.getGroups = function (callback) { get('/api/v1/groups', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.groups); }); }; Client.prototype.setLocalGroups = function (userId, localGroupIds, callback) { put('/api/v1/users/' + userId + '/groups', { groupIds: localGroupIds }, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.getGroup = function (groupId, callback) { get('/api/v1/groups/' + groupId, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.createGroup = function (name, callback) { var data = { name: name }; post('/api/v1/groups', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 201) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.setGroupName = function (id, name, callback) { var data = { name: name }; put('/api/v1/groups/' + id + '/name', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.setGroupMembers = function (id, userIds, callback) { var data = { userIds: userIds }; put('/api/v1/groups/' + id + '/members', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.removeGroup = function (groupId, callback) { var config = { data: {}, headers: { 'Content-Type': 'application/json' } }; del('/api/v1/groups/' + groupId, config, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.getApp = function (appId, callback) { var that = this; get('/api/v1/apps/' + appId, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); that._appPostProcess(data); callback(null, data); }); }; Client.prototype.getAppTask = function (appId, callback) { get('/api/v1/apps/' + appId + '/task', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.getAppWithTask = function (appId, callback) { var that = this; this.getApp(appId, function (error, app) { if (error) return callback(error); if (!app.taskId) return callback(null, app); that.getAppTask(appId, function (error, task) { if (error) return callback(error); if (task) { app.progress = task.percent; app.message = task.message; app.taskMinutesActive = moment.duration(moment.utc().diff(moment.utc(task.creationTime))).asMinutes(); } else { app.progress = 0; app.message = ''; app.taskMinutesActive = 0; } callback(null, app); }); }); }; Client.prototype.getCachedAppSync = function (appId) { var appFound = null; this._installedApps.some(function (app) { if (app.id === appId) { appFound = app; return true; } else { return false; } }); return appFound; }; Client.prototype.disableTwoFactorAuthenticationByUserId = function (userId, callback) { post('/api/v1/users/' + userId + '/twofactorauthentication_disable', {}, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.detectIp = function (callback) { post('/api/v1/provision/detect_ip', {}, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.setup = function (data, callback) { post('/api/v1/provision/setup', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.getProvisionBlockDevices = function (callback) { get('/api/v1/provision/block_devices', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.devices); }); }; Client.prototype.createAdmin = function (data, callback) { var that = this; post('/api/v1/provision/activate', data, null, function (error, result, status) { if (error) return callback(error); if (status !== 201) return callback(new ClientError(status, result)); callback(null, result.token); }); }; Client.prototype.getTokens = function (callback) { get('/api/v1/tokens/', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.tokens); }); }; Client.prototype.createToken = function (name, scope, callback) { var data = { name: name, scope: scope }; post('/api/v1/tokens', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 201) return callback(new ClientError(status, data)); callback(null, data); }); }; // FIXME clashes with existing getToken() // Client.prototype.getToken = function (id, callback) { // get('/api/v1/tokens/' + id, null, function (error, data, status) { // if (error) return callback(error); // if (status !== 200) return callback(new ClientError(status, data)); // callback(null, data.token); // }); // }; Client.prototype.delToken = function (tokenId, callback) { del('/api/v1/tokens/' + tokenId, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.getOidcClients = function (callback) { get('/api/v1/oidc/clients', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.clients); }); }; Client.prototype.addOidcClient = function (name, loginRedirectUri, tokenSignatureAlgorithm, callback) { var data = { name: name, loginRedirectUri: loginRedirectUri, tokenSignatureAlgorithm: tokenSignatureAlgorithm }; post('/api/v1/oidc/clients', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 201) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.updateOidcClient = function (id, name, loginRedirectUri, tokenSignatureAlgorithm, callback) { var data = { name: name, loginRedirectUri: loginRedirectUri, tokenSignatureAlgorithm, tokenSignatureAlgorithm }; post('/api/v1/oidc/clients/' + id, data, null, function (error, data, status) { if (error) return callback(error); if (status !== 201) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.delOidcClient = function (id, callback) { del('/api/v1/oidc/clients/' + id, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.addAppPassword = function (identifier, name, callback) { var data = { identifier: identifier, name: name }; post('/api/v1/app_passwords', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 201) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.getAppPasswords = function (callback) { get('/api/v1/app_passwords', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.delAppPassword = function (id, callback) { del('/api/v1/app_passwords/' + id, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.update = function (options, callback) { var data = { skipBackup: !!options.skipBackup }; post('/api/v1/updater/update', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null, data.taskId); }); }; Client.prototype.isRebootRequired = function (callback) { get('/api/v1/system/info', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.rebootRequired); }); }; Client.prototype.reboot = function (callback) { post('/api/v1/system/reboot', {}, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.getBlockDevices = function (callback) { get('/api/v1/system/block_devices', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.devices); }); }; Client.prototype.diskUsage = function (callback) { get('/api/v1/system/disk_usage', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.refreshDiskUsage = function (callback) { post('/api/v1/system/disk_usage', {}, null, function (error, data, status) { if (error) return callback(error); if (status !== 201) return callback(new ClientError(status, data)); callback(null, data.taskId); }); }; Client.prototype.systemInfo = function (callback) { get('/api/v1/system/info', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.info); }); }; Client.prototype.cpus = function (callback) { get('/api/v1/system/cpus', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.cpus); }); }; Client.prototype.memory = function (callback) { get('/api/v1/system/memory', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.getAppGraphs = function (appId, fromMinutes, callback) { get('/api/v1/apps/' + appId + '/graphs', { params: { fromMinutes: fromMinutes } }, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.getSystemGraphs = function (fromMinutes, callback) { get('/api/v1/system/graphs', { params: { fromMinutes: fromMinutes } }, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.systemGraphs = function (targets, from, options, callback) { // if we have a lot of apps, targets can be very large. node will just disconnect since it exceeds header size var size = 10, chunks = []; for (var i = 0; i < targets.length; i += size) { chunks.push(targets.slice(i, i+size)); } var result = []; async.eachSeries(chunks, function (chunk, iteratorCallback) { var config = { params: { target: chunk, format: 'json', from: from, until: 'now' } }; if (options.noNullPoints) config.params.noNullPoints = true; get(options.appId ? '/api/v1/apps/' + options.appId + '/graphs' : '/api/v1/cloudron/graphs', config, function (error, data, status) { if (error) return iteratorCallback(error); if (status !== 200) return iteratorCallback(new ClientError(status, data)); // the datapoint returned here is an [value, timestamp] result = result.concat(data); iteratorCallback(null); }); }, function iteratorDone(error) { callback(error, result); }); }; Client.prototype.createTicket = function (ticket, callback) { // just to be eplicit here var data = { enableSshSupport: !!ticket.enableSshSupport, type: ticket.type, subject: ticket.subject, description: ticket.description, appId: ticket.appId || undefined, altEmail: ticket.altEmail || undefined }; post('/api/v1/support/ticket', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 201) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.getApplinks = function (callback) { var that = this; get('/api/v1/applinks', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); // amend properties to mimick full app data.applinks.forEach(function (applink) { applink.type = APP_TYPES.LINK; applink.fqdn = applink.upstreamUri; applink.manifest = { addons: {}}; applink.installationState = ISTATES.INSTALLED; applink.runState = RSTATES.RUNNING; applink.health = HSTATES.HEALTHY; applink.iconUrl = that.apiOrigin + '/api/v1/applinks/' + applink.id + '/icon?access_token=' + token + '&ts=' + applink.ts; applink.accessLevel = that._userInfo.isAtLeastAdmin ? 'admin' : 'user'; }); callback(null, data.applinks); }); }; Client.prototype.addApplink = function (applink, callback) { post('/api/v1/applinks', applink, null, function (error, data, status) { if (error) return callback(error); if (status !== 201) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.updateApplink = function (id, data, callback) { post('/api/v1/applinks/' + id, data, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.removeApplink = function (id, callback) { del('/api/v1/applinks/' + id, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.addUser = function (user, callback) { var data = { email: user.email, fallbackEmail: user.fallbackEmail, displayName: user.displayName, role: user.role }; if (user.username) data.username = user.username; if (user.password) data.password = user.password; post('/api/v1/users', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 201) return callback(new ClientError(status, data)); callback(null, data.id); }); }; Client.prototype.updateUserProfile = function (userId, data, callback) { post('/api/v1/users/' + userId + '/profile', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.setRole = function (userId, role, callback) { put('/api/v1/users/' + userId + '/role', { role: role }, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.setActive = function (userId, active, callback) { put('/api/v1/users/' + userId + '/active', { active: active }, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.removeUser = function (userId, callback) { var config = { data: {}, headers: { 'Content-Type': 'application/json' } }; del('/api/v1/users/' + userId, config, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.setProfileDisplayName = function (displayName, callback) { post('/api/v1/profile/display_name', { displayName: displayName }, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.setProfileLanguage = function (language, callback) { post('/api/v1/profile/language', { language: language }, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.setNotificationConfig = function (notificationConfig, callback) { post('/api/v1/profile/notification_config', { notificationConfig }, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.setProfileEmail = function (email, password, callback) { post('/api/v1/profile/email', { email: email, password: password }, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.setProfileFallbackEmail = function (fallbackEmail, password, callback) { post('/api/v1/profile/fallback_email', { fallbackEmail: fallbackEmail, password: password }, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.changeAvatar = function (avatarFileOrType, callback) { // Blob type if object if (typeof avatarFileOrType === 'object') { var fd = new FormData(); fd.append('avatar', avatarFileOrType); var config = { headers: { 'Content-Type': undefined }, transformRequest: angular.identity }; post('/api/v1/profile/avatar', fd, config, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); } else { post('/api/v1/profile/avatar', { avatar: avatarFileOrType === 'gravatar' ? 'gravatar' : '' }, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); } }; Client.prototype.getBackgroundImageUrl = function () { return client.apiOrigin + '/api/v1/profile/background_image?access_token=' + token + '&bustcache=' + Date.now(); }; Client.prototype.setBackgroundImage = function (backgroundImage, callback) { // Blob type if object var fd = new FormData(); if (backgroundImage) fd.append('backgroundImage', backgroundImage); var config = { headers: { 'Content-Type': undefined }, transformRequest: angular.identity }; post('/api/v1/profile/background_image', fd, config, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.changePassword = function (currentPassword, newPassword, callback) { var data = { password: currentPassword, newPassword: newPassword }; post('/api/v1/profile/password', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.getPasswordResetLink = function (userId, callback) { get('/api/v1/users/' + userId + '/password_reset_link', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.sendPasswordResetEmail = function (userId, email, callback) { post('/api/v1/users/' + userId + '/send_password_reset_email', { email }, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.sendSelfPasswordReset = function (identifier, callback) { post('/api/v1/auth/password_reset_request', { identifier }, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.getInviteLink = function (userId, callback) { get('/api/v1/users/' + userId + '/invite_link', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.sendInviteEmail = function (userId, email, callback) { post('/api/v1/users/' + userId + '/send_invite_email', { email }, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.setTwoFactorAuthenticationSecret = function (callback) { var data = {}; post('/api/v1/profile/twofactorauthentication_secret', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.enableTwoFactorAuthentication = function (totpToken, callback) { var data = { totpToken: totpToken }; post('/api/v1/profile/twofactorauthentication_enable', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.disableTwoFactorAuthentication = function (password, callback) { var data = { password: password }; post('/api/v1/profile/twofactorauthentication_disable', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.setGhost = function (userId, password, expiresAt, callback) { var data = { password }; if (expiresAt) data.expiresAt = expiresAt; post('/api/v1/users/' + userId + '/ghost', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.startExternalLdapSync = function (callback) { post('/api/v1/external_ldap/sync', {}, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null, data.taskId); }); }; Client.prototype.setUserActive = function (userId, active, callback) { var data = { active: active }; post('/api/v1/users/' + userId + '/active', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.refreshProfile = function (callback) { var that = this; callback = typeof callback === 'function' ? callback : function () {}; this.getProfile(function (error, result) { if (error) return callback(error); if (result.language !== '' && $translate.use() !== result.language) { console.log('Changing users language from ' + $translate.use() + ' to ', result.language); $translate.use(result.language); } that.setUserInfo(result); callback(null); }); }; Client.prototype.refreshConfig = function (callback) { var that = this; callback = typeof callback === 'function' ? callback : function () {}; this.config(function (error, result) { if (error) return callback(error); that.getUpdateInfo(function (error, info) { if (error) return callback(error); result.update = info.update; that.setConfig(result); callback(null); }); }); }; Client.prototype.refreshAvailableLanguages = function (callback) { var that = this; get('/api/v1/cloudron/languages', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); angular.copy(data.languages, that._availableLanguages); callback(null, data.languages); }); }; Client.prototype._appPostProcess = function (app) { // calculate the icon paths app.iconUrl = app.iconUrl ? (this.apiOrigin + app.iconUrl + '?access_token=' + token + '&ts=' + app.ts) : null; // amend the post install confirm state app.pendingPostInstallConfirmation = !!localStorage['confirmPostInstall_' + app.id]; if (app.manifest.upstreamVersion) { app.upstreamVersion = app.manifest.upstreamVersion; } else if (app.manifest.description) { // can be empty for dev apps var tmp = app.manifest.description.match(/\(.*)\<\/upstream\>/i); app.upstreamVersion = (tmp && tmp[1]) ? tmp[1] : ''; } else { app.upstreamVersion = ''; } if (!app.manifest.title) app.manifest.title = 'Untitled'; if (app.manifest.postInstallMessage) { var text= app.manifest.postInstallMessage; // we chose - because underscore has special meaning in markdown text = text.replace(/\$CLOUDRON-APP-LOCATION/g, app.subdomain); text = text.replace(/\$CLOUDRON-APP-DOMAIN/g, app.domain); text = text.replace(/\$CLOUDRON-APP-FQDN/g, app.fqdn); text = text.replace(/\$CLOUDRON-APP-ORIGIN/g, 'https://' + app.fqdn); text = text.replace(/\$CLOUDRON-API-DOMAIN/g, this._config.adminFqdn); text = text.replace(/\$CLOUDRON-API-ORIGIN/g, 'https://' + this._config.adminFqdn); text = text.replace(/\$CLOUDRON-USERNAME/g, this._userInfo.username); text = text.replace(/\$CLOUDRON-APP-ID/g, app.id); // [^] matches even newlines. '?' makes it non-greedy if (app.sso) text = text.replace(/[^]*?<\/nosso>/g, ''); else text = text.replace(/[^]*?<\/sso>/g, ''); app.manifest.postInstallMessage = text; } app.type = app.manifest.id === 'io.cloudron.builtin.appproxy' ? APP_TYPES.PROXIED : APP_TYPES.APP; return app; }; function binarySearch(array, pred) { var lo = -1, hi = array.length; while (1 + lo !== hi) { var mi = lo + ((hi - lo) >> 1); if (pred(array[mi])) { hi = mi; } else { lo = mi; } } return hi; } Client.prototype._updateAppCache = function (app) { var tmp = {}; angular.copy(app, tmp); var foundIndex = this._installedApps.findIndex(function (a) { return a.id === app.id; }); // we replace new data into the existing reference to keep angular bindings if (foundIndex !== -1) { angular.copy(tmp, this._installedApps[foundIndex]); } else { this._installedApps.push(tmp); foundIndex = this._installedApps.length-1; } // add reference to object map with appId keys this._installedAppsById[app.id] = this._installedApps[foundIndex]; // TODO this not very elegant // update app tags tmp = this._installedApps .map(function (app) { return app.tags || []; }) // return array of arrays .reduce(function (a, i) { return a.concat(i); }, []) // merge all arrays into one .filter(function (v, i, self) { return self.indexOf(v) === i; }) // filter duplicates .sort(function (a, b) { return a.localeCompare(b); }); // sort // keep tag array references angular.copy(tmp, this._appTags); }; Client.prototype.refreshInstalledApps = function (callback) { callback = callback || function (error) { if (error) console.error(error); }; var that = this; this.getApps(function (error, apps) { if (error) return callback(error); that.getApplinks(function (error, applinks) { if (error) return callback(error); apps = apps.concat(applinks); async.eachLimit(apps, 20, function (app, iteratorCallback) { app.ssoAuth = app.sso && (app.manifest.addons['ldap'] || app.manifest.addons['oidc'] || app.manifest.addons['proxyAuth']); // checking app.sso first ensures app.manifest.addons is not null if (app.accessLevel !== 'operator' && app.accessLevel !== 'admin') { // only fetch if we have permissions app.progress = 0; app.message = ''; app.taskMinutesActive = 0; that._updateAppCache(app); return iteratorCallback(); } var getTaskFunc = app.taskId ? that.getAppTask.bind(null, app.id) : function (next) { return next(); }; getTaskFunc(function (error, task) { if (error) return iteratorCallback(error); if (task) { app.progress = task.percent; app.message = task.message; app.taskMinutesActive = moment.duration(moment.utc().diff(moment.utc(task.creationTime))).asMinutes(); } else { app.progress = 0; app.message = ''; app.taskMinutesActive = 0; } that._updateAppCache(app); iteratorCallback(); }); }, function iteratorDone(error) { if (error) return callback(error); // filter out old apps, going backwards to allow splicing for (var i = that._installedApps.length - 1; i >= 0; --i) { if (!apps.some(function (elem) { return (elem.id === that._installedApps[i].id); })) { var removed = that._installedApps.splice(i, 1); delete that._installedAppsById[removed[0].id]; } } callback(null); }); }); }); }; Client.prototype.login = function () { this.setToken(null); localStorage.setItem('redirectToHash', window.location.hash); // start oidc flow window.location.href = this.apiOrigin + '/openid/auth?client_id=' + (window.cloudronApiOrigin ? TOKEN_TYPES.ID_DEVELOPMENT : TOKEN_TYPES.ID_WEBADMIN) + '&scope=openid email profile&response_type=code token&redirect_uri=' + window.location.origin + '/authcallback.html'; }; Client.prototype.logout = function () { var that = this; // destroy oidc session in the spirit of true SSO del('/api/v1/oidc/sessions', null, function (error, data, status) { if (error) console.error('Failed to logout from oidc session'); that.setToken(null); window.location.href = '/'; }); }; Client.prototype.getAppEventLog = function (appId, page, perPage, callback) { var config = { params: { page: page, per_page: perPage } }; get('/api/v1/apps/' + appId + '/eventlog', config, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.eventlogs); }); }; Client.prototype.sendTestMail = function (domain, to, callback) { var data = { to: to }; post('/api/v1/mail/' + domain + '/send_test_mail', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; // Domains Client.prototype.getDomains = function (callback) { get('/api/v1/domains', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.domains); }); }; Client.prototype.getDomain = function (domain, callback) { get('/api/v1/domains/' + domain, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.checkDNSRecords = function (domain, subdomain, callback) { get('/api/v1/domains/' + domain + '/dns_check?subdomain=' + subdomain, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.addDomain = function (domain, zoneName, provider, config, fallbackCertificate, tlsConfig, callback) { var data = { domain: domain, provider: provider, config: config, tlsConfig: tlsConfig, }; if (zoneName) data.zoneName = zoneName; var that = this; if (fallbackCertificate) data.fallbackCertificate = fallbackCertificate; // hack until we fix the domains.js post('/api/v1/domains', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 201) return callback(new ClientError(status, data)); callback(); }); }; Client.prototype.updateDomainConfig = function (domain, zoneName, provider, config, fallbackCertificate, tlsConfig, callback) { var data = { provider: provider, config: config, tlsConfig: tlsConfig }; if (zoneName) data.zoneName = zoneName; var that = this; if (fallbackCertificate) data.fallbackCertificate = fallbackCertificate; post('/api/v1/domains/' + domain + '/config', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); that.setDnsRecords({ domain: domain, type: 'mail' }, callback); // this is done so that an out-of-sync dkim key can be synced }); }; Client.prototype.updateDomainWellKnown = function (domain, wellKnown, callback) { var data = { wellKnown: wellKnown }; var that = this; post('/api/v1/domains/' + domain + '/wellknown', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.renewCerts = function (options, callback) { post('/api/v1/reverseproxy/renew_certs', options, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null, data.taskId); }); }; Client.prototype.removeDomain = function (domain, callback) { var config = { data: { }, headers: { 'Content-Type': 'application/json' } }; del('/api/v1/domains/' + domain, config, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.prepareDashboardDomain = function (domain, callback) { var data = { domain: domain }; post('/api/v1/dashboard/prepare_location', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null, data.taskId); }); }; Client.prototype.setDashboardDomain = function (domain, callback) { var data = { domain: domain }; post('/api/v1/dashboard/location', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null); }); }; // Email Client.prototype.getMailEventLogs = function (search, types, page, perPage, callback) { var config = { params: { page: page, types: types, per_page: perPage, search: search } }; get('/api/v1/mailserver/eventlog', config, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.eventlogs); }); }; Client.prototype.getMailUsage = function (domain, callback) { var config = { params: { domain: domain } }; get('/api/v1/mailserver/usage', config, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.usage); // { quotaValue (IEC/1024), quotaLimit (IEC/1024), quotaPercent } }); }; Client.prototype.getMailLocation = function (callback) { var config = {}; get('/api/v1/mailserver/location', config, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); // { subdomain, domain } }); }; Client.prototype.setMailLocation = function (subdomain, domain, callback) { post('/api/v1/mailserver/location', { subdomain: subdomain, domain: domain }, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null, { taskId: data.taskId }); }); }; Client.prototype.getMaxEmailSize = function (callback) { var config = {}; get('/api/v1/mailserver/max_email_size', config, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.size); }); }; Client.prototype.setMaxEmailSize = function (size, callback) { post('/api/v1/mailserver/max_email_size', { size: size }, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.getMailboxSharing = function (callback) { get('/api/v1/mailserver/mailbox_sharing', {}, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.enabled); }); }; Client.prototype.setMailboxSharing = function (enable, callback) { post('/api/v1/mailserver/mailbox_sharing', { enable: enable }, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.getVirtualAllMail = function (callback) { get('/api/v1/mailserver/virtual_all_mail', {}, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.enabled); }); }; Client.prototype.setVirtualAllMail = function (enable, callback) { post('/api/v1/mailserver/virtual_all_mail', { enable: enable }, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.getDnsblConfig = function (callback) { var config = {}; get('/api/v1/mailserver/dnsbl_config', config, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.setDnsblConfig = function (zones, callback) { post('/api/v1/mailserver/dnsbl_config', { zones: zones }, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.getFtsConfig = function (callback) { var config = {}; get('/api/v1/mailserver/fts_config', config, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.setFtsConfig = function (state, callback) { post('/api/v1/mailserver/fts_config', { enable: state }, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.getSpamAcl = function (callback) { var config = {}; get('/api/v1/mailserver/spam_acl', config, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.setSpamAcl = function (acl, callback) { post('/api/v1/mailserver/spam_acl', { allowlist: acl.allowlist, blocklist: acl.blocklist }, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.getSpamCustomConfig = function (callback) { var config = {}; get('/api/v1/mailserver/spam_custom_config', config, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.config); }); }; Client.prototype.setSpamCustomConfig = function (config, callback) { post('/api/v1/mailserver/spam_custom_config', { config: config }, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.listMailQueue = function (search, page, perPage, callback) { var config = { params: { search: search, page: page, per_page: perPage } }; get('/api/v1/mailserver/queue', config, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.queue); }); }; Client.prototype.delMailQueueItem = function (file, callback) { del('/api/v1/mailserver/queue/' + file, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.usage); }); }; Client.prototype.resendMailQueueItem = function (file, callback) { post('/api/v1/mailserver/queue/' + file + '/resend', {}, null, function (error, data, status) { if (error) return callback(error); if (status !== 201) return callback(new ClientError(status, data)); callback(null, data.usage); }); }; Client.prototype.getMailConfigForDomain = function (domain, callback) { get('/api/v1/mail/' + domain, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.enableMailForDomain = function (domain, enabled, callback) { post('/api/v1/mail/' + domain + '/enable', { enabled: enabled }, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.setDnsRecords = function (options, callback) { post('/api/v1/domains/sync_dns', options, null, function (error, data, status) { if (error) return callback(error); if (status !== 201) return callback(new ClientError(status, data)); callback(null, data.taskId); }); }; Client.prototype.getMailStatusForDomain = function (domain, callback) { get('/api/v1/mail/' + domain + '/status', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.setMailRelay = function (domain, data, callback) { post('/api/v1/mail/' + domain + '/relay', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.setMailBanner = function (domain, data, callback) { post('/api/v1/mail/' + domain + '/banner', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.setCatchallAddresses = function (domain, addresses, callback) { post('/api/v1/mail/' + domain + '/catch_all', { addresses: addresses }, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.setMailFromValidation = function (domain, enabled, callback) { post('/api/v1/mail/' + domain + '/mail_from_validation', { enabled: enabled }, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; // Mailboxes Client.prototype.getAllMailboxes = function (callback) { var that = this; this.getDomains(function (error, domains) { if (error) return callback(error); var mailboxes = []; async.eachLimit(domains, 5, function (domain, callback) { that.listMailboxes(domain.domain, '', 1, 1000, function (error, result) { if (error) return callback(error); mailboxes = mailboxes.concat(result); callback(); }); }, function (error) { if (error) return callback(error); callback(null, mailboxes); }); }); }; Client.prototype.getMailboxCount = function (domain, callback) { get('/api/v1/mail/' + domain + '/mailbox_count', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.count); }); }; Client.prototype.listMailboxes = function (domain, search, page, perPage, callback) { var config = { params: { search: search, page: page, per_page: perPage } }; get('/api/v1/mail/' + domain + '/mailboxes', config, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.mailboxes); }); }; Client.prototype.getMailbox = function (domain, name, callback) { get('/api/v1/mail/' + domain + '/mailboxes/' + name, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.mailbox); }); }; Client.prototype.addMailbox = function (domain, name, ownerId, ownerType, callback) { var data = { name: name, ownerId: ownerId, ownerType: ownerType, active: true, storageQuota: 0, messagesQuota: 0 }; post('/api/v1/mail/' + domain + '/mailboxes', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 201) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.updateMailbox = function (domain, name, data, callback) { post('/api/v1/mail/' + domain + '/mailboxes/' + name, data, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.removeMailbox = function (domain, name, deleteMails, callback) { var config = { data: { deleteMails: deleteMails }, headers: { 'Content-Type': 'application/json' } }; del('/api/v1/mail/' + domain + '/mailboxes/' + name, config, function (error, data, status) { if (error) return callback(error); if (status !== 201) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.getAliases = function (name, domain, callback) { var config = { params: { page: 1, per_page: 1000 } }; get('/api/v1/mail/' + domain + '/mailboxes/' + name + '/aliases', config, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.aliases); }); }; Client.prototype.setAliases = function (name, domain, aliases, callback) { var data = { aliases: aliases }; put('/api/v1/mail/' + domain + '/mailboxes/' + name + '/aliases', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.listMailingLists = function (domain, search, page, perPage, callback) { var config = { params: { search: search, page: page, per_page: perPage } }; get('/api/v1/mail/' + domain + '/lists', config, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.lists); }); }; Client.prototype.getMailingList = function (domain, name, callback) { get('/api/v1/mail/' + domain + '/lists/' + name, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.list); }); }; Client.prototype.addMailingList = function (domain, name, members, membersOnly, callback) { var data = { name: name, members: members, membersOnly: membersOnly, active: true }; post('/api/v1/mail/' + domain + '/lists', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 201) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.updateMailingList = function (domain, name, members, membersOnly, active, callback) { var data = { members: members, membersOnly: membersOnly, active: active }; post('/api/v1/mail/' + domain + '/lists/' + name, data, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.removeMailingList = function (domain, name, callback) { del('/api/v1/mail/' + domain + '/lists/' + name, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null); }); }; // Volumes Client.prototype.getVolumes = function (callback) { get('/api/v1/volumes', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.volumes); }); }; Client.prototype.getVolume = function (volume, callback) { get('/api/v1/volumes/' + volume, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.getVolumeStatus = function (volume, callback) { get('/api/v1/volumes/' + volume + '/status', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.addVolume = function (name, mountType, mountOptions, callback) { var data = { name: name, mountType: mountType, mountOptions: mountOptions }; post('/api/v1/volumes', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 201) return callback(new ClientError(status, data)); callback(null, data.id); }); }; Client.prototype.updateVolume = function (volumeId, mountOptions, callback) { var data = { mountOptions: mountOptions }; post('/api/v1/volumes/' + volumeId, data, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(); }); }; Client.prototype.remountVolume = function (volumeId, callback) { post('/api/v1/volumes/' + volumeId + '/remount', {}, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(); }); }; Client.prototype.removeVolume = function (volume, callback) { var config = { data: { }, headers: { 'Content-Type': 'application/json' } }; del('/api/v1/volumes/' + volume, config, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); callback(null); }); }; // This will change the location Client.prototype.openSubscriptionSetup = function (subscription) { // we only allow the owner to do so if (!this._userInfo.isAtLeastOwner) return; // basically the user has not setup appstore account yet if (!subscription.plan) return window.location.href = '/#/appstore'; window.open(this.getConfig().consoleServerOrigin + '/#/cloudron/' + subscription.cloudronId + '?email=' + subscription.emailEncoded, '_blank'); }; Client.prototype.getAppstoreAppByIdAndVersion = function (appId, version, callback) { var url = '/api/v1/appstore/apps/' + appId; if (version && version !== 'latest') url += '/versions/' + version; get(url, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype._onAppstoreApps = function (callback) { if (!this._fetchingAppstoreApps) { callback(); } else this._fetchingAppstoreAppsListener.push(callback); }; Client.prototype.getAppstoreApps = function (callback) { var that = this; callback = callback || function () {}; get('/api/v1/appstore/apps', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); angular.copy(data.apps, that._appstoreAppCache); return callback(null, that._appstoreAppCache); }); }; Client.prototype.getAppstoreAppsFast = function (callback) { if (this._appstoreAppCache.length !== 0) return callback(null, this._appstoreAppCache); this.getAppstoreApps(callback); }; Client.prototype.getSubscription = function (callback) { get('/api/v1/appstore/subscription', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); // just some helper property, since angular bindings cannot dot his easily data.emailEncoded = encodeURIComponent(data.email); callback(null, data); // { email, plan: { id, name }, cancel_at, status } }); }; Client.prototype.registerCloudronWithSetupToken = function (setupToken, callback) { var data = { setupToken: setupToken }; post('/api/v1/appstore/register_cloudron_with_setup_token', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 201) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.registerCloudron = function (email, password, totpToken, signup, callback) { var data = { email: email, password: password, signup: signup }; if (totpToken) data.totpToken = totpToken; post('/api/v1/appstore/register_cloudron', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 201) return callback(new ClientError(status, data)); callback(null); }); }; // ---------------------------------------------- // Eventlog helpers // ---------------------------------------------- Client.prototype.eventLogDetails = function (eventLog, appIdContext) { var ACTION_ACTIVATE = 'cloudron.activate'; var ACTION_PROVISION = 'cloudron.provision'; var ACTION_RESTORE = 'cloudron.restore'; var ACTION_APP_CLONE = 'app.clone'; var ACTION_APP_REPAIR = 'app.repair'; var ACTION_APP_CONFIGURE = 'app.configure'; var ACTION_APP_INSTALL = 'app.install'; var ACTION_APP_RESTORE = 'app.restore'; var ACTION_APP_IMPORT = 'app.import'; var ACTION_APP_UNINSTALL = 'app.uninstall'; var ACTION_APP_UPDATE = 'app.update'; var ACTION_APP_UPDATE_FINISH = 'app.update.finish'; var ACTION_APP_BACKUP = 'app.backup'; var ACTION_APP_BACKUP_FINISH = 'app.backup.finish'; var ACTION_APP_LOGIN = 'app.login'; var ACTION_APP_OOM = 'app.oom'; var ACTION_APP_UP = 'app.up'; var ACTION_APP_DOWN = 'app.down'; var ACTION_APP_START = 'app.start'; var ACTION_APP_STOP = 'app.stop'; var ACTION_APP_RESTART = 'app.restart'; var ACTION_ARCHIVES_ADD = 'archives.add'; var ACTION_ARCHIVES_DEL = 'archives.del'; var ACTION_BACKUP_FINISH = 'backup.finish'; var ACTION_BACKUP_START = 'backup.start'; var ACTION_BACKUP_CLEANUP_START = 'backup.cleanup.start'; var ACTION_BACKUP_CLEANUP_FINISH = 'backup.cleanup.finish'; var ACTION_BRANDING_AVATAR = 'branding.avatar'; var ACTION_BRANDING_NAME = 'branding.name'; var ACTION_BRANDING_FOOTER = 'branding.footer'; var ACTION_CERTIFICATE_NEW = 'certificate.new'; var ACTION_CERTIFICATE_RENEWAL = 'certificate.renew'; var ACTION_CERTIFICATE_CLEANUP = 'certificate.cleanup'; var ACTION_DASHBOARD_DOMAIN_UPDATE = 'dashboard.domain.update'; var ACTION_DIRECTORY_SERVER_CONFIGURE = 'directoryserver.configure'; var ACTION_DOMAIN_ADD = 'domain.add'; var ACTION_DOMAIN_UPDATE = 'domain.update'; var ACTION_DOMAIN_REMOVE = 'domain.remove'; var ACTION_EXTERNAL_LDAP_CONFIGURE = 'externalldap.configure'; var ACTION_GROUP_ADD = 'group.add'; var ACTION_GROUP_UPDATE = 'group.update'; var ACTION_GROUP_REMOVE = 'group.remove'; var ACTION_GROUP_MEMBERSHIP = 'group.membership'; var ACTION_INSTALL_FINISH = 'cloudron.install.finish'; var ACTION_START = 'cloudron.start'; var ACTION_SERVICE_CONFIGURE = 'service.configure'; var ACTION_SERVICE_REBUILD = 'service.rebuild'; var ACTION_SERVICE_RESTART = 'service.restart'; var ACTION_UPDATE = 'cloudron.update'; var ACTION_UPDATE_FINISH = 'cloudron.update.finish'; var ACTION_USER_ADD = 'user.add'; var ACTION_USER_LOGIN = 'user.login'; var ACTION_USER_LOGIN_GHOST = 'user.login.ghost'; var ACTION_USER_LOGOUT = 'user.logout'; var ACTION_USER_REMOVE = 'user.remove'; var ACTION_USER_UPDATE = 'user.update'; var ACTION_USER_TRANSFER = 'user.transfer'; var ACTION_USER_DIRECTORY_PROFILE_CONFIG_UPDATE = 'userdirectory.profileconfig.update'; var ACTION_MAIL_LOCATION = 'mail.location'; var ACTION_MAIL_ENABLED = 'mail.enabled'; var ACTION_MAIL_DISABLED = 'mail.disabled'; var ACTION_MAIL_MAILBOX_ADD = 'mail.box.add'; var ACTION_MAIL_MAILBOX_UPDATE = 'mail.box.update'; var ACTION_MAIL_MAILBOX_REMOVE = 'mail.box.remove'; var ACTION_MAIL_LIST_ADD = 'mail.list.add'; var ACTION_MAIL_LIST_UPDATE = 'mail.list.update'; var ACTION_MAIL_LIST_REMOVE = 'mail.list.remove'; var ACTION_SUPPORT_TICKET = 'support.ticket'; var ACTION_SUPPORT_SSH = 'support.ssh'; var ACTION_VOLUME_ADD = 'volume.add'; var ACTION_VOLUME_UPDATE = 'volume.update'; var ACTION_VOLUME_REMOVE = 'volume.remove'; var ACTION_DYNDNS_UPDATE = 'dyndns.update'; var data = eventLog.data; var errorMessage = data.errorMessage; var details, app; function appName(pre, app, defaultValue) { if (appIdContext) return defaultValue || ''; pre = pre ? (pre + ' ') : ''; return pre + (app.label || app.fqdn || app.subdomain) + ' (' + app.manifest.title + ') '; } function eventBy() { if (eventLog.source && eventLog.source.username) return ' by ' + eventLog.source.username; return ''; } switch (eventLog.action) { case ACTION_ACTIVATE: return 'Cloudron was activated'; case ACTION_PROVISION: return 'Cloudron was setup'; case ACTION_RESTORE: return 'Cloudron was restored using backup at ' + data.remotePath; case ACTION_APP_CONFIGURE: { if (!data.app) return ''; app = data.app; if ('accessRestriction' in data) { // since it can be null return 'Access restriction ' + appName('of', app) + ' was changed'; } else if ('operators' in data) { return 'Operators ' + appName('of', app) + ' was changed'; } else if (data.label) { return `Label ${appName('of', app)} was set to ${data.label}`; } else if (data.tags) { return `Tags ${appName('of', app)} was set to ${data.tags.join(', ')}`; } else if (data.icon) { return 'Icon ' + appName('of', app) + ' was changed'; } else if (data.memoryLimit) { return 'Memory limit ' + appName('of', app) + ' was set to ' + prettyBinarySize(data.memoryLimit); } else if (data.cpuShares) { // replaced by cpuQuota in 8.0 return 'CPU shares ' + appName('of', app) + ' was set to ' + Math.round((data.cpuShares * 100)/1024) + '%'; } else if (data.cpuQuota) { return 'CPU quota ' + appName('of', app) + ' was set to ' + data.cpuQuota + '%'; } else if (data.env) { return 'Env vars ' + appName('of', app) + ' was changed'; } else if ('debugMode' in data) { // since it can be null if (data.debugMode) { return appName('', app, 'App') + ' was placed in repair mode'; } else { return appName('', app, 'App') + ' was taken out of repair mode'; } } else if ('enableBackup' in data) { return 'Automatic backups ' + appName('of', app) + ' was ' + (data.enableBackup ? 'enabled' : 'disabled'); } else if ('enableAutomaticUpdate' in data) { return 'Automatic updates ' + appName('of', app) + ' was ' + (data.enableAutomaticUpdate ? 'enabled' : 'disabled'); } else if ('reverseProxyConfig' in data) { return 'Reverse proxy configuration ' + appName('of', app) + ' was updated'; } else if ('upstreamUri' in data) { return 'Upstream URI ' + appName('of', app) + ' was updated'; } else if ('cert' in data) { if (data.cert) { return 'Custom certificate was set ' + appName('for', app); } else { return 'Certificate ' + appName('of', app) + ' was reset'; } } else if (data.subdomain) { if (data.fqdn !== data.app.fqdn) { return 'Location ' + appName('of', app) + ' was changed to ' + data.fqdn; } else if (!angular.equals(data.redirectDomains, data.app.redirectDomains)) { var altFqdns = data.redirectDomains.map(function (a) { return a.fqdn; }); return 'Alternate domains ' + appName('of', app) + ' was ' + (altFqdns.length ? 'set to ' + altFqdns.join(', ') : 'reset'); } else if (!angular.equals(data.aliasDomains, data.app.aliasDomains)) { var aliasDomains = data.aliasDomains.map(function (a) { return a.fqdn; }); return 'Alias domains ' + appName('of', app) + ' was ' + (aliasDomains.length ? 'set to ' + aliasDomains.join(', ') : 'reset'); } else if (!angular.equals(data.portBindings, data.app.portBindings)) { return 'Port bindings ' + appName('of', app) + ' was changed'; } } else if ('dataDir' in data) { if (data.dataDir) { return 'Data directory ' + appName('of', app) + ' was set ' + data.dataDir; } else { return 'Data directory ' + appName('of', app) + ' was reset'; } } else if ('icon' in data) { if (data.icon) { return 'Icon ' + appName('of', app) + ' was set'; } else { return 'Icon ' + appName('of', app) + ' was reset'; } } else if ('mailboxName' in data) { if (data.mailboxName) { return `Mailbox ${appName('of', app)} was set to ${data.mailboxDisplayName || '' } ${data.mailboxName}@${data.mailboxDomain}`; } else { return 'Mailbox ' + appName('of', app) + ' was disabled'; } } return appName('', app, 'App ') + 'was re-configured'; } case ACTION_APP_INSTALL: if (!data.app) return ''; return data.app.manifest.title + ' (package v' + data.app.manifest.version + ') was installed ' + appName('at', data.app) + eventBy(); case ACTION_APP_RESTORE: if (!data.app) return ''; details = appName('', data.app, 'App') + ' was restored'; // older versions (<3.5) did not have these fields if (data.fromManifest) details += ' from version ' + data.fromManifest.version; if (data.toManifest) details += ' to version ' + data.toManifest.version; if (data.remotePath) details += ' using backup at ' + data.remotePath; return details; case ACTION_APP_IMPORT: if (!data.app) return ''; details = appName('', data.app, 'App') + ' was imported'; if (data.toManifest) details += ' to version ' + data.toManifest.version; if (data.remotePath) details += ' using backup at ' + data.remotePath; return details; case ACTION_APP_UNINSTALL: if (!data.app) return ''; return appName('', data.app, 'App') + ' (package v' + data.app.manifest.version + ') was uninstalled'; case ACTION_APP_UPDATE: if (!data.app) return ''; return 'Update ' + appName('of', data.app) + ' started from v' + data.fromManifest.version + ' to v' + data.toManifest.version; case ACTION_APP_UPDATE_FINISH: if (!data.app) return ''; return appName('', data.app, 'App') + ' was updated to v' + data.app.manifest.version; case ACTION_APP_BACKUP: if (!data.app) return ''; return 'Backup ' + appName('of', data.app) + ' started'; case ACTION_APP_BACKUP_FINISH: if (!data.app) return ''; if (data.errorMessage) { return 'Backup ' + appName('of', data.app) + ' failed: ' + data.errorMessage; } else { return 'Backup ' + appName('of', data.app) + ' succeeded with backup id ' + data.backupId + ' at ' + data.remotePath; } case ACTION_APP_CLONE: if (appIdContext === data.oldAppId) { return 'App was cloned to ' + data.newApp.fqdn + ' using backup at ' + data.remotePath; } else if (appIdContext === data.appId) { return 'App was cloned from ' + data.oldApp.fqdn + ' using backup at ' + data.remotePath; } else { return appName('', data.newApp, 'App') + ' was cloned ' + appName('from', data.oldApp) + ' using backup at ' + data.remotePath; } case ACTION_APP_REPAIR: return appName('', data.app, 'App') + ' was re-configured'; // re-configure of email apps is more common? case ACTION_APP_LOGIN: { app = this.getCachedAppSync(data.appId); if (!app) return ''; return 'App ' + app.fqdn + ' logged in'; } case ACTION_APP_OOM: if (!data.app) return ''; return appName('', data.app, 'App') + ' ran out of memory'; case ACTION_APP_DOWN: if (!data.app) return ''; return appName('', data.app, 'App') + ' is down'; case ACTION_APP_UP: if (!data.app) return ''; return appName('', data.app, 'App') + ' is back online'; case ACTION_APP_START: if (!data.app) return ''; return appName('', data.app, 'App') + ' was started'; case ACTION_APP_STOP: if (!data.app) return ''; return appName('', data.app, 'App') + ' was stopped'; case ACTION_APP_RESTART: if (!data.app) return ''; return appName('', data.app, 'App') + ' was restarted'; case ACTION_ARCHIVES_ADD: return 'Backup ' + data.backupId + ' added to archive'; case ACTION_ARCHIVES_DEL: return 'Backup ' + data.backupId + ' deleted from archive'; case ACTION_BACKUP_START: return 'Backup started'; case ACTION_BACKUP_FINISH: if (!errorMessage) { return 'Cloudron backup created at ' + data.remotePath; } else { return 'Cloudron backup errored with error: ' + errorMessage; } case ACTION_BACKUP_CLEANUP_START: return 'Backup cleaner started'; case ACTION_BACKUP_CLEANUP_FINISH: return data.errorMessage ? 'Backup cleaner errored: ' + data.errorMessage : 'Backup cleaner removed ' + (data.removedBoxBackupPaths ? data.removedBoxBackupPaths.length : '0') + ' backups'; case ACTION_BRANDING_AVATAR: return 'Cloudron Avatar Changed'; case ACTION_BRANDING_NAME: return 'Cloudron Name set to ' + data.name; case ACTION_BRANDING_FOOTER: return 'Cloudron Footer set to ' + data.footer; case ACTION_CERTIFICATE_NEW: return 'Certificate install for ' + data.domain + (errorMessage ? ' failed' : ' succeeded'); case ACTION_CERTIFICATE_RENEWAL: return 'Certificate renewal for ' + data.domain + (errorMessage ? ' failed' : ' succeeded'); case ACTION_CERTIFICATE_CLEANUP: return 'Certificate(s) of ' + data.domains.join(',') + ' was cleaned up since they expired 6 months ago'; case ACTION_DASHBOARD_DOMAIN_UPDATE: return 'Dashboard domain set to ' + data.fqdn || (data.subdomain + '.' + data.domain); case ACTION_DIRECTORY_SERVER_CONFIGURE: if (data.fromEnabled !== data.toEnabled) { return 'Directory server was ' + (data.toEnabled ? 'enabled' : 'disabled'); } else { return 'Directory server configuration was changed'; } case ACTION_DOMAIN_ADD: return 'Domain ' + data.domain + ' with ' + data.provider + ' provider was added'; case ACTION_DOMAIN_UPDATE: return 'Domain ' + data.domain + ' with ' + data.provider + ' provider was updated'; case ACTION_DOMAIN_REMOVE: return 'Domain ' + data.domain + ' was removed'; case ACTION_EXTERNAL_LDAP_CONFIGURE: if (data.config.provider === 'noop') { return 'External Directory disabled'; } else { return 'External Directory set to ' + data.config.url + ' (' + data.config.provider + ')'; } case ACTION_GROUP_ADD: return 'Group ' + data.name + ' was added'; case ACTION_GROUP_UPDATE: return 'Group name changed from ' + data.oldName + ' to ' + data.group.name; case ACTION_GROUP_REMOVE: return 'Group ' + data.group.name + ' was removed'; case ACTION_GROUP_MEMBERSHIP: return 'Group membership of ' + data.group.name + ' changed. Now was ' + data.userIds.length + ' member(s).'; case ACTION_INSTALL_FINISH: return 'Cloudron version ' + data.version + ' installed'; case ACTION_MAIL_LOCATION: return 'Mail server location was changed to ' + data.subdomain + (data.subdomain ? '.' : '') + data.domain; case ACTION_MAIL_ENABLED: return 'Mail was enabled for domain ' + data.domain; case ACTION_MAIL_DISABLED: return 'Mail was disabled for domain ' + data.domain; case ACTION_MAIL_MAILBOX_ADD: return 'Mailbox ' + data.name + '@' + data.domain + ' was added'; case ACTION_MAIL_MAILBOX_UPDATE: if (data.aliases) { return 'Mailbox aliases of ' + data.name + '@' + data.domain + ' was updated'; } else { return 'Mailbox ' + data.name + '@' + data.domain + ' was updated'; } case ACTION_MAIL_MAILBOX_REMOVE: return 'Mailbox ' + data.name + '@' + data.domain + ' was removed'; case ACTION_MAIL_LIST_ADD: return 'Mail list ' + data.name + '@' + data.domain + 'was added'; case ACTION_MAIL_LIST_UPDATE: return 'Mail list ' + data.name + '@' + data.domain + ' was updated'; case ACTION_MAIL_LIST_REMOVE: return 'Mail list ' + data.name + '@' + data.domain + ' was removed'; case ACTION_START: return 'Cloudron started with version ' + data.version; case ACTION_SERVICE_CONFIGURE: return 'Service ' + data.id + ' was configured'; case ACTION_SERVICE_REBUILD: return 'Service ' + data.id + ' was rebuilt'; case ACTION_SERVICE_RESTART: return 'Service ' + data.id + ' was restarted'; case ACTION_UPDATE: return 'Cloudron update to version ' + data.boxUpdateInfo.version + ' was started'; case ACTION_UPDATE_FINISH: if (data.errorMessage) { return 'Cloudron update errored. Error: ' + data.errorMessage; } else { return 'Cloudron updated to version ' + data.newVersion; } case ACTION_USER_ADD: return 'User ' + data.email + (data.user.username ? ' (' + data.user.username + ')' : '') + ' was added'; case ACTION_USER_UPDATE: return 'User ' + (data.user ? (data.user.email + (data.user.username ? ' (' + data.user.username + ')' : '')) : data.userId) + ' was updated'; case ACTION_USER_REMOVE: return 'User ' + (data.user ? (data.user.email + (data.user.username ? ' (' + data.user.username + ')' : '')) : data.userId) + ' was removed'; case ACTION_USER_TRANSFER: return 'Apps of ' + data.oldOwnerId + ' was transferred to ' + data.newOwnerId; case ACTION_USER_LOGIN: if (data.mailboxId) { return 'User ' + (data.user ? data.user.username : data.userId) + ' logged in to mailbox ' + data.mailboxId; } else if (data.appId) { app = this.getCachedAppSync(data.appId); return 'User ' + (data.user ? data.user.username : data.userId) + ' logged in to ' + (app ? app.fqdn : data.appId); } else { // can happen with directoryserver return 'User ' + (data.user ? data.user.username : data.userId) + ' authenticated'; } case ACTION_USER_LOGIN_GHOST: return 'User ' + (data.user ? data.user.username : data.userId) + ' was impersonated'; case ACTION_USER_LOGOUT: return 'User ' + (data.user ? data.user.username : data.userId) + ' logged out'; case ACTION_USER_DIRECTORY_PROFILE_CONFIG_UPDATE: return 'User directory profile config updated. Mandatory 2FA: ' + (data.config.mandatory2FA) + ' Lock profiles: ' + (data.config.lockUserProfiles); case ACTION_DYNDNS_UPDATE: { details = data.errorMessage ? 'Error updating DNS. ' : 'Updated DNS. '; if (data.fromIpv4 !== data.toIpv4) details += 'From IPv4 ' + data.fromIpv4 + ' to ' + data.toIpv4 + '. '; if (data.fromIpv6 !== data.toIpv6) details += 'From IPv6 ' + data.fromIpv6 + ' to ' + data.toIpv6 + '.'; if (data.errorMessage) details += ' ' + data.errorMessage; return details; } case ACTION_SUPPORT_SSH: return 'Remote Support was ' + (data.enable ? 'enabled' : 'disabled'); case ACTION_SUPPORT_TICKET: return 'Support ticket was created'; case ACTION_VOLUME_ADD: return 'Volume "' + data.volume.name + '" was added'; case ACTION_VOLUME_UPDATE: return 'Volme "' + data.volume.name + '" was updated'; case ACTION_VOLUME_REMOVE: return 'Volume "' + data.volume.name + '" was removed'; default: return eventLog.action; } }; Client.prototype.eventLogSource = function (eventLog) { var source = eventLog.source; var line = ''; line = source.username || source.userId || source.mailboxId || source.authType || 'system'; if (source.appId) { var app = this.getCachedAppSync(source.appId); line += ' - ' + (app ? app.fqdn : source.appId); } return line; }; client = new Client(); return client; }]);