'use strict'; /* global $ */ /* global angular */ /* global EventSource */ /* global async */ // 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_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 tasks.js var TASK_TYPES = { TASK_APP: 'app', TASK_BACKUP: 'backup', TASK_UPDATE: 'update', TASK_CHECK_CERTS: 'checkCerts', TASK_SETUP_DNS_AND_CERT: 'setupDnsAndCert', TASK_CLEAN_BACKUPS: 'cleanBackups', TASK_SYNC_EXTERNAL_LDAP: 'syncExternalLdap', TASK_CHANGE_MAIL_LOCATION: 'changeMailLocation', TASK_SYNC_DNS_RECORDS: 'syncDnsRecords', TASK_UPDATE_DISK_USAGE: 'updateDiskUsage', }; 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' } ]; // 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: '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: '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' } ]; const REGIONS_LINODE = [ { name: 'Atlanta', value: 'us-southeast-1.linodeobjects.com', region: 'us-east-1' }, // default { name: 'Newark', value: 'us-east-1.linodeobjects.com', region: 'us-east-1' }, { name: 'Frankfurt', value: 'eu-central-1.linodeobjects.com', region: 'us-east-1' }, { name: 'Singapore', value: 'ap-south-1.linodeobjects.com', region: 'us-east-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.cloud.ovh.net', region: 'bhs' }, // default { name: 'Frankfurt (DE)', value: 'https://s3.de.cloud.ovh.net', region: 'de' }, { name: 'Gravelines (GRA)', value: 'https://s3.gra.cloud.ovh.net', region: 'gra' }, { name: 'Strasbourg (SBG)', value: 'https://s3.sbg.cloud.ovh.net', region: 'sbg' }, { name: 'London (UK)', value: 'https://s3.uk.cloud.ovh.net', region: 'uk' }, { name: 'Sydney (SYD)', value: 'https://s3.syd.cloud.ovh.net', region: 'syd' }, { name: 'Warsaw (WAW)', value: 'https://s3.waw.cloud.ovh.net', region: 'waw' }, ]; // https://devops.ionos.com/api/s3/ const REGIONS_IONOS = [ { name: 'DE', value: 'https://s3-de-central.profitbricks.com', region: 's3-de-central' }, // default ]; // 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' }, ]; 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: 'DigitalOcean Spaces', value: 'digitalocean-spaces' }, { 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: '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(2) * 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) { 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?' + '<%= revision %>' }); $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); // ---------------------------------------------- // Cloudron REST API wrapper // ---------------------------------------------- angular.module('Application').service('Client', ['$http', '$interval', '$timeout', 'md5', 'Notification', function ($http, $interval, $timeout, md5, Notification) { 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 head(url, config, callback) { if (arguments.length !== 3) { console.error('HEAD', arguments); throw('Wrong number of arguments'); } config = config || {}; config.headers = config.headers || {}; config.headers.Authorization = 'Bearer ' + token; return $http.head(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, hasBackgroundImage: false }; 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 = '<%= apiOrigin %>' || window.location.origin; this.avatar = ''; 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 + '?s=128&default=mp&ts=' + Date.now(); // we add the timestamp to avoid caching this._userInfo.hasBackgroundImage = userInfo.hasBackgroundImage; 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 is just for easier testing // this._config.features.externalLdap = false; 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; }; Client.prototype.makeURL = function (url) { if (url.indexOf('?') === -1) { return this.apiOrigin + url + '?access_token=' + token; } else { return this.apiOrigin + url + '&access_token=' + token; } }; /* * Rest API wrappers */ Client.prototype.config = function (callback) { get('/api/v1/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.userInfo = 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.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, title, config, callback) { var data = { appStoreId: id + '@' + manifest.version, subdomain: config.subdomain, domain: config.domain, secondaryDomains: config.secondaryDomains, portBindings: config.portBindings, accessRestriction: config.accessRestriction, cert: config.cert, key: config.key, sso: config.sso, overwriteDns: config.overwriteDns, upstreamUri: config.upstreamUri }; post('/api/v1/apps/install', 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, portBindings: config.portBindings, 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.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.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.createExec = function (id, options, callback) { post('/api/v1/apps/' + id + '/exec', options, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.id); }); }; Client.prototype.version = function (callback) { get('/api/v1/cloudron/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.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/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) { post('/api/v1/settings/backup_config', backupConfig, 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/settings/backup_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.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.getSupportConfig = function (callback) { get('/api/v1/settings/support_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.setExternalLdapConfig = function (config, callback) { post('/api/v1/settings/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/settings/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/settings/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/settings/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/settings/user_directory_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/settings/user_directory_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.setSysinfoConfig = function (config, callback) { post('/api/v1/settings/sysinfo_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.getSysinfoConfig = function (callback) { get('/api/v1/settings/sysinfo_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/cloudron/server_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/cloudron/server_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) { var config = {}; get('/api/v1/network/blocklist', config, 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.setDynamicDnsConfig = function (enabled, callback) { post('/api/v1/settings/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/settings/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/settings/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/settings/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.setUnstableAppsConfig = function (enabled, callback) { post('/api/v1/settings/unstable_apps', { 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.getUnstableAppsConfig = function (callback) { get('/api/v1/settings/unstable_apps', 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.setRegistryConfig = function (rc, callback) { post('/api/v1/settings/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/settings/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) { if (!this._userInfo.isAtLeastAdmin) return callback(new Error('Not allowed')); get('/api/v1/cloudron/update', 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/cloudron/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/settings/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/settings/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/settings/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/settings/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/settings/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/settings/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.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.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.getTaskLogs = function (taskId, follow, lines, callback) { if (follow) { var eventSource = new EventSource(client.apiOrigin + '/api/v1/tasks/' + taskId + '/logstream?lines=' + lines + '&access_token=' + token); callback(null, eventSource); } else { get('/api/v1/services/' + taskId + '/logs?lines=' + lines, 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 (backupConfig, remotePath, version, sysinfoConfig, skipDnsSetup, setupToken, callback) { var data = { backupConfig: backupConfig, remotePath: remotePath, version: version, sysinfoConfig: sysinfoConfig, skipDnsSetup: skipDnsSetup, setupToken: setupToken }; post('/api/v1/cloudron/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/cloudron/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/cloudron/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.getPlatformLogs = function (unit, follow, lines, callback) { if (follow) { var eventSource = new EventSource(client.apiOrigin + '/api/v1/cloudron/logstream/' + unit + '?lines=' + lines + '&access_token=' + token); callback(null, eventSource); } else { get('/api/v1/cloudron/logs/' + unit + '?lines=' + lines, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); } }; Client.prototype.getServiceLogs = function (serviceName, follow, lines, callback) { if (follow) { var eventSource = new EventSource(client.apiOrigin + '/api/v1/services/' + serviceName + '/logstream?lines=' + lines + '&access_token=' + token); callback(null, eventSource); } else { get('/api/v1/services/' + serviceName + '/logs?lines=' + lines, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); } }; 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.getAppLogs = function (appId, follow, lines, callback) { if (follow) { var eventSource = new EventSource(client.apiOrigin + '/api/v1/apps/' + appId + '/logstream?lines=' + lines + '&access_token=' + token); callback(null, eventSource); } else { get('/api/v1/apps/' + appId + '/logs', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); } }; 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.setGroups = function (userId, groupIds, callback) { put('/api/v1/users/' + userId + '/groups', { groupIds: groupIds }, 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.updateGroup = function (id, name, callback) { var data = { name: name }; post('/api/v1/groups/' + id, 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.getAppLimits = function (appId, callback) { get('/api/v1/apps/' + appId + '/limits', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data.limits); }); }; 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.setup = function (data, callback) { post('/api/v1/cloudron/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.createAdmin = function (data, callback) { var that = this; post('/api/v1/cloudron/activate', data, null, function (error, result, status) { if (error) return callback(error); if (status !== 201) return callback(new ClientError(status, result)); that.setToken(result.token); that.setUserInfo({ username: data.username, email: data.email, admin: true, twoFactorAuthenticationEnabled: false, source: '', avatarUrl: null }); callback(null, result.activated); }); }; 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.destroyOidcSession = function (callback) { del('/api/v1/oidc/sessions', 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 (id, name, secret, loginRedirectUri, logoutRedirectUri, tokenSignatureAlgorithm, callback) { var data = { id: id, name: name, secret: secret, loginRedirectUri: loginRedirectUri, tokenSignatureAlgorithm: tokenSignatureAlgorithm }; if (logoutRedirectUri) data.logoutRedirectUri = logoutRedirectUri; 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, secret, loginRedirectUri, logoutRedirectUri, tokenSignatureAlgorithm, callback) { var data = { secret: secret, name: name, loginRedirectUri: loginRedirectUri, tokenSignatureAlgorithm, tokenSignatureAlgorithm }; if (logoutRedirectUri) data.logoutRedirectUri = logoutRedirectUri; 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/cloudron/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/cloudron/reboot', 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/cloudron/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.setCertificate = function (certificateFile, keyFile, callback) { var data = { cert: certificateFile, key: keyFile }; post('/api/v1/settings/certificate', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.getBlockDevices = function (callback) { get('/api/v1/cloudron/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.disks = function (callback) { get('/api/v1/cloudron/disks', null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.diskUsage = function (callback) { get('/api/v1/cloudron/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/cloudron/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.memory = function (callback) { get('/api/v1/cloudron/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/cloudron/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; // this fqdn may contain the protocol! 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 !== 202) 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.updateUser = function (user, callback) { var data = { email: user.email, displayName: user.displayName, fallbackEmail: user.fallbackEmail, active: user.active, role: user.role }; if (user.username) data.username = user.username; post('/api/v1/users/' + user.id, data, 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.updateProfile = function (data, callback) { post('/api/v1/profile', 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.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/backgroundImage?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/backgroundImage', fd, config, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.makeUserLocal = function (userId, callback) { post('/api/v1/users/' + userId + '/make_local', {}, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) 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/cloudron/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 !== 201) 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 !== 202) 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 !== 202) 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/cloudron/sync_external_ldap', {}, 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.refreshUserInfo = function (callback) { var that = this; callback = typeof callback === 'function' ? callback : function () {}; this.userInfo(function (error, result) { if (error) return callback(error); 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) { // note: non-admin users may get access denied for this if (!error) result.update = info.update; // attach update information to config object 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.manifest.addons['ldap'] || app.manifest.addons['oidc'] || app.manifest.addons['proxyAuth']) && app.sso; 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); window.location.href = '/login.html?returnTo=/' + encodeURIComponent(window.location.hash); }; Client.prototype.logout = function () { var token = this.getToken(); this.setToken(null); // invalidates the token window.location.href = client.apiOrigin + '/api/v1/cloudron/logout?access_token=' + token; }; 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.uploadFile = function (appId, file, progressCallback, callback) { var fd = new FormData(); fd.append('file', file); var config = { headers: { 'Content-Type': undefined }, transformRequest: angular.identity, uploadEventHandlers: { progress: progressCallback } }; post('/api/v1/apps/' + appId + '/upload?file=' + encodeURIComponent('/tmp/' + file.name), fd, config, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); callback(null); }); }; Client.prototype.checkDownloadableFile = function (appId, filePath, callback) { var config = { headers: { 'Content-Type': undefined } }; head('/api/v1/apps/' + appId + '/download?file=' + encodeURIComponent(filePath), config, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null); }); }; 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/cloudron/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/cloudron/prepare_dashboard_domain', 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/cloudron/set_dashboard_domain', 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.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.getSolrConfig = function (callback) { var config = {}; get('/api/v1/mailserver/solr_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.setSolrConfig = function (enabled, callback) { post('/api/v1/mailserver/solr_config', { 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.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', { whitelist: acl.whitelist, blacklist: acl.blacklist }, 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/cloudron/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, mountType, mountOptions, callback) { var data = { mountType: mountType, mountOptions: mountOptions }; post('/api/v1/volumes/' + volumeId, data, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) 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'; if (subscription.plan.id === 'free') window.open(this.getConfig().consoleServerOrigin + '/#/subscription_setup/' + subscription.cloudronId + '?email=' + subscription.emailEncoded, '_blank'); else 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) {console.log('not fetching'); 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.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); }); }; // FileManager API // mode can be 'download', 'open', 'link' or 'data' function getObjpath(id, type) { if (type === 'mail') return 'mailserver'; if (type === 'app') return 'apps/' + id; if (type === 'volume') return 'volumes/' + id; } Client.prototype.filesGetLink = function (id, type, path) { var objpath = getObjpath(id, type); return client.apiOrigin + '/api/v1/' + objpath + '/files/' + path + '?download=false&access_token=' + token; }; Client.prototype.filesGet = function (id, type, path, mode, callback) { var objpath = getObjpath(id, type); if (mode === 'download') { window.open(client.apiOrigin + '/api/v1/' + objpath + '/files/' + path + '?download=true&access_token=' + token); callback(null); } else if (mode === 'open') { window.open(client.apiOrigin + '/api/v1/' + objpath + '/files/' + path + '?download=false&access_token=' + token); callback(null); } else { function responseHandler(data, headers, status) { if (headers()['content-type'] && headers()['content-type'].indexOf('application/json') !== -1) return JSON.parse(data); return data; } get('/api/v1/' + objpath + '/files/' + path, { transformResponse: responseHandler }, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); } }; Client.prototype.filesRemove = function (id, type, path, callback) { var objpath = getObjpath(id, type); del('/api/v1/' + objpath + '/files/' + path, null, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.filesExtract = function (id, type, path, callback) { var objpath = getObjpath(id, type); put('/api/v1/' + objpath + '/files/' + path, { action: 'extract' }, {}, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.filesChown = function (id, type, path, uid, recursive, callback) { var objpath = getObjpath(id, type); put('/api/v1/' + objpath + '/files/' + path, { action: 'chown', uid: uid, recursive: recursive }, {}, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.filesRename = function (id, type, path, newPath, callback) { var objpath = getObjpath(id, type); put('/api/v1/' + objpath + '/files/' + path, { action: 'rename', newFilePath: decodeURIComponent(newPath) }, {}, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.filesCopy = function (id, type, path, newPath, callback) { var that = this; var objpath = getObjpath(id, type); put('/api/v1/' + objpath + '/files/' + path, { action: 'copy', newFilePath: decodeURIComponent(newPath) }, {}, function (error, data, status) { if (error && error.statusCode === 409) return that.filesCopy(id, type, path, newPath + '-copy', callback); if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.filesCreateDirectory = function (id, type, path, callback) { var objpath = getObjpath(id, type); post('/api/v1/' + objpath + '/files/' + path, { directory: decodeURIComponent(path) }, {}, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.filesCreateFile = function (id, type, path, callback) { var objpath = getObjpath(id, type); post('/api/v1/' + objpath + '/files/' + path, {}, {}, function (error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); }); }; Client.prototype.filesUpload = function (id, type, path, file, overwrite, progressHandler, callback) { var objpath = getObjpath(id, type); var fd = new FormData(); fd.append('file', file); if (overwrite) fd.append('overwrite', 'true'); function done(error, data, status) { if (error) return callback(error); if (status !== 200) return callback(new ClientError(status, data)); callback(null, data); } $http({ url: client.apiOrigin + '/api/v1/' + objpath + '/files/' + path, method: 'POST', data: fd, transformRequest: angular.identity, headers: { 'Content-Type': undefined, Authorization: 'Bearer ' + token }, uploadEventHandlers: { progress: function (e) { progressHandler(e.loaded); } } }).success(defaultSuccessHandler(done)).error(defaultErrorHandler(done)); }; // ---------------------------------------------- // 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_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_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_DOMAIN_ADD = 'domain.add'; var ACTION_DOMAIN_UPDATE = 'domain.update'; var ACTION_DOMAIN_REMOVE = 'domain.remove'; 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_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 ACTION_SYSTEM_CRASH = 'system.crash'; 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 + ') '; } 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; var q = function (x) { return '"' + x + '"'; }; 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 ' + q(data.label); } else if (data.tags) { return 'Tags ' + appName('of', app) + ' was set to ' + q(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 ' + data.memoryLimit; } else if (data.cpuShares) { return 'CPU shares ' + appName('of', app) + ' was set to ' + Math.round((data.cpuShares * 100)/1024) + '%'; } 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) + ' were ' + (data.enableBackup ? 'enabled' : 'disabled'); } else if ('enableAutomaticUpdate' in data) { return 'Automatic updates ' + appName('of', app) + ' were ' + (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) && data.mailboxName !== data.app.mailboxName) { if (data.mailboxName) { return 'Mailbox ' + appName('of', app) + ' was set to ' + q(data.mailboxName); } else { return 'Mailbox ' + appName('of', app) + ' was reset'; } } 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); 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_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_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; 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_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: return 'User ' + (data.user ? data.user.username : data.userId) + ' logged in'; 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_DYNDNS_UPDATE: { details = ''; if (data.fromIpv4 !== data.toIpv4) details += 'DNS was updated from IPv4 ' + data.fromIpv4 + ' to ' + data.toIpv4 + '. '; if (data.fromIpv6 !== data.toIpv6) details += 'DNS was updated from IPv6 ' + data.fromIpv6 + ' to ' + data.toIpv6 + '.'; 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_SYSTEM_CRASH: return 'A system process crashed'; 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); } else if (source.ip) { line += ' - ' + source.ip; } return line; }; client = new Client(); return client; }]);