Files
cloudron-box/dashboard/public/js/index.js
2024-10-04 17:43:45 +02:00

851 lines
32 KiB
JavaScript

'use strict';
/* global $, async, angular, redirectIfNeeded */
/* global ERROR,ISTATES,HSTATES,RSTATES,APP_TYPES,NOTIFICATION_TYPES */
// deal with accessToken in the query, this is passed for example on password reset and account setup upon invite
var search = decodeURIComponent(window.location.search).slice(1).split('&').map(function (item) { return item.split('='); }).reduce(function (o, k) { o[k[0]] = k[1]; return o; }, {});
if (search.accessToken) {
localStorage.token = search.accessToken;
// strip the accessToken and expiresAt, then preserve the rest
delete search.accessToken;
delete search.expiresAt;
// this will reload the page as this is not a hash change
window.location.search = encodeURIComponent(Object.keys(search).map(function (key) { return key + '=' + search[key]; }).join('&'));
}
// create main application module
var app = angular.module('Application', ['pascalprecht.translate', 'ngCookies', 'ngFitText', 'ngRoute', 'ngAnimate', 'ngSanitize', 'angular-md5', 'base64', 'slick', 'ui-notification', 'ui.bootstrap', 'ui.multiselect']);
app.config(['NotificationProvider', function (NotificationProvider) {
NotificationProvider.setOptions({
delay: 5000,
startTop: 60,
positionX: 'left',
templateUrl: 'notification.html'
});
}]);
// configure resourceUrlWhitelist https://code.angularjs.org/1.5.8/docs/api/ng/provider/$sceDelegateProvider#resourceUrlWhitelist
app.config(function ($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist([
// Allow same origin resource loads.
'self',
// Allow loading from our assets domain.
'https://cloudron.io/**',
'https://staging.cloudron.io/**',
'https://dev.cloudron.io/**',
// Allow local development against the appstore pages
'http://localhost:5000/**'
]);
});
// setup all major application routes
app.config(['$routeProvider', function ($routeProvider) {
$routeProvider.when('/', {
redirectTo: '/apps'
}).when('/users', {
controller: 'UsersController',
templateUrl: 'views/users.html?<%= revision %>'
}).when('/usersettings', {
controller: 'UserSettingsController',
templateUrl: 'views/user-settings.html?<%= revision %>'
}).when('/app/:appId/:view?', {
controller: 'AppController',
templateUrl: 'views/app.html?<%= revision %>'
}).when('/appstore', {
controller: 'AppStoreController',
templateUrl: 'views/appstore.html?<%= revision %>'
}).when('/appstore/:appId', {
controller: 'AppStoreController',
templateUrl: 'views/appstore.html?<%= revision %>'
}).when('/apps', {
controller: 'AppsController',
templateUrl: 'views/apps.html?<%= revision %>'
}).when('/profile', {
controller: 'ProfileController',
templateUrl: 'views/profile.html?<%= revision %>'
}).when('/backups', {
controller: 'BackupsController',
templateUrl: 'views/backups.html?<%= revision %>'
}).when('/branding', {
controller: 'BrandingController',
templateUrl: 'views/branding.html?<%= revision %>'
}).when('/network', {
controller: 'NetworkController',
templateUrl: 'views/network.html?<%= revision %>'
}).when('/domains', {
controller: 'DomainsController',
templateUrl: 'views/domains.html?<%= revision %>'
}).when('/email', {
controller: 'EmailsController',
templateUrl: 'views/emails.html?<%= revision %>'
}).when('/emails-eventlog', {
controller: 'EmailsEventlogController',
templateUrl: 'views/emails-eventlog.html?<%= revision %>'
}).when('/emails-queue', {
controller: 'EmailsQueueController',
templateUrl: 'views/emails-queue.html?<%= revision %>'
}).when('/email/:domain/:view?', {
controller: 'EmailController',
templateUrl: 'views/email.html?<%= revision %>'
}).when('/notifications', {
controller: 'NotificationsController',
templateUrl: 'views/notifications.html?<%= revision %>'
}).when('/oidc', {
redirectTo: '/usersettings'
}).when('/settings', {
controller: 'SettingsController',
templateUrl: 'views/settings.html?<%= revision %>'
}).when('/eventlog', {
controller: 'EventLogController',
templateUrl: 'views/eventlog.html?<%= revision %>'
}).when('/support', {
controller: 'SupportController',
templateUrl: 'views/support.html?<%= revision %>'
}).when('/system', {
controller: 'SystemController',
templateUrl: 'views/system.html?<%= revision %>'
}).when('/services', {
controller: 'ServicesController',
templateUrl: 'views/services.html?<%= revision %>'
}).when('/volumes', {
controller: 'VolumesController',
templateUrl: 'views/volumes.html?<%= revision %>'
}).otherwise({ redirectTo: '/'});
}]);
app.filter('notificationTypeToColor', function () {
return function (n) {
switch (n.type) {
case NOTIFICATION_TYPES.ALERT_REBOOT:
case NOTIFICATION_TYPES.ALERT_APP_OOM:
case NOTIFICATION_TYPES.ALERT_MAIL_STATUS:
case NOTIFICATION_TYPES.ALERT_CERTIFICATE_RENEWAL_FAILED:
case NOTIFICATION_TYPES.ALERT_DISK_SPACE:
case NOTIFICATION_TYPES.ALERT_BACKUP_CONFIG:
case NOTIFICATION_TYPES.ALERT_BACKUP_FAILED:
return '#ff4c4c';
case NOTIFICATION_TYPES.ALERT_BOX_UPDATE:
case NOTIFICATION_TYPES.ALERT_MANUAL_APP_UPDATE:
return '#f0ad4e';
default:
return '#2196f3';
}
};
});
app.filter('capitalize', function () {
return function (s) {
return s.charAt(0).toUpperCase() + s.slice(1);
};
});
app.filter('activeTask', function () {
return function (app) {
if (!app) return false;
return app.taskId !== null;
};
});
app.filter('installSuccess', function () {
return function (app) {
if (!app) return false;
return app.installationState === ISTATES.INSTALLED;
};
});
app.filter('appIsInstalledAndHealthy', function () {
return function (app) {
if (!app) return false;
return (app.installationState === ISTATES.INSTALLED && app.health === HSTATES.HEALTHY && app.runState === RSTATES.RUNNING);
};
});
app.filter('applicationLink', function () {
return function(app) {
if (!app) return '';
// app links have http already in the fqdn
if (app.fqdn.indexOf('http') !== 0) return 'https://' + app.fqdn;
return app.fqdn;
};
});
app.filter('userManagementFilter', function () {
return function(apps, option) {
return apps.filter(function (app) {
if (option.id === '') return true;
if (option.id === 'sso') return !!(app.manifest.optionalSso || app.manifest.addons.ldap || app.manifest.addons.proxyAuth);
if (option.id === 'nosso') return app.manifest.optionalSso || (!app.manifest.addons.ldap && !app.manifest.addons.proxyAuth);
if (option.id === 'email') return !!app.manifest.addons.email;
return false;
});
};
});
// this appears when an item in app grid is clicked
app.filter('prettyAppErrorMessage', function () {
return function (app) {
if (!app) return '';
if (app.installationState === ISTATES.INSTALLED) {
// app.health can also be null to indicate insufficient data
if (app.health === HSTATES.UNHEALTHY) return 'The app is not responding to health checks. Check the logs for any error messages.';
}
if (app.error.reason === 'Access Denied') {
if (app.error.domain) return 'The DNS record for this location is not setup correctly. Please verify your DNS settings and repair this app.';
} else if (app.error.reason === 'Already Exists') {
if (app.error.domain) return 'The DNS record for this location already exists. Cloudron does not remove existing DNS records. Manually remove the DNS record and then click on repair.';
}
return app.error.message;
};
});
// this appears as tool tip in app grid
app.filter('appProgressMessage', function () {
return function (app) {
var message = app.message || (app.error ? app.error.message : '');
return message;
};
});
// see apps.js $scope.states
app.filter('selectedStateFilter', ['Client', function (Client) {
return function selectedStateFilter(apps, selectedState) {
return apps.filter(function (app) {
if (!selectedState || !selectedState.state) return true;
if (selectedState.state === 'running') return app.runState === RSTATES.RUNNING && app.health === HSTATES.HEALTHY && app.installationState === ISTATES.INSTALLED;
if (selectedState.state === 'stopped') return app.runState === RSTATES.STOPPED;
if (selectedState.state === 'update_available') return !!(Client.getConfig().update[app.id] && Client.getConfig().update[app.id].manifest.version && Client.getConfig().update[app.id].manifest.version !== app.manifest.version);
return app.runState === RSTATES.RUNNING && (app.health !== HSTATES.HEALTHY || app.installationState !== ISTATES.INSTALLED); // not responding
});
};
}]);
app.filter('selectedGroupAccessFilter', function () {
return function selectedGroupAccessFilter(apps, group) {
return apps.filter(function (app) {
if (!group.id) return true; // case for no filter entry
if (!app.accessRestriction) return true;
if (!app.accessRestriction.groups) return false;
if (app.accessRestriction.groups.indexOf(group.id) !== -1) return true;
return false;
});
};
});
app.filter('selectedTagFilter', function () {
return function selectedTagFilter(apps, selectedTags) {
return apps.filter(function (app) {
if (selectedTags.length === 0) return true;
if (!app.tags) return false;
for (var i = 0; i < selectedTags.length; i++) {
if (app.tags.indexOf(selectedTags[i]) === -1) return false;
}
return true;
});
};
});
app.filter('selectedDomainFilter', function () {
return function selectedDomainFilter(apps, selectedDomain) {
return apps.filter(function (app) {
if (selectedDomain._alldomains) return true; // magic domain for single select, see apps.js ALL_DOMAINS_DOMAIN
if (app.type === APP_TYPES.LINK) return false;
if (selectedDomain.domain === app.domain) return true;
if (app.aliasDomains.find(function (ad) { return ad.domain === selectedDomain.domain; })) return true;
if (app.redirectDomains.find(function (ad) { return ad.domain === selectedDomain.domain; })) return true;
return false;
});
};
});
app.filter('appSearchFilter', function () {
return function appSearchFilter(apps, appSearch) {
return apps.filter(function (app) {
if (!appSearch) return true;
appSearch = appSearch.toLowerCase();
return app.fqdn.indexOf(appSearch) !== -1
|| (app.label && app.label.toLowerCase().indexOf(appSearch) !== -1)
|| (app.manifest.title && app.manifest.title.toLowerCase().indexOf(appSearch) !== -1)
|| (appSearch.length >=6 && app.id.indexOf(appSearch) !== -1);
});
};
});
app.filter('prettyDomains', function () {
return function prettyDomains(domains) {
return domains.map(function (d) { return d.domain; }).join(', ');
};
});
app.filter('installationActive', function () {
return function (app) {
if (app.installationState === ISTATES.ERROR) return false;
if (app.installationState === ISTATES.INSTALLED) return false;
return true;
};
});
// color indicator in app list
app.filter('installationStateClass', function () {
const ERROR_CLASS = 'status-error';
const BUSY_CLASS = 'status-starting fa-beat-fade';
const INACTIVE_CLASS = 'status-inactive';
const ACTIVE_CLASS = 'status-active';
return function(app) {
if (!app) return '';
switch (app.installationState) {
case ISTATES.ERROR: return ERROR_CLASS;
case ISTATES.INSTALLED: {
if (app.debugMode) {
return INACTIVE_CLASS;
} else {
if (app.runState === RSTATES.RUNNING) {
if (!app.health) return BUSY_CLASS; // no data yet
if (app.type === APP_TYPES.LINK || app.health === HSTATES.HEALTHY) return ACTIVE_CLASS;
return ERROR_CLASS; // dead/exit/unhealthy
} else {
return INACTIVE_CLASS;
}
}
}
default: return BUSY_CLASS;
}
};
});
// this appears in the app grid
app.filter('installationStateLabel', function () {
return function(app) {
if (!app) return '';
var waiting = app.progress === 0 ? ' (Queued)' : '';
switch (app.installationState) {
case ISTATES.PENDING_INSTALL:
return 'Installing' + waiting;
case ISTATES.PENDING_CLONE:
return 'Cloning' + waiting;
case ISTATES.PENDING_LOCATION_CHANGE:
case ISTATES.PENDING_CONFIGURE:
case ISTATES.PENDING_RECREATE_CONTAINER:
case ISTATES.PENDING_SERVICES_CHANGE:
case ISTATES.PENDING_DEBUG:
return 'Configuring' + waiting;
case ISTATES.PENDING_RESIZE:
return 'Resizing' + waiting;
case ISTATES.PENDING_DATA_DIR_MIGRATION:
return 'Migrating data' + waiting;
case ISTATES.PENDING_UNINSTALL: return 'Uninstalling' + waiting;
case ISTATES.PENDING_RESTORE: return 'Restoring' + waiting;
case ISTATES.PENDING_IMPORT: return 'Importing' + waiting;
case ISTATES.PENDING_UPDATE: return 'Updating' + waiting;
case ISTATES.PENDING_BACKUP: return 'Backing up' + waiting;
case ISTATES.PENDING_START: return 'Starting' + waiting;
case ISTATES.PENDING_STOP: return 'Stopping' + waiting;
case ISTATES.PENDING_RESTART: return 'Restarting' + waiting;
case ISTATES.ERROR: {
if (app.error && app.error.message === 'ETRYAGAIN') return 'DNS Error';
return 'Error';
}
case ISTATES.INSTALLED: {
if (app.debugMode) {
return 'Recovery Mode';
} else if (app.runState === RSTATES.RUNNING) {
if (!app.health) return 'Starting...'; // no data yet
if (app.type === APP_TYPES.LINK) return '';
if (app.health === HSTATES.HEALTHY) return 'Running';
return 'Not responding'; // dead/exit/unhealthy
} else if (app.runState === RSTATES.STOPPED) return 'Stopped';
else return app.runState;
}
default: return app.installationState;
}
};
});
app.filter('taskName', function () {
return function(installationState) {
switch (installationState) {
case ISTATES.PENDING_INSTALL: return 'install';
case ISTATES.PENDING_CLONE: return 'clone';
case ISTATES.PENDING_LOCATION_CHANGE: return 'location change';
case ISTATES.PENDING_CONFIGURE: return 'configure';
case ISTATES.PENDING_RECREATE_CONTAINER: return 'create container';
case ISTATES.PENDING_DEBUG: return 'debug';
case ISTATES.PENDING_RESIZE: return 'resize';
case ISTATES.PENDING_DATA_DIR_MIGRATION: return 'data migration';
case ISTATES.PENDING_UNINSTALL: return 'uninstall';
case ISTATES.PENDING_RESTORE: return 'restore';
case ISTATES.PENDING_IMPORT: return 'import';
case ISTATES.PENDING_UPDATE: return 'update';
case ISTATES.PENDING_BACKUP: return 'backup';
case ISTATES.PENDING_START: return 'start app';
case ISTATES.PENDING_STOP: return 'stop app';
case ISTATES.PENDING_RESTART: return 'restart app';
default: return installationState || '';
}
};
});
app.filter('errorSuggestion', function () {
return function (error) {
if (!error) return '';
switch (error.reason) {
case ERROR.ACCESS_DENIED:
if (error.domain) return 'Check the DNS credentials of ' + error.domain.domain + ' in the Domains & Certs view';
return '';
case ERROR.COLLECTD_ERROR: return 'Check if collectd is running on the server';
case ERROR.DATABASE_ERROR: return 'Check if MySQL database is running on the server';
case ERROR.DOCKER_ERROR: return 'Check if docker is running on the server';
case ERROR.DNS_ERROR: return 'Check if the DNS service of the domain is running';
case ERROR.LOGROTATE_ERROR: return 'Check if logrotate is running on the server';
case ERROR.NETWORK_ERROR: return 'Check if there are any network issues on the server';
case ERROR.REVERSEPROXY_ERROR: return 'Check if nginx is running on the server';
default: return '';
}
};
});
app.filter('canUpdate', function () {
return function (apps) {
return apps.every(function (app) {
return (app.installationState === ISTATES.ERROR) || (app.installationState === ISTATES.INSTALLED);
});
};
});
app.filter('inProgressApps', function () {
return function (apps) {
return apps.filter(function (app) {
return app.installationState !== ISTATES.ERROR && app.installationState !== ISTATES.INSTALLED;
});
};
});
app.filter('prettyHref', function () {
return function (input) {
if (!input) return input;
if (input.indexOf('http://') === 0) return input.slice('http://'.length);
if (input.indexOf('https://') === 0) return input.slice('https://'.length);
return input;
};
});
app.filter('prettyEmailAddresses', function () {
return function prettyEmailAddresses(addresses) {
if (!addresses) return '';
if (addresses === '<>') return '<>';
if (Array.isArray(addresses)) return addresses.map(function (a) { return a.slice(1, -1); }).join(', ');
return addresses.slice(1, -1);
};
});
// custom directive for dynamic names in forms
// See http://stackoverflow.com/questions/23616578/issue-registering-form-control-with-interpolated-name#answer-23617401
app.directive('laterName', function () { // (2)
return {
restrict: 'A',
require: ['?ngModel', '^?form'], // (3)
link: function postLink(scope, elem, attrs, ctrls) {
attrs.$set('name', attrs.laterName);
var modelCtrl = ctrls[0]; // (3)
var formCtrl = ctrls[1]; // (3)
if (modelCtrl && formCtrl) {
modelCtrl.$name = attrs.name; // (4)
formCtrl.$addControl(modelCtrl); // (2)
scope.$on('$destroy', function () {
formCtrl.$removeControl(modelCtrl); // (5)
});
}
}
};
});
app.run(['$route', '$rootScope', '$location', function ($route, $rootScope, $location) {
var original = $location.path;
$location.path = function (path, reload) {
if (reload === false) {
var lastRoute = $route.current;
var un = $rootScope.$on('$locationChangeSuccess', function () {
$route.current = lastRoute;
un();
});
}
return original.apply($location, [path]);
};
}]);
app.directive('ngClickSelect', function () {
return {
restrict: 'AC',
link: function (scope, element/*, attrs */) {
element.bind('click', function () {
var selection = window.getSelection();
var range = document.createRange();
range.selectNodeContents(this);
selection.removeAllRanges();
selection.addRange(range);
});
}
};
});
// handles various states and triggers a href or configure/repair view
// used by attaching to controller $scope
// if $scope.appPostInstallConfirm.show(app); exists it will be called if not yet confirmed
function onAppClick(app, $event, isOperator, $scope) {
function stopEvent() {
$event.originalEvent.stopPropagation();
$event.originalEvent.preventDefault();
}
if (app.installationState !== ISTATES.INSTALLED) {
if (app.installationState === ISTATES.ERROR && isOperator) $scope.showAppConfigure(app, 'repair');
return stopEvent();
}
// app.health can also be null to indicate insufficient data
if (!app.health) return stopEvent();
if (app.runState === RSTATES.STOPPED) return stopEvent();
if (app.runState === RSTATES.STOPPED) return stopEvent();
if (app.health === HSTATES.UNHEALTHY || app.health === HSTATES.ERROR || app.health === HSTATES.DEAD) {
if (isOperator) $scope.showAppConfigure(app, 'repair');
return stopEvent();
}
if (app.pendingPostInstallConfirmation && $scope.appPostInstallConfirm) {
$scope.appPostInstallConfirm.show(app);
return stopEvent();
}
}
app.directive('ngClickReveal', function () {
return {
restrict: 'A',
link: function (scope, element, attrs) {
element.addClass('hand');
var value = '';
scope.$watch(attrs.ngClickReveal, function (newValue, oldValue) {
if (newValue !== oldValue) {
element.html('<i>hidden</i>');
value = newValue;
}
});
element.bind('click', function () {
element.text(value);
});
}
};
});
// https://codepen.io/webmatze/pen/isuHh
app.directive('tagInput', function () {
return {
restrict: 'E',
scope: {
inputTags: '=taglist'
},
require: '^form',
link: function ($scope, element, attrs, formCtrl) {
$scope.defaultWidth = 200;
$scope.tagText = ''; // current tag being edited
$scope.placeholder = attrs.placeholder;
$scope.tagArray = function () {
if ($scope.inputTags === undefined) {
return [];
}
return $scope.inputTags.split(' ').filter(function (tag) {
return tag !== '';
});
};
$scope.addTag = function () {
var tagArray = $scope.tagArray();
// prevent adding empty or existing items
if ($scope.tagText.length === 0 || tagArray.indexOf($scope.tagText) !== -1) {
return $scope.tagText = '';
}
tagArray.push($scope.tagText);
$scope.inputTags = tagArray.join(' ');
return $scope.tagText = '';
};
$scope.deleteTag = function (key) {
var tagArray;
tagArray = $scope.tagArray();
if (tagArray.length > 0 && $scope.tagText.length === 0 && key === undefined) {
tagArray.pop();
} else {
if (key !== undefined) {
tagArray.splice(key, 1);
}
}
formCtrl.$setDirty();
return $scope.inputTags = tagArray.join(' ');
};
$scope.$watch('tagText', function (newVal, oldVal) {
var tempEl;
if (!(newVal === oldVal && newVal === undefined)) {
tempEl = $('<span>' + newVal + '</span>').appendTo('body');
$scope.inputWidth = tempEl.width() + 5;
if ($scope.inputWidth < $scope.defaultWidth) {
$scope.inputWidth = $scope.defaultWidth;
}
return tempEl.remove();
}
});
element.bind('click', function () {
element[0].firstChild.lastChild.focus();
});
element.bind('keydown', function (e) {
var key = e.which;
if (key === 9 || key === 13) {
e.preventDefault();
}
if (key === 8) {
return $scope.$apply('deleteTag()');
}
});
element.bind('keyup', function (e) {
var key = e.which;
if (key === 9 || key === 13 || key === 32) {
e.preventDefault();
return $scope.$apply('addTag()');
}
});
},
template:
'<div class="tag-input-container">' +
'<div class="btn-group input-tag" data-ng-repeat="tag in tagArray()">' +
'<button type="button" class="btn btn-xs btn-primary" disabled>{{ tag }}</button>' +
'<button type="button" class="btn btn-xs btn-primary" data-ng-click="deleteTag($index)">&times;</button>' +
'</div>' +
'<input type="text" data-ng-model="tagText" ng-blur="addTag()" placeholder="{{placeholder}}"/>' +
'</div>'
};
});
app.config(['fitTextConfigProvider', function (fitTextConfigProvider) {
fitTextConfigProvider.config = {
loadDelay: 250,
compressor: 0.9,
min: 8,
max: 24
};
}]);
app.controller('MainController', ['$scope', '$route', '$timeout', '$location', '$interval', 'Notification', 'Client', function ($scope, $route, $timeout, $location, $interval, Notification, Client) {
$scope.initialized = false; // used to animate the UI
$scope.user = Client.getUserInfo();
$scope.installedApps = Client.getInstalledApps();
$scope.config = {};
$scope.client = Client;
$scope.subscription = {};
$scope.notificationCount = 0;
$scope.hideNavBarActions = $location.path() === '/logs';
$scope.backgroundImageUrl = '';
$scope.reboot = {
busy: false,
show: function () {
$scope.reboot.busy = false;
$('#rebootModal').modal('show');
},
submit: function () {
$scope.reboot.busy = true;
Client.reboot(function (error) {
if (error) return Client.error(error);
$('#rebootModal').modal('hide');
// trigger refetch to show offline banner
$timeout(function () { Client.getStatus(function () {}); }, 5000);
});
}
};
$scope.isActive = function (url) {
if (!$route.current) return false;
return $route.current.$$route.originalPath.indexOf(url) === 0;
};
$scope.logout = function (event) {
event.stopPropagation();
$scope.initialized = false;
Client.logout();
};
$scope.openSubscriptionSetup = function () {
Client.openSubscriptionSetup($scope.subscription);
};
// NOTE: this function is exported and called from the appstore.js
$scope.updateSubscriptionStatus = function () {
Client.getSubscription(function (error, subscription) {
if (error && error.statusCode === 412) return; // not yet registered
if (error && error.statusCode === 402) return; // invalid appstore token
if (error) return console.error(error);
$scope.subscription = subscription;
});
};
function refreshNotifications() {
if (!Client.getUserInfo().isAtLeastAdmin) return;
Client.getNotifications({ acknowledged: false }, 1, 100, function (error, results) { // counter maxes out at 100
if (error) console.error(error);
else $scope.notificationCount = results.length;
});
}
// update state of acknowledged notification
$scope.notificationAcknowledged = function () {
refreshNotifications();
};
function redirectOnMandatory2FA() {
if (Client.getConfig().mandatory2FA) {
if (Client.getUserInfo().twoFactorAuthenticationEnabled) return; // user already has 2fa
if (Client.getUserInfo().source && $scope.config.external2FA) return; // 2fa is external
$location.path('/profile').search({ setup2fa: true });
}
}
// Make it redirect if the browser URL is changed directly - https://forum.cloudron.io/topic/7510/bug-in-2fa-force
$scope.$on('$routeChangeStart', function (/* event */) {
if ($scope.initialized) redirectOnMandatory2FA();
});
var gPlatformStatusNotification = null;
function trackPlatformStatus() {
Client.getPlatformStatus(function (error, result) {
if (error) return console.error('Failed to get platform status.', error);
// see box/src/platform.js
if (result.message === 'Ready') {
if (gPlatformStatusNotification) {
gPlatformStatusNotification.kill();
gPlatformStatusNotification = null;
}
return;
}
if (!gPlatformStatusNotification) {
var options = { title: 'Platform status', message: result.message, delay: 'notimeout', replaceMessage: true, closeOnClick: false };
Notification.primary(options).then(function (result) {
gPlatformStatusNotification = result;
$timeout(trackPlatformStatus, 5000);
});
} else {
gPlatformStatusNotification.message = result.message;
$timeout(trackPlatformStatus, 5000);
}
});
}
// this loads the very first thing when accessing via IP or domain
function init() {
Client.getProvisionStatus(function (error, status) {
if (error) return Client.initError(error, init);
if (redirectIfNeeded(status, 'dashboard')) return; // we got redirected...
// check version and force reload if needed
if (!localStorage.version) {
localStorage.version = status.version;
} else if (localStorage.version !== status.version) {
localStorage.version = status.version;
window.location.reload(true);
}
console.log('Running dashboard version ', localStorage.version);
// get user profile as the first thing. this populates the "scope" and affects subsequent API calls
async.series([
Client.refreshProfile.bind(Client),
Client.refreshConfig.bind(Client),
Client.refreshAvailableLanguages.bind(Client),
Client.refreshInstalledApps.bind(Client)
], function (error) {
if (error) return Client.initError(error, init);
// now mark the Client to be ready
Client.setReady();
$scope.config = Client.getConfig();
if (Client.getUserInfo().hasBackgroundImage) {
document.getElementById('mainContentContainer').style.backgroundImage = 'url("' + Client.getBackgroundImageUrl() + '")';
document.getElementById('mainContentContainer').classList.add('has-background');
}
$scope.initialized = true;
redirectOnMandatory2FA();
$interval(refreshNotifications, 60 * 1000);
refreshNotifications();
Client.getSubscription(function (error, subscription) {
if (error && error.statusCode === 412) return; // not yet registered
if (error && error.statusCode === 402) return; // invalid appstore token
if (error) return console.error(error);
$scope.subscription = subscription;
// only track platform status if we are registered
trackPlatformStatus();
});
});
});
}
Client.onConfig(function (config) {
if (config.cloudronName) {
document.title = config.cloudronName;
}
});
init();
// setup all the dialog focus handling
['updateModal'].forEach(function (id) {
$('#' + id).on('shown.bs.modal', function () {
$(this).find('[autofocus]:first').focus();
});
});
}]);