mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-11-02 12:05:57 +01:00
closes #4658
This commit is contained in:
64
public/src/admin/advanced/errors.js
Normal file
64
public/src/admin/advanced/errors.js
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
"use strict";
|
||||||
|
/*global config, define, app, socket, ajaxify, bootbox, templates, Chart, utils */
|
||||||
|
|
||||||
|
define('admin/advanced/errors', ['Chart'], function(Chart) {
|
||||||
|
var Errors = {};
|
||||||
|
|
||||||
|
Errors.init = function() {
|
||||||
|
var notFoundCanvas = document.getElementById('not-found'),
|
||||||
|
tooBusyCanvas = document.getElementById('toobusy'),
|
||||||
|
dailyLabels = utils.getDaysArray();
|
||||||
|
|
||||||
|
dailyLabels.length = 7;
|
||||||
|
|
||||||
|
if (utils.isMobile()) {
|
||||||
|
Chart.defaults.global.showTooltips = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
'not-found': {
|
||||||
|
labels: dailyLabels,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: "",
|
||||||
|
fillColor: "rgba(186,139,175,0.2)",
|
||||||
|
strokeColor: "rgba(186,139,175,1)",
|
||||||
|
pointColor: "rgba(186,139,175,1)",
|
||||||
|
pointStrokeColor: "#fff",
|
||||||
|
pointHighlightFill: "#fff",
|
||||||
|
pointHighlightStroke: "rgba(186,139,175,1)",
|
||||||
|
data: ajaxify.data.analytics['not-found']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'toobusy': {
|
||||||
|
labels: dailyLabels,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: "",
|
||||||
|
fillColor: "rgba(151,187,205,0.2)",
|
||||||
|
strokeColor: "rgba(151,187,205,1)",
|
||||||
|
pointColor: "rgba(151,187,205,1)",
|
||||||
|
pointStrokeColor: "#fff",
|
||||||
|
pointHighlightFill: "#fff",
|
||||||
|
pointHighlightStroke: "rgba(151,187,205,1)",
|
||||||
|
data: ajaxify.data.analytics['toobusy']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
notFoundCanvas.width = $(notFoundCanvas).parent().width();
|
||||||
|
tooBusyCanvas.width = $(tooBusyCanvas).parent().width();
|
||||||
|
new Chart(notFoundCanvas.getContext('2d')).Line(data['not-found'], {
|
||||||
|
responsive: true,
|
||||||
|
animation: false
|
||||||
|
});
|
||||||
|
new Chart(tooBusyCanvas.getContext('2d')).Line(data['toobusy'], {
|
||||||
|
responsive: true,
|
||||||
|
animation: false
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return Errors;
|
||||||
|
});
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
/*global config, define, app, socket, ajaxify, bootbox, templates, Chart, utils */
|
/*global config, define, app, socket, ajaxify, bootbox, templates, utils */
|
||||||
|
|
||||||
define('admin/manage/category-analytics', [], function() {
|
define('admin/manage/category-analytics', ['Chart'], function(Chart) {
|
||||||
var CategoryAnalytics = {};
|
var CategoryAnalytics = {};
|
||||||
|
|
||||||
CategoryAnalytics.init = function() {
|
CategoryAnalytics.init = function() {
|
||||||
|
|||||||
@@ -187,4 +187,11 @@ var db = require('./database');
|
|||||||
}, callback);
|
}, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Analytics.getErrorAnalytics = function(callback) {
|
||||||
|
async.parallel({
|
||||||
|
'not-found': async.apply(Analytics.getDailyStatsForSet, 'analytics:errors:404', Date.now(), 7),
|
||||||
|
'toobusy': async.apply(Analytics.getDailyStatsForSet, 'analytics:errors:503', Date.now(), 7)
|
||||||
|
}, callback);
|
||||||
|
};
|
||||||
|
|
||||||
}(exports));
|
}(exports));
|
||||||
@@ -14,6 +14,7 @@ var adminController = {
|
|||||||
},
|
},
|
||||||
events: require('./admin/events'),
|
events: require('./admin/events'),
|
||||||
logs: require('./admin/logs'),
|
logs: require('./admin/logs'),
|
||||||
|
errors: require('./admin/errors'),
|
||||||
database: require('./admin/database'),
|
database: require('./admin/database'),
|
||||||
postCache: require('./admin/postCache'),
|
postCache: require('./admin/postCache'),
|
||||||
plugins: require('./admin/plugins'),
|
plugins: require('./admin/plugins'),
|
||||||
|
|||||||
20
src/controllers/admin/errors.js
Normal file
20
src/controllers/admin/errors.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var async = require('async');
|
||||||
|
|
||||||
|
var meta = require('../../meta'),
|
||||||
|
analytics = require('../../analytics');
|
||||||
|
|
||||||
|
var errorsController = {};
|
||||||
|
|
||||||
|
errorsController.get = function(req, res) {
|
||||||
|
async.parallel({
|
||||||
|
'not-found': async.apply(meta.errors.get),
|
||||||
|
analytics: async.apply(analytics.getErrorAnalytics)
|
||||||
|
}, function(err, data) {
|
||||||
|
res.render('admin/advanced/errors', data);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = errorsController;
|
||||||
@@ -373,12 +373,14 @@ Controllers.handle404 = function(req, res) {
|
|||||||
} else if (isLanguage.test(req.url)) {
|
} else if (isLanguage.test(req.url)) {
|
||||||
res.status(200).json({});
|
res.status(200).json({});
|
||||||
} else if (req.path.startsWith(relativePath + '/uploads') || (req.get('accept') && req.get('accept').indexOf('text/html') === -1) || req.path === '/favicon.ico') {
|
} else if (req.path.startsWith(relativePath + '/uploads') || (req.get('accept') && req.get('accept').indexOf('text/html') === -1) || req.path === '/favicon.ico') {
|
||||||
|
meta.errors.log404(req.path || '');
|
||||||
res.sendStatus(404);
|
res.sendStatus(404);
|
||||||
} else if (req.accepts('html')) {
|
} else if (req.accepts('html')) {
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
winston.warn('Route requested but not found: ' + req.url);
|
winston.warn('Route requested but not found: ' + req.url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
meta.errors.log404(req.path.replace(/^\/api/, '') || '');
|
||||||
res.status(404);
|
res.status(404);
|
||||||
|
|
||||||
if (res.locals.isAPI) {
|
if (res.locals.isAPI) {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ var async = require('async'),
|
|||||||
require('./meta/sounds')(Meta);
|
require('./meta/sounds')(Meta);
|
||||||
require('./meta/settings')(Meta);
|
require('./meta/settings')(Meta);
|
||||||
require('./meta/logs')(Meta);
|
require('./meta/logs')(Meta);
|
||||||
|
require('./meta/errors')(Meta);
|
||||||
require('./meta/tags')(Meta);
|
require('./meta/tags')(Meta);
|
||||||
require('./meta/dependencies')(Meta);
|
require('./meta/dependencies')(Meta);
|
||||||
Meta.templates = require('./meta/templates');
|
Meta.templates = require('./meta/templates');
|
||||||
|
|||||||
36
src/meta/errors.js
Normal file
36
src/meta/errors.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var async = require('async'),
|
||||||
|
winston = require('winston'),
|
||||||
|
validator = require('validator');
|
||||||
|
|
||||||
|
var db = require('../database'),
|
||||||
|
analytics = require('../analytics');
|
||||||
|
|
||||||
|
module.exports = function(Meta) {
|
||||||
|
|
||||||
|
Meta.errors = {};
|
||||||
|
|
||||||
|
Meta.errors.log404 = function(route, callback) {
|
||||||
|
callback = callback || function() {};
|
||||||
|
route = route.replace(/\/$/, ''); // remove trailing slashes
|
||||||
|
analytics.increment('errors:404');
|
||||||
|
db.sortedSetIncrBy('errors:404', 1, route, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
Meta.errors.get = function(callback) {
|
||||||
|
db.getSortedSetRevRangeByScoreWithScores('errors:404', 0, -1, '+inf', '-inf', function(err, data) {
|
||||||
|
data = data.map(function(nfObject) {
|
||||||
|
nfObject.value = validator.escape(nfObject.value);
|
||||||
|
return nfObject;
|
||||||
|
});
|
||||||
|
|
||||||
|
callback(null, data);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Meta.errors.clear = function(callback) {
|
||||||
|
console.log('clear errors');
|
||||||
|
callback();
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -302,6 +302,7 @@ middleware.privateUploads = function(req, res, next) {
|
|||||||
|
|
||||||
middleware.busyCheck = function(req, res, next) {
|
middleware.busyCheck = function(req, res, next) {
|
||||||
if (global.env === 'production' && (!meta.config.hasOwnProperty('eventLoopCheckEnabled') || parseInt(meta.config.eventLoopCheckEnabled, 10) === 1) && toobusy()) {
|
if (global.env === 'production' && (!meta.config.hasOwnProperty('eventLoopCheckEnabled') || parseInt(meta.config.eventLoopCheckEnabled, 10) === 1) && toobusy()) {
|
||||||
|
analytics.increment('errors:503');
|
||||||
res.status(503).type('text/html').sendFile(path.join(__dirname, '../../public/503.html'));
|
res.status(503).type('text/html').sendFile(path.join(__dirname, '../../public/503.html'));
|
||||||
} else {
|
} else {
|
||||||
next();
|
next();
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ function addRoutes(router, middleware, controllers) {
|
|||||||
router.get('/advanced/database', middlewares, controllers.admin.database.get);
|
router.get('/advanced/database', middlewares, controllers.admin.database.get);
|
||||||
router.get('/advanced/events', middlewares, controllers.admin.events.get);
|
router.get('/advanced/events', middlewares, controllers.admin.events.get);
|
||||||
router.get('/advanced/logs', middlewares, controllers.admin.logs.get);
|
router.get('/advanced/logs', middlewares, controllers.admin.logs.get);
|
||||||
|
router.get('/advanced/errors', middlewares, controllers.admin.errors.get);
|
||||||
router.get('/advanced/post-cache', middlewares, controllers.admin.postCache.get);
|
router.get('/advanced/post-cache', middlewares, controllers.admin.postCache.get);
|
||||||
|
|
||||||
router.get('/development/logger', middlewares, controllers.admin.logger.get);
|
router.get('/development/logger', middlewares, controllers.admin.logger.get);
|
||||||
|
|||||||
61
src/views/admin/advanced/errors.tpl
Normal file
61
src/views/admin/advanced/errors.tpl
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-9">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-6 text-center">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-body">
|
||||||
|
<div><canvas id="not-found" height="250"></canvas></div>
|
||||||
|
</div>
|
||||||
|
<div class="panel-footer"><small><strong>Figure 1</strong> – <code>404 Not Found</code> events per day</small></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6 text-center">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-body">
|
||||||
|
<div><canvas id="toobusy" height="250"></canvas></div>
|
||||||
|
</div>
|
||||||
|
<div class="panel-footer"><small><strong>Figure 2</strong> – <code>503 Service Unavailable</code> events per day</small></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading"><i class="fa fa-exclamation-triangle"></i> 404 Not Found</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<th>Route</th>
|
||||||
|
<th>Count</th>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<!-- BEGIN not-found -->
|
||||||
|
<tr>
|
||||||
|
<td>{../value}</td>
|
||||||
|
<td>{../score}</td>
|
||||||
|
</tr>
|
||||||
|
<!-- END not-found -->
|
||||||
|
<!-- IF !not-found.length -->
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
<div class="alert alert-success">
|
||||||
|
Hooray! There are no routes that were not found.
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- ENDIF !not-found.length -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-3 acp-sidebar">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">Manage Error Log</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<div class="btn-group-vertical btn-block" role="group">
|
||||||
|
<button class="btn btn-info" data-action="export"><i class="fa fa-download"></i> Export Error Log (CSV)</button>
|
||||||
|
<button class="btn btn-danger" data-action="clear"><i class="fa fa-trash"></i> Clear Error Log</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -95,6 +95,7 @@
|
|||||||
<li><a href="{relative_path}/admin/advanced/database">Database</a></li>
|
<li><a href="{relative_path}/admin/advanced/database">Database</a></li>
|
||||||
<li><a href="{relative_path}/admin/advanced/events">Events</a></li>
|
<li><a href="{relative_path}/admin/advanced/events">Events</a></li>
|
||||||
<li><a href="{relative_path}/admin/advanced/logs">Logs</a></li>
|
<li><a href="{relative_path}/admin/advanced/logs">Logs</a></li>
|
||||||
|
<li><a href="{relative_path}/admin/advanced/errors">Errors</a></li>
|
||||||
<li><a href="{relative_path}/admin/advanced/post-cache">Post Cache</a></li>
|
<li><a href="{relative_path}/admin/advanced/post-cache">Post Cache</a></li>
|
||||||
<!-- IF env -->
|
<!-- IF env -->
|
||||||
<li><a href="{relative_path}/admin/development/logger">Logger</a></li>
|
<li><a href="{relative_path}/admin/development/logger">Logger</a></li>
|
||||||
@@ -247,6 +248,7 @@
|
|||||||
<li><a href="{relative_path}/admin/advanced/database">Database</a></li>
|
<li><a href="{relative_path}/admin/advanced/database">Database</a></li>
|
||||||
<li><a href="{relative_path}/admin/advanced/events">Events</a></li>
|
<li><a href="{relative_path}/admin/advanced/events">Events</a></li>
|
||||||
<li><a href="{relative_path}/admin/advanced/logs">Logs</a></li>
|
<li><a href="{relative_path}/admin/advanced/logs">Logs</a></li>
|
||||||
|
<li><a href="{relative_path}/admin/advanced/errors">Errors</a></li>
|
||||||
<li><a href="{relative_path}/admin/advanced/post-cache">Post Cache</a></li>
|
<li><a href="{relative_path}/admin/advanced/post-cache">Post Cache</a></li>
|
||||||
<!-- IF env -->
|
<!-- IF env -->
|
||||||
<li><a href="{relative_path}/admin/development/logger">Logger</a></li>
|
<li><a href="{relative_path}/admin/development/logger">Logger</a></li>
|
||||||
|
|||||||
Reference in New Issue
Block a user