mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-11-01 03:26:04 +01:00
ACP quick actions (#6374)
* ACP quick actions - Moved restart, build & restart, and logout into separate buttons - Moved buttons on mobile into the side menu - Added version and upgrade alert to header / mobile menu - Moved version checking to server-side with a cache for rate limiting - Changed "reload" translations to "rebuild and restart" * Change info alert to black-on-white to match focused search bar * Fix tests * Fallback for failed fetch of latest version
This commit is contained in:
committed by
Julian Lam
parent
81e085bb9d
commit
eaae5b52cd
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"alert.confirm-reload": "Are you sure you wish to reload NodeBB?",
|
||||
"alert.confirm-reload": "Are you sure you wish to rebuild and restart NodeBB?",
|
||||
"alert.confirm-restart": "Are you sure you wish to restart NodeBB?",
|
||||
|
||||
"acp-title": "%1 | NodeBB Admin Control Panel",
|
||||
|
||||
@@ -23,10 +23,11 @@
|
||||
"running-version": "You are running <strong>NodeBB v<span id=\"version\">%1</span></strong>.",
|
||||
"keep-updated": "Always make sure that your NodeBB is up to date for the latest security patches and bug fixes.",
|
||||
"up-to-date": "<p>You are <strong>up-to-date</strong> <i class=\"fa fa-check\"></i></p>",
|
||||
"upgrade-available": "<p>A new version (v%1) has been released. Consider <a href=\"https://docs.nodebb.org/configuring/upgrade/\">upgrading your NodeBB</a>.</p>",
|
||||
"prerelease-upgrade-available": "<p>This is an outdated pre-release version of NodeBB. A new version (v%1) has been released. Consider <a href=\"https://docs.nodebb.org/configuring/upgrade/\">upgrading your NodeBB</a>.</p>",
|
||||
"upgrade-available": "<p>A new version (v%1) has been released. Consider <a href=\"https://docs.nodebb.org/configuring/upgrade/\" target=\"_blank\">upgrading your NodeBB</a>.</p>",
|
||||
"prerelease-upgrade-available": "<p>This is an outdated pre-release version of NodeBB. A new version (v%1) has been released. Consider <a href=\"https://docs.nodebb.org/configuring/upgrade/\" target=\"_blank\">upgrading your NodeBB</a>.</p>",
|
||||
"prerelease-warning": "<p>This is a <strong>pre-release</strong> version of NodeBB. Unintended bugs may occur. <i class=\"fa fa-exclamation-triangle\"></i></p>",
|
||||
"running-in-development": "<span>Forum is running in development mode. The forum may be open to potential vulnerabilities; please contact your system administrator.</span>",
|
||||
"latest-lookup-failed": "<p>Failed to look up latest available version of NodeBB</p>",
|
||||
|
||||
"notices": "Notices",
|
||||
"restart-not-required": "Restart not required",
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
"development/logger": "Logger",
|
||||
"development/info": "Info",
|
||||
|
||||
"reload-forum": "Reload Forum",
|
||||
"reload-forum": "Rebuild & Restart Forum",
|
||||
"restart-forum": "Restart Forum",
|
||||
"logout": "Log out",
|
||||
"view-forum": "View Forum",
|
||||
@@ -74,5 +74,8 @@
|
||||
"search.keep-typing": "Type more to see results...",
|
||||
"search.start-typing": "Start typing to see results...",
|
||||
|
||||
"connection-lost": "Connection to %1 has been lost, attempting to reconnect..."
|
||||
"connection-lost": "Connection to %1 has been lost, attempting to reconnect...",
|
||||
|
||||
"alerts.version": "Running <strong>NodeBB v%1</strong>",
|
||||
"alerts.upgrade": "Upgrade to v%1"
|
||||
}
|
||||
@@ -16,10 +16,31 @@
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
#user_label {
|
||||
position: absolute;
|
||||
right: 30px;
|
||||
bottom: 125px;
|
||||
.quick-actions {
|
||||
position: static;
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
margin: 0;
|
||||
|
||||
> * {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
> .menu-button {
|
||||
margin-right: 0;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
.alert {
|
||||
font-size: 14px;
|
||||
margin-bottom: 0;
|
||||
|
||||
&.alert-info {
|
||||
background-color: #eee;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
margin-right: 0px;
|
||||
@@ -29,7 +50,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.fa-home {
|
||||
.fa {
|
||||
margin-top: 12px;
|
||||
font-size: 25px;
|
||||
}
|
||||
@@ -46,9 +67,6 @@
|
||||
}
|
||||
|
||||
#acp-search {
|
||||
margin-top: 2px;
|
||||
margin-right: 20px;
|
||||
|
||||
input {
|
||||
padding: 10px 20px;
|
||||
width: 250px;
|
||||
@@ -96,9 +114,6 @@
|
||||
}
|
||||
|
||||
.reconnect-spinner {
|
||||
left: auto;
|
||||
right: 380px;
|
||||
bottom: initial;
|
||||
top: 14px;
|
||||
line-height: 44px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,6 @@
|
||||
width: 22px;
|
||||
background: none;
|
||||
border: none;
|
||||
vertical-align: 10%;
|
||||
margin-right: 10px;
|
||||
margin-left: -5px;
|
||||
outline: none !important;
|
||||
@@ -86,6 +85,23 @@
|
||||
|
||||
.menu-section {
|
||||
margin: 25px 0;
|
||||
|
||||
&.quick-actions {
|
||||
margin: 0;
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.alert {
|
||||
border-radius: 0;
|
||||
|
||||
.span {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.menu-section-title {
|
||||
@@ -99,9 +115,9 @@
|
||||
}
|
||||
|
||||
.menu-section-list {
|
||||
padding:0;
|
||||
padding: 0;
|
||||
margin: 10px 0;
|
||||
list-style:none;
|
||||
list-style: none;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
|
||||
@@ -8,7 +8,6 @@ define('admin/general/dashboard', ['semver', 'Chart', 'translator', 'benchpress'
|
||||
graphs: false,
|
||||
};
|
||||
var isMobile = false;
|
||||
var isPrerelease = /^v?\d+\.\d+\.\d+-.+$/;
|
||||
var graphData = {
|
||||
rooms: {},
|
||||
traffic: {},
|
||||
@@ -42,42 +41,6 @@ define('admin/general/dashboard', ['semver', 'Chart', 'translator', 'benchpress'
|
||||
|
||||
isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
||||
|
||||
$.get('https://api.github.com/repos/NodeBB/NodeBB/tags', function (releases) {
|
||||
// Re-sort the releases, as they do not follow Semver (wrt pre-releases)
|
||||
releases = releases.sort(function (a, b) {
|
||||
a = a.name.replace(/^v/, '');
|
||||
b = b.name.replace(/^v/, '');
|
||||
return semver.lt(a, b) ? 1 : -1;
|
||||
}).filter(function (version) {
|
||||
return !isPrerelease.test(version.name); // filter out automated prerelease versions
|
||||
});
|
||||
|
||||
var version = $('#version').html();
|
||||
var latestVersion = releases[0].name.slice(1);
|
||||
var checkEl = $('.version-check');
|
||||
var text;
|
||||
|
||||
// Alter box colour accordingly
|
||||
if (semver.eq(latestVersion, version)) {
|
||||
checkEl.removeClass('alert-info').addClass('alert-success');
|
||||
text = '[[admin/general/dashboard:up-to-date]]';
|
||||
} else if (semver.gt(latestVersion, version)) {
|
||||
checkEl.removeClass('alert-info').addClass('alert-warning');
|
||||
if (!isPrerelease.test(version)) {
|
||||
text = '[[admin/general/dashboard:upgrade-available, ' + latestVersion + ']]';
|
||||
} else {
|
||||
text = '[[admin/general/dashboard:prerelease-upgrade-available, ' + latestVersion + ']]';
|
||||
}
|
||||
} else if (isPrerelease.test(version)) {
|
||||
checkEl.removeClass('alert-info').addClass('alert-info');
|
||||
text = '[[admin/general/dashboard:prerelease-warning]]';
|
||||
}
|
||||
|
||||
translator.translate(text, function (text) {
|
||||
checkEl.append(text);
|
||||
});
|
||||
});
|
||||
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
|
||||
setupRealtimeButton();
|
||||
|
||||
55
src/admin/versions.js
Normal file
55
src/admin/versions.js
Normal file
@@ -0,0 +1,55 @@
|
||||
'use strict';
|
||||
|
||||
var semver = require('semver');
|
||||
var request = require('request');
|
||||
|
||||
var meta = require('../meta');
|
||||
|
||||
var versionCache = '';
|
||||
var versionCacheLastModified = '';
|
||||
|
||||
var isPrerelease = /^v?\d+\.\d+\.\d+-.+$/;
|
||||
|
||||
function getLatestVersion(callback) {
|
||||
var headers = {
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
'User-Agent': 'NodeBB Admin Control Panel/' + meta.config.title,
|
||||
};
|
||||
|
||||
if (versionCacheLastModified) {
|
||||
headers['If-Modified-Since'] = versionCacheLastModified;
|
||||
}
|
||||
|
||||
request('https://api.github.com/repos/NodeBB/NodeBB/tags', {
|
||||
json: true,
|
||||
headers: headers,
|
||||
}, function (err, res, releases) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (res.statusCode === 304) {
|
||||
return callback(null, versionCache);
|
||||
}
|
||||
|
||||
if (res.statusCode !== 200) {
|
||||
return callback(Error(res.statusMessage));
|
||||
}
|
||||
|
||||
releases = releases.filter(function (version) {
|
||||
return !isPrerelease.test(version.name); // filter out automated prerelease versions
|
||||
}).map(function (version) {
|
||||
return version.name.replace(/^v/, '');
|
||||
}).sort(function (a, b) {
|
||||
return semver.lt(a, b) ? 1 : -1;
|
||||
});
|
||||
|
||||
versionCache = releases[0];
|
||||
versionCacheLastModified = res.headers['last-modified'];
|
||||
|
||||
callback(null, versionCache);
|
||||
});
|
||||
}
|
||||
|
||||
exports.getLatestVersion = getLatestVersion;
|
||||
exports.isPrerelease = isPrerelease;
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
var async = require('async');
|
||||
var nconf = require('nconf');
|
||||
var semver = require('semver');
|
||||
var winston = require('winston');
|
||||
|
||||
var versions = require('../../admin/versions');
|
||||
var db = require('../../database');
|
||||
var meta = require('../../meta');
|
||||
var plugins = require('../../plugins');
|
||||
@@ -13,9 +16,7 @@ dashboardController.get = function (req, res, next) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
async.parallel({
|
||||
stats: function (next) {
|
||||
getStats(next);
|
||||
},
|
||||
stats: getStats,
|
||||
notices: function (next) {
|
||||
var notices = [
|
||||
{
|
||||
@@ -41,11 +42,26 @@ dashboardController.get = function (req, res, next) {
|
||||
|
||||
plugins.fireHook('filter:admin.notices', notices, next);
|
||||
},
|
||||
latestVersion: function (next) {
|
||||
versions.getLatestVersion(function (err, result) {
|
||||
if (err) {
|
||||
winston.error('[acp] Failed to fetch latest version', err);
|
||||
}
|
||||
|
||||
next(null, err ? null : result);
|
||||
});
|
||||
},
|
||||
}, next);
|
||||
},
|
||||
function (results) {
|
||||
var version = nconf.get('version');
|
||||
|
||||
res.render('admin/general/dashboard', {
|
||||
version: nconf.get('version'),
|
||||
version: version,
|
||||
lookupFailed: results.latestVersion === null,
|
||||
latestVersion: results.latestVersion,
|
||||
upgradeAvailable: results.latestVersion && semver.gt(results.latestVersion, version),
|
||||
currentPrerelease: versions.isPrerelease.test(version),
|
||||
notices: results.notices,
|
||||
stats: results.stats,
|
||||
canRestart: !!process.send,
|
||||
|
||||
@@ -2,10 +2,14 @@
|
||||
|
||||
var async = require('async');
|
||||
var winston = require('winston');
|
||||
var jsesc = require('jsesc');
|
||||
var nconf = require('nconf');
|
||||
var semver = require('semver');
|
||||
|
||||
var user = require('../user');
|
||||
var meta = require('../meta');
|
||||
var plugins = require('../plugins');
|
||||
var jsesc = require('jsesc');
|
||||
var versions = require('../admin/versions');
|
||||
|
||||
var controllers = {
|
||||
api: require('../controllers/api'),
|
||||
@@ -54,6 +58,15 @@ module.exports = function (middleware) {
|
||||
configs: function (next) {
|
||||
meta.configs.list(next);
|
||||
},
|
||||
latestVersion: function (next) {
|
||||
versions.getLatestVersion(function (err, result) {
|
||||
if (err) {
|
||||
winston.error('[acp] Failed to fetch latest version', err);
|
||||
}
|
||||
|
||||
next(null, err ? null : result);
|
||||
});
|
||||
},
|
||||
}, next);
|
||||
},
|
||||
function (results, next) {
|
||||
@@ -67,6 +80,8 @@ module.exports = function (middleware) {
|
||||
});
|
||||
acpPath = acpPath.join(' > ');
|
||||
|
||||
var version = nconf.get('version');
|
||||
|
||||
var templateValues = {
|
||||
config: res.locals.config,
|
||||
configJSON: jsesc(JSON.stringify(res.locals.config), { isScriptContext: true }),
|
||||
@@ -81,6 +96,9 @@ module.exports = function (middleware) {
|
||||
env: !!process.env.NODE_ENV,
|
||||
title: (acpPath || 'Dashboard') + ' | NodeBB Admin Control Panel',
|
||||
bodyClass: data.bodyClass,
|
||||
version: version,
|
||||
latestVersion: results.latestVersion,
|
||||
upgradeAvailable: results.latestVersion && semver.gt(results.latestVersion, version),
|
||||
};
|
||||
|
||||
templateValues.template = { name: res.locals.template };
|
||||
|
||||
@@ -65,8 +65,27 @@
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">[[admin/general/dashboard:updates]]</div>
|
||||
<div class="panel-body">
|
||||
<div class="alert alert-info version-check">
|
||||
<div class="alert <!-- IF lookupFailed -->alert-danger<!-- ELSE --><!-- IF upgradeAvailable -->alert-warning<!-- ELSE --><!-- IF currentPrerelease -->alert-info<!-- ELSE -->alert-success<!-- END --><!-- END --><!-- END --> version-check">
|
||||
<p>[[admin/general/dashboard:running-version, {version}]]</p>
|
||||
<p>
|
||||
<!-- IF lookupFailed -->
|
||||
[[admin/general/dashboard:latest-lookup-failed]]
|
||||
<!-- ELSE -->
|
||||
<!-- IF upgradeAvailable -->
|
||||
<!-- IF currentPrerelease -->
|
||||
[[admin/general/dashboard:prerelease-upgrade-available, {latestVersion}]]
|
||||
<!-- ELSE -->
|
||||
[[admin/general/dashboard:upgrade-available, {latestVersion}]]
|
||||
<!-- END -->
|
||||
<!-- ELSE -->
|
||||
<!-- IF currentPrerelease -->
|
||||
[[admin/general/dashboard:prerelease-warning]]
|
||||
<!-- ELSE -->
|
||||
[[admin/general/dashboard:up-to-date]]
|
||||
<!-- END -->
|
||||
<!-- END -->
|
||||
<!-- END -->
|
||||
</p>
|
||||
</div>
|
||||
<p>
|
||||
[[admin/general/dashboard:keep-updated]]
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
<nav id="menu" class="hidden-md hidden-lg">
|
||||
<section class="menu-section quick-actions">
|
||||
<ul class="menu-section-list">
|
||||
<div class="button-group">
|
||||
<!-- IMPORT admin/partials/quick_actions/buttons.tpl -->
|
||||
</div>
|
||||
|
||||
<!-- IMPORT admin/partials/quick_actions/alerts.tpl -->
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="menu-section">
|
||||
<h3 class="menu-section-title">[[admin/menu:section-general]]</h3>
|
||||
<ul class="menu-section-list">
|
||||
<a href="{relative_path}/admin/general/dashboard">[[admin/menu:general/dashboard]]</a>
|
||||
<li><a href="{relative_path}/admin/general/dashboard">[[admin/menu:general/dashboard]]</a></li>
|
||||
<li><a href="{relative_path}/admin/general/homepage">[[admin/menu:general/homepage]]</a></li>
|
||||
<li><a href="{relative_path}/admin/general/navigation">[[admin/menu:general/navigation]]</a></li>
|
||||
<li><a href="{relative_path}/admin/general/languages">[[admin/menu:general/languages]]</a></li>
|
||||
@@ -119,37 +129,11 @@
|
||||
<h1 id="main-page-title"></h1>
|
||||
</div>
|
||||
|
||||
<ul id="user_label" class="pull-right">
|
||||
<li class="dropdown pull-right">
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#" id="user_dropdown">
|
||||
<i class="fa fa-fw fa-ellipsis-v"></i>
|
||||
</a>
|
||||
<ul id="user-control-list" class="dropdown-menu" aria-labelledby="user_dropdown">
|
||||
<li>
|
||||
<a href="#" class="reload" title="[[admin/menu:reload-forum]]">
|
||||
[[admin/menu:reload-forum]]
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="restart" title="[[admin/menu:restart-forum]]">
|
||||
[[admin/menu:restart-forum]]
|
||||
</a>
|
||||
</li>
|
||||
<li role="presentation" class="divider"></li>
|
||||
<li component="logout">
|
||||
<a href="#">[[admin/menu:logout]]</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<ul class="quick-actions hidden-xs hidden-sm">
|
||||
<!-- IMPORT admin/partials/quick_actions/buttons.tpl -->
|
||||
|
||||
<li class="pull-right">
|
||||
<a href="{config.relative_path}/">
|
||||
<i class="fa fa-fw fa-home" title="[[admin/menu:view-forum]]"></i>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<form class="pull-right hidden-sm hidden-xs" role="search">
|
||||
<div class="" id="acp-search" >
|
||||
<form role="search">
|
||||
<div id="acp-search" >
|
||||
<div class="dropdown">
|
||||
<input type="text" autofocus data-toggle="dropdown" class="form-control" placeholder="[[admin/menu:search.placeholder]]">
|
||||
<ul class="dropdown-menu dropdown-menu-right state-start-typing" role="menu">
|
||||
@@ -172,7 +156,17 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- IMPORT admin/partials/quick_actions/alerts.tpl -->
|
||||
|
||||
<li class="reconnect-spinner">
|
||||
<a href="#" id="reconnect" class="hide" title="[[admin/menu:connection-lost, {title}]]">
|
||||
<i class="fa fa-check"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
<ul id="main-menu">
|
||||
<li class="menu-item">
|
||||
<a href="{relative_path}/admin/general/dashboard">[[admin/menu:general/dashboard]]</a>
|
||||
@@ -281,12 +275,4 @@
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="nav navbar-nav navbar-right hidden-xs reconnect-spinner">
|
||||
<li>
|
||||
<a href="#" id="reconnect" class="hide" title="[[admin/menu:connection-lost, {title}]]">
|
||||
<i class="fa fa-check"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
10
src/views/admin/partials/quick_actions/alerts.tpl
Normal file
10
src/views/admin/partials/quick_actions/alerts.tpl
Normal file
@@ -0,0 +1,10 @@
|
||||
<div class="alert <!-- IF upgradeAvailable -->alert-warning<!-- ELSE -->alert-info<!-- END --> well-sm">
|
||||
<span>[[admin/menu:alerts.version, {version}]]</span>
|
||||
<!-- IF upgradeAvailable -->
|
||||
<span style="margin-left: 10px">
|
||||
<a href="https://docs.nodebb.org/configuring/upgrade/" target="_blank">
|
||||
<u>[[admin/menu:alerts.upgrade, {latestVersion}]]</u>
|
||||
</a>
|
||||
</span>
|
||||
<!-- END -->
|
||||
</div>
|
||||
21
src/views/admin/partials/quick_actions/buttons.tpl
Normal file
21
src/views/admin/partials/quick_actions/buttons.tpl
Normal file
@@ -0,0 +1,21 @@
|
||||
<li component="logout">
|
||||
<a href="#" title="[[admin/menu:logout]]" data-placement="bottom" data-toggle="tooltip">
|
||||
<i class="fa fw-fw fa-sign-out"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="restart" data-toggle="tooltip" data-placement="bottom" title="[[admin/menu:restart-forum]]">
|
||||
<i class="fa fa-fw fa-repeat"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="reload" data-toggle="tooltip" data-placement="bottom" title="[[admin/menu:reload-forum]]">
|
||||
<i class="fa fa-fw fa-refresh"></i>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="{config.relative_path}/" data-toggle="tooltip" data-placement="bottom" title="[[admin/menu:view-forum]]">
|
||||
<i class="fa fa-fw fa-home"></i>
|
||||
</a>
|
||||
</li>
|
||||
@@ -12,6 +12,7 @@ var nconf = require('nconf');
|
||||
var url = require('url');
|
||||
var errorText;
|
||||
|
||||
var packageInfo = require('../../package');
|
||||
|
||||
nconf.file({ file: path.join(__dirname, '../../config.json') });
|
||||
nconf.defaults({
|
||||
@@ -120,6 +121,8 @@ before(function (done) {
|
||||
nconf.set('theme_config', path.join(nconf.get('themes_path'), 'nodebb-theme-persona', 'theme.json'));
|
||||
nconf.set('bcrypt_rounds', 1);
|
||||
|
||||
nconf.set('version', packageInfo.version);
|
||||
|
||||
meta.dependencies.check(next);
|
||||
},
|
||||
function (next) {
|
||||
|
||||
Reference in New Issue
Block a user