Do not redirect to error.html if the angular main application fails to init

We now only show the offline banner and retry the application init until
box comes back up
This commit is contained in:
Johannes Zellner
2019-09-05 22:22:42 +02:00
parent 8b8b137cad
commit b6e00a3107
9 changed files with 154 additions and 222 deletions

View File

@@ -1,91 +0,0 @@
<!DOCTYPE html>
<html ng-app="Application" ng-controller="Controller">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height" />
<title> Cloudron Error </title>
<link id="favicon" type="image/png" rel="icon" href="/api/v1/cloudron/avatar">
<!-- Theme CSS -->
<link type="text/css" rel="stylesheet" href="/theme.css">
<!-- external fonts and CSS -->
<link type="text/css" rel="stylesheet" href="/3rdparty/fontawesome/css/all.min.css">
<!-- jQuery-->
<script type="text/javascript" src="/3rdparty/js/jquery.min.js"></script>
<!-- Bootstrap Core JavaScript -->
<script type="text/javascript" src="/3rdparty/js/bootstrap.min.js"></script>
<!-- Angularjs scripts -->
<script type="text/javascript" src="/3rdparty/js/angular.min.js"></script>
<script type="text/javascript" src="/3rdparty/js/angular-loader.min.js"></script>
<script>
'use strict';
// create main application module
var app = angular.module('Application', []);
app.controller('Controller', ['$scope', '$http', function ($scope, $http) {
$scope.errorMessage = '';
$scope.statusOk = false;
$scope.avatarUrl = '/api/v1/cloudron/avatar?' + Math.random();
var favicon = $('#favicon');
if (favicon) favicon.attr('href', $scope.avatarUrl);
// try to fetch the cloudron status
$http.get('/api/v1/cloudron/status').success(function(data, status) {
if (status !== 200 || typeof data !== 'object') return console.error(status, data);
$scope.statusOk = true;
}).error(function (data, status) {
console.error(status, data);
$scope.statusOk = false;
});
var search = window.location.search.slice(1).split('&').map(function (item) { return item.split('='); }).reduce(function (o, k) { o[k[0]] = k[1]; return o; }, {});
$scope.errorContext = search.errorContext || '';
}]);
</script>
<style>
h3 {
padding-bottom: 15px;
}
</style>
</head>
<body class="status-page">
<div class="wrapper">
<div class="content">
<img ng-src="{{avatarUrl}}" width="128" height="128" onerror="this.src = '/img/logo.png'"/>
<h1> Cloudron </h1>
<div>
<h3> <i class="far fa-frown fa-fw text-danger"></i> Something has gone wrong </h3>
<a class="btn btn-primary" href="/" ng-show="statusOk">Back to the dashboard</a>
<br/>
<br/>
<p>If you are the server administrator, follow the <a href="https://cloudron.io/documentation/troubleshooting/" target="_blank">troubleshooting guide</a>.</p>
</div>
</div>
</div>
<footer class="text-center">
<span class="text-muted">&copy;2019 <a href="https://cloudron.io" target="_blank">Cloudron</a></span>
<span class="text-muted"><a href="https://forum.cloudron.io" target="_blank">Forum <i class="fa fa-comments"></i></a></span>
</footer>
</body>
</html>

View File

@@ -5,7 +5,7 @@
/* global EventSource:false */
/* global asyncForEach:false */
angular.module('Application').service('Client', ['$http', '$interval', 'md5', 'Notification', function ($http, $interval, md5, Notification) {
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
@@ -190,6 +190,13 @@ angular.module('Application').service('Client', ['$http', '$interval', 'md5', 'N
Notification.error({ title: 'Cloudron Error', message: message });
};
// 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();
};

View File

@@ -1,11 +1,13 @@
'use strict';
/* global angular */
/* global moment */
/* global $ */
// create main application module
var app = angular.module('Application', ['angular-md5', 'ui-notification']);
app.controller('LogsController', ['$scope', '$timeout', '$location', 'Client', function ($scope, $timeout, $location, Client) {
app.controller('LogsController', ['$scope', 'Client', function ($scope, Client) {
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; }, {});
$scope.initialized = false;
@@ -16,11 +18,6 @@ app.controller('LogsController', ['$scope', '$timeout', '$location', 'Client', f
$scope.selectedAppInfo = null;
$scope.selectedTaskInfo = null;
$scope.error = function (error) {
console.error(error);
window.location.href = '/error.html';
};
function ab2str(buf) {
return String.fromCharCode.apply(null, new Uint16Array(buf));
}
@@ -143,43 +140,48 @@ app.controller('LogsController', ['$scope', '$timeout', '$location', 'Client', f
}
}
Client.getStatus(function (error, status) {
if (error) return $scope.error(error);
function init() {
if (!status.activated) {
console.log('Not activated yet, redirecting', status);
window.location.href = '/';
return;
}
Client.getStatus(function (error, status) {
if (error) return Client.initError(error, init);
// 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);
}
if (!status.activated) {
console.log('Not activated yet, redirecting', status);
window.location.href = '/';
return;
}
console.log('Running log version ', localStorage.version);
// 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);
}
// get user profile as the first thing. this populates the "scope" and affects subsequent API calls
Client.refreshUserInfo(function (error) {
if (error) return $scope.error(error);
console.log('Running log version ', localStorage.version);
Client.refreshConfig(function (error) {
if (error) return $scope.error(error);
// get user profile as the first thing. this populates the "scope" and affects subsequent API calls
Client.refreshUserInfo(function (error) {
if (error) return Client.initError(error, init);
select({ id: search.id, taskId: search.taskId, appId: search.appId, crashId: search.crashId }, function (error) {
if (error) return $scope.error(error);
Client.refreshConfig(function (error) {
if (error) return Client.initError(error, init);
// now mark the Client to be ready
Client.setReady();
select({ id: search.id, taskId: search.taskId, appId: search.appId, crashId: search.crashId }, function (error) {
if (error) return Client.initError(error, init);
$scope.initialized = true;
// now mark the Client to be ready
Client.setReady();
showLogs();
$scope.initialized = true;
showLogs();
});
});
});
});
});
}
init();
}]);

View File

@@ -1,7 +1,7 @@
'use strict';
/* global angular:false */
/* global $:false */
/* global angular */
/* global $ */
angular.module('Application').controller('MainController', ['$scope', '$route', '$timeout', '$location', 'Client', function ($scope, $route, $timeout, $location, Client) {
$scope.initialized = false; // used to animate the UI
@@ -26,11 +26,6 @@ angular.module('Application').controller('MainController', ['$scope', '$route',
Client.logout();
};
$scope.error = function (error) {
console.error(error);
window.location.href = '/error.html';
};
$scope.waitingForPlanSelection = false;
$('#setupSubscriptionModal').on('hide.bs.modal', function () {
$scope.waitingForPlanSelection = false;
@@ -95,68 +90,70 @@ angular.module('Application').controller('MainController', ['$scope', '$route',
$scope.notifications = $scope.notifications.filter(function (n) { return n.id !== notificationId; });
};
Client.getStatus(function (error, status) {
if (error) return $scope.error(error);
function init() {
Client.getStatus(function (error, status) {
if (error) return Client.initError(error, init);
// WARNING if anything about the routing is changed here test these use-cases:
//
// 1. Caas
// 3. selfhosted restore
// 4. local development with gulp develop
// WARNING if anything about the routing is changed here test these use-cases:
//
// 1. Caas
// 3. selfhosted restore
// 4. local development with gulp develop
if (!status.activated) {
console.log('Not activated yet, redirecting', status);
if (status.restore.active || status.restore.errorMessage) { // show the error message in restore page
window.location.href = '/restore.html';
} else {
window.location.href = status.adminFqdn ? '/setup.html' : '/setupdns.html';
if (!status.activated) {
console.log('Not activated yet, redirecting', status);
if (status.restore.active || status.restore.errorMessage) { // show the error message in restore page
window.location.href = '/restore.html';
} else {
window.location.href = status.adminFqdn ? '/setup.html' : '/setupdns.html';
}
return;
}
return;
}
// support local development with localhost check
if (window.location.hostname !== status.adminFqdn && window.location.hostname !== 'localhost') {
// user is accessing by IP or by the old admin location (pre-migration)
window.location.href = '/setupdns.html';
return;
}
// support local development with localhost check
if (window.location.hostname !== status.adminFqdn && window.location.hostname !== 'localhost') {
// user is accessing by IP or by the old admin location (pre-migration)
window.location.href = '/setupdns.html';
return;
}
$scope.status = status;
$scope.status = status;
// 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);
}
// 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);
console.log('Running dashboard version ', localStorage.version);
// get user profile as the first thing. this populates the "scope" and affects subsequent API calls
Client.refreshUserInfo(function (error) {
if (error) return $scope.error(error);
// get user profile as the first thing. this populates the "scope" and affects subsequent API calls
Client.refreshUserInfo(function (error) {
if (error) return Client.initError(error, init);
Client.refreshConfig(function (error) {
if (error) return $scope.error(error);
Client.refreshConfig(function (error) {
if (error) return Client.initError(error, init);
Client.refreshInstalledApps(function (error) {
if (error) return $scope.error(error);
Client.refreshInstalledApps(function (error) {
if (error) return Client.initError(error, init);
// now mark the Client to be ready
Client.setReady();
// now mark the Client to be ready
Client.setReady();
$scope.config = Client.getConfig();
$scope.config = Client.getConfig();
$scope.initialized = true;
$scope.initialized = true;
refreshNotifications();
refreshNotifications();
$scope.updateSubscriptionStatus();
$scope.updateSubscriptionStatus();
});
});
});
});
});
}
Client.onConfig(function (config) {
if (config.cloudronName) {
@@ -164,6 +161,8 @@ angular.module('Application').controller('MainController', ['$scope', '$route',
}
});
init();
// setup all the dialog focus handling
['updateModal'].forEach(function (id) {
$('#' + id).on('shown.bs.modal', function () {

View File

@@ -1,6 +1,8 @@
'use strict';
/* global angular */
/* global tld */
/* global $ */
// create main application module
var app = angular.module('Application', ['angular-md5', 'ui-notification']);
@@ -11,9 +13,10 @@ app.filter('zoneName', function () {
};
});
app.controller('RestoreController', ['$scope', '$http', 'Client', function ($scope, $http, Client) {
app.controller('RestoreController', ['$scope', 'Client', function ($scope, Client) {
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; }, {});
$scope.client = Client;
$scope.busy = false;
$scope.error = {};
$scope.message = ''; // progress
@@ -261,22 +264,23 @@ app.controller('RestoreController', ['$scope', '$http', 'Client', function ($sco
document.getElementById('gcsKeyFileInput').onchange = readFileLocally($scope.gcsKey, 'content', 'keyFileName');
Client.getStatus(function (error, status) {
if (error) {
window.location.href = '/error.html';
return;
}
function init() {
Client.getStatus(function (error, status) {
if (error) return Client.initError(error, init);
if (status.restore.active) return waitForRestore();
if (status.restore.active) return waitForRestore();
if (status.restore.errorMessage) $scope.error.generic = status.restore.errorMessage;
if (status.restore.errorMessage) $scope.error.generic = status.restore.errorMessage;
if (status.activated) {
window.location.href = '/';
return;
}
if (status.activated) {
window.location.href = '/';
return;
}
$scope.instanceId = search.instanceId;
$scope.initialized = true;
});
$scope.instanceId = search.instanceId;
$scope.initialized = true;
});
}
init();
}]);

View File

@@ -1,12 +1,16 @@
'use strict';
/* global angular */
/* global $ */
// create main application module
var app = angular.module('Application', ['angular-md5', 'ui-notification', 'ui.bootstrap']);
app.controller('SetupController', ['$scope', '$http', 'Client', function ($scope, $http, Client) {
app.controller('SetupController', ['$scope', 'Client', function ($scope, Client) {
// Stupid angular location provider either wants html5 location mode or not, do the query parsing on my own
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; }, {});
$scope.client = Client;
$scope.initialized = false;
$scope.busy = false;
$scope.account = {
@@ -43,40 +47,41 @@ app.controller('SetupController', ['$scope', '$http', 'Client', function ($scope
});
};
Client.getStatus(function (error, status) {
if (error) {
window.location.href = '/error.html';
return;
}
function init() {
Client.getStatus(function (error, status) {
if (error) return Client.initError(error, init);
// if we are here from the ip first go to the real domain if already setup
if (status.adminFqdn && status.adminFqdn !== window.location.hostname) {
window.location.href = 'https://' + status.adminFqdn + '/setup.html';
return;
}
// if we are here from the ip first go to the real domain if already setup
if (status.adminFqdn && status.adminFqdn !== window.location.hostname) {
window.location.href = 'https://' + status.adminFqdn + '/setup.html';
return;
}
// if we don't have a domain yet, first go to domain setup
if (!status.adminFqdn) {
window.location.href = '/setupdns.html';
return;
}
// if we don't have a domain yet, first go to domain setup
if (!status.adminFqdn) {
window.location.href = '/setupdns.html';
return;
}
if (status.activated) {
window.location.href = '/';
return;
}
if (status.activated) {
window.location.href = '/';
return;
}
$scope.account.email = search.email || $scope.account.email;
$scope.account.displayName = search.displayName || $scope.account.displayName;
$scope.account.requireEmail = !search.email;
$scope.provider = status.provider;
$scope.apiServerOrigin = status.apiServerOrigin;
$scope.account.email = search.email || $scope.account.email;
$scope.account.displayName = search.displayName || $scope.account.displayName;
$scope.account.requireEmail = !search.email;
$scope.provider = status.provider;
$scope.apiServerOrigin = status.apiServerOrigin;
$scope.initialized = true;
$scope.initialized = true;
// Ensure we have a good autofocus
setTimeout(function () {
$(document).find("[autofocus]:first").focus();
}, 250);
});
// Ensure we have a good autofocus
setTimeout(function () {
$(document).find("[autofocus]:first").focus();
}, 250);
});
}
init();
}]);

View File

@@ -49,6 +49,8 @@
<body class="logs">
<div class="offline-banner animateMe" ng-show="client.offline" ng-cloak><i class="fa fa-circle-notch fa-spin"></i> Cloudron is offline. Reconnecting...</div>
<div class="animateMe ng-hide layout-root" ng-show="initialized">
<div class="logs-controls">

View File

@@ -37,6 +37,8 @@
<body class="setup" ng-app="Application" ng-controller="RestoreController">
<div class="offline-banner animateMe" ng-show="client.offline" ng-cloak><i class="fa fa-circle-notch fa-spin"></i> Cloudron is offline. Reconnecting...</div>
<div class="main-container ng-cloak text-center" ng-show="busy">
<div class="row">
<div class="col-md-6 col-md-offset-3">

View File

@@ -37,6 +37,8 @@
<body class="setup" ng-app="Application" ng-controller="SetupController">
<div class="offline-banner animateMe" ng-show="client.offline" ng-cloak><i class="fa fa-circle-notch fa-spin"></i> Cloudron is offline. Reconnecting...</div>
<div class="main-container ng-cloak text-center" ng-show="busy">
<div class="row">
<div class="col-md-6 col-md-offset-3">