[enh] Reboot/shutdown from admin interface (#173)

* [enh] Add reboot/shutdown action in tools section.
* [enh] Use icon in front of reboot buttons.
* [enh] Improve reboot string.
* Enhance shutdown/reboot page with a header
* Enhance wording
* [fix] Indicate to users server is rebooting/shutting down
* [fix] Dead code
This commit is contained in:
opi 2017-10-08 23:44:55 +02:00 committed by Alexandre Aubin
parent 713a3d59a5
commit 26ad22df9e
5 changed files with 170 additions and 67 deletions

View file

@ -178,6 +178,63 @@
});
});
// Reboot or shutdown button
app.get('#/tools/reboot', function (c) {
c.view('tools/tools_reboot');
});
// Reboot or shutdown actions
app.get('#/tools/reboot/:action', function (c) {
var action = c.params['action'].toLowerCase();
if (action == 'reboot' || action == 'shutdown') {
c.confirm(
y18n.t('tools_' + action),
// confirm_reboot_action_reboot or confirm_reboot_action_shutdown
y18n.t('confirm_reboot_action_' + action),
function(){
c.api('/'+action+'?force', function(data) {
// This code is not executed due to 502 response (reboot or shutdown)
c.redirect('#/logout');
}, 'PUT', {}, false, function (xhr) {
c.flash('success', y18n.t('tools_' + action + '_done'))
// Disconnect from the webadmin
store.clear('url');
store.clear('connected');
store.set('path', '#/');
// Rename the page to allow refresh without ask for rebooting
window.location.href = window.location.href.split('#')[0] + '#/';
// Display reboot or shutdown info
// We can't use template because now the webserver is off
if (action == 'reboot') {
$('#main').replaceWith('<div id="main"><div class="alert alert-warning"><i class="fa-refresh"></i> ' + y18n.t('tools_rebooting') + '</div></div>');
}
else {
$('#main').replaceWith('<div id="main"><div class="alert alert-warning"><i class="fa-power-off"></i> ' + y18n.t('tools_shuttingdown') + '</div></div>');
}
// Remove loader if any
$('div.loader').remove();
// Force scrollTop on page load
$('html, body').scrollTop(0);
store.clear('slide');
});
},
function(){
store.clear('slide');
c.redirect('#/tools/reboot');
}
);
}
else {
c.flash('fail', y18n.t('unknown_action', [action]));
store.clear('slide');
c.redirect('#/tools/reboot');
}
});
// Diagnosis
app.get('#/tools/diagnosis(/:private)?', function (c) {
// See http://sammyjs.org/docs/routes for splat documentation

View file

@ -62,10 +62,10 @@
},
// API call
api: function(uri, callback, method, data, websocket) {
api: function(uri, callback, method, data, websocket, callbackOnFailure) {
c = this;
call = function(uri, callback, method, data) {
call = function(uri, callback, method, data, callbackOnFailure) {
method = typeof method !== 'undefined' ? method : 'GET';
data = typeof data !== 'undefined' ? data : {};
if (window.navigator && window.navigator.language && (typeof data.locale === 'undefined')) {
@ -84,6 +84,71 @@
if ($('div.loader').length === 0) {
$('#main').append('<div class="loader loader-content"></div>');
}
if (typeof callbackOnFailure !== 'function') {
callbackOnFailure = function(xhr) {
// Postinstall is a custom case, we have to wait that
// operation is done before doing anything
if (uri === '/postinstall') {
if (installing) {
interval = window.location.hostname === args.domain ? 20000 : 5000;
checkInstall = setInterval(function () {
c.checkInstall(function(isInstalled) {
if (isInstalled || typeof isInstalled === 'undefined') {
c.flash('success', y18n.t('installation_complete'));
clearInterval(checkInstall);
window.location.href = 'https://'+ window.location.hostname +'/yunohost/admin/';
}
});
}, interval);
} else {
c.flash('fail', y18n.t('error_occured'));
}
}
// Regular errors
else {
if (xhr.status == 200) {
// Fail with 200, WTF
callback({});
}
// Unauthorized or wrong password
else if (xhr.status == 401) {
if (uri === '/login') {
c.flash('fail', y18n.t('wrong_password'));
} else {
c.flash('fail', y18n.t('unauthorized'));
c.redirect('#/login');
}
}
// 500
else if (xhr.status == 500) {
error_log = JSON.parse(xhr.responseText);
error_log.route = error_log.route.join(' ') + '\n';
error_log.arguments = JSON.stringify(error_log.arguments);
c.flash('fail', y18n.t('internal_exception', [error_log.route, error_log.arguments, error_log.traceback]));
}
// 502 Bad gateway means API is down
else if (xhr.status == 502) {
c.flash('fail', y18n.t('api_not_responding'));
}
// More verbose error messages first
else if (typeof xhr.responseText !== 'undefined') {
c.flash('fail', xhr.responseText);
}
// Return HTTP error code at least
else {
var errorMessage = xhr.status+' '+xhr.statusText;
c.flash('fail', y18n.t('error_server_unexpected', [errorMessage]));
}
// Remove loader if any
$('div.loader').remove();
// Force scrollTop on page load
$('html, body').scrollTop(0);
store.clear('slide');
}
};
}
jQuery.ajax({
url: 'https://' + store.get('url') + uri,
@ -99,69 +164,7 @@
data = data || {};
callback(data);
})
.fail(function(xhr) {
// Postinstall is a custom case, we have to wait that
// operation is done before doing anything
if (uri === '/postinstall') {
if (installing) {
interval = window.location.hostname === args.domain ? 20000 : 5000;
checkInstall = setInterval(function () {
c.checkInstall(function(isInstalled) {
if (isInstalled || typeof isInstalled === 'undefined') {
c.flash('success', y18n.t('installation_complete'));
clearInterval(checkInstall);
window.location.href = 'https://'+ window.location.hostname +'/yunohost/admin/';
}
});
}, interval);
} else {
c.flash('fail', y18n.t('error_occured'));
}
}
// Regular errors
else {
if (xhr.status == 200) {
// Fail with 200, WTF
callback({});
}
// Unauthorized or wrong password
else if (xhr.status == 401) {
if (uri === '/login') {
c.flash('fail', y18n.t('wrong_password'));
} else {
c.flash('fail', y18n.t('unauthorized'));
c.redirect('#/login');
}
}
// 500
else if (xhr.status == 500) {
error_log = JSON.parse(xhr.responseText);
error_log.route = error_log.route.join(' ') + '\n';
error_log.arguments = JSON.stringify(error_log.arguments);
c.flash('fail', y18n.t('internal_exception', [error_log.route, error_log.arguments, error_log.traceback]));
}
// 502 Bad gateway means API is down
else if (xhr.status == 502) {
c.flash('fail', y18n.t('api_not_responding'));
}
// More verbose error messages first
else if (typeof xhr.responseText !== 'undefined') {
c.flash('fail', xhr.responseText);
}
// Return HTTP error code at least
else {
var errorMessage = xhr.status+' '+xhr.statusText;
c.flash('fail', y18n.t('error_server_unexpected', [errorMessage]));
}
// Remove loader if any
$('div.loader').remove();
// Force scrollTop on page load
$('html, body').scrollTop(0);
store.clear('slide');
}
});
.fail(callbackOnFailure);
};
websocket = typeof websocket !== 'undefined' ? websocket : true;
@ -181,9 +184,9 @@
ws.onclose = function() {};
ws.onopen = call(uri, callback, method, data);
ws.onopen = call(uri, callback, method, data, callbackOnFailure);
} else {
call(uri, callback, method, data);
call(uri, callback, method, data, callbackOnFailure);
}
},

View file

@ -79,6 +79,8 @@
"confirm_update_specific_app": "Are you sure you want to update %s?",
"confirm_upnp_enable": "Are you sure you want to enable UPnP?",
"confirm_upnp_disable": "Are you sure you want to disable UPnP?",
"confirm_reboot_action_reboot": "Are you sure you want to reboot your server?",
"confirm_reboot_action_shutdown": "Are you sure you want to shutdown your server?",
"connection": "Connection",
"copy": "Copy",
"count_min": "%s min",
@ -272,6 +274,15 @@
"tools_security_feed_no_items": "No security notifications",
"tools_security_feed_subscribe_rss": "Subscribe to security notifications RSS",
"tools_security_feed_view_items": "View all security notifications",
"tools_reboot": "Reboot your server",
"tools_reboot_btn": "Reboot",
"tools_reboot_done": "Rebooting...",
"tools_rebooting": "Your server is rebooting. To return on the web administration interface you need to wait your server to be up. You can check that by refreshing regularly this page (F5).",
"tools_shutdown": "Shutdown your server",
"tools_shutdown_btn": "Shutdown",
"tools_shutdown_done": "Shutting down...",
"tools_shuttingdown": "Your server is powerring off. As long as your server is off, you won't be able to use the web administration.",
"tools_shutdown_reboot": "Shutdown/Reboot",
"total": "Total",
"transmission": "Transmission",
"udp": "UDP",

View file

@ -31,6 +31,10 @@
<span class="fa-chevron-right pull-right"></span>
<h2 class="list-group-item-heading">{{t 'tools_security_feed'}}</h2>
</a>
<a href="#/tools/reboot" class="list-group-item slide clearfix">
<span class="pull-right fa-chevron-right"></span>
<h2 class="list-group-item-heading">{{t 'tools_shutdown_reboot'}}</h2>
</a>
<a href="#/tools/versions" class="list-group-item slide clearfix">
<span class="pull-right fa-chevron-right"></span>
<h2 class="list-group-item-heading">{{t 'versions'}}</h2>

View file

@ -0,0 +1,28 @@
<div class="btn-breadcrumb">
<a href="#/" ><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
<a href="#/tools">{{t 'tools'}}</a>
<a href="#/tools/reboot">{{t 'tools_shutdown_reboot'}}</a>
</div>
<div class="separator"></div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">
<span class="fa-fw fa-wrench"></span> {{t 'operations'}}
</h2>
</div>
<div class="panel-body">
<p>
<a href="#/tools/reboot/reboot" class="btn btn-danger">
<i class="fa-refresh"></i> {{t 'tools_reboot_btn'}}
</a>
</p>
<p>
<a href="#/tools/reboot/shutdown" class="btn btn-danger">
<i class="fa-power-off"></i> {{t 'tools_shutdown_btn'}}
</a>
</p>
</div>
</div>