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:
@@ -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">©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>
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
}]);
|
||||
|
||||
101
src/js/main.js
101
src/js/main.js
@@ -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 () {
|
||||
|
||||
@@ -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();
|
||||
}]);
|
||||
|
||||
@@ -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();
|
||||
}]);
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user