mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-11-09 07:25:46 +01:00
refactor: abstract out some client side dashboard code into modules, analytics subpages for users, topics, and logins
This commit is contained in:
@@ -123,10 +123,17 @@ async function getStats() {
|
||||
getStatsForSet('topics:tid', 'topicCount'),
|
||||
]);
|
||||
results[0].name = '[[admin/dashboard:unique-visitors]]';
|
||||
|
||||
results[1].name = '[[admin/dashboard:logins]]';
|
||||
results[1].href = `${nconf.get('relative_path')}/admin/dashboard/logins`;
|
||||
|
||||
results[2].name = '[[admin/dashboard:new-users]]';
|
||||
results[2].href = `${nconf.get('relative_path')}/admin/dashboard/users`;
|
||||
|
||||
results[3].name = '[[admin/dashboard:posts]]';
|
||||
|
||||
results[4].name = '[[admin/dashboard:topics]]';
|
||||
results[4].href = `${nconf.get('relative_path')}/admin/dashboard/topics`;
|
||||
|
||||
({ results } = await plugins.hooks.fire('filter:admin.getStats', {
|
||||
results,
|
||||
@@ -221,3 +228,66 @@ async function getLastRestart() {
|
||||
lastrestart.timestampISO = utils.toISOString(lastrestart.timestamp);
|
||||
return lastrestart;
|
||||
}
|
||||
|
||||
dashboardController.getLogins = async (req, res) => {
|
||||
let stats = await getStats();
|
||||
const dataset = await analytics.getHourlyStatsForSet('analytics:logins', Date.now(), 24);
|
||||
stats = stats.filter(stat => stat.name === '[[admin/dashboard:logins]]').map(({ ...stat }) => {
|
||||
delete stat.href;
|
||||
return stat;
|
||||
});
|
||||
const summary = {
|
||||
day: stats[0].today,
|
||||
week: stats[0].thisweek,
|
||||
month: stats[0].thismonth,
|
||||
};
|
||||
|
||||
res.render('admin/dashboard/logins', {
|
||||
set: 'logins',
|
||||
stats,
|
||||
dataset,
|
||||
summary,
|
||||
});
|
||||
};
|
||||
|
||||
dashboardController.getUsers = async (req, res) => {
|
||||
let stats = await getStats();
|
||||
const dataset = await analytics.getHourlyStatsForSet('analytics:registrations', Date.now(), 24);
|
||||
stats = stats.filter(stat => stat.name === '[[admin/dashboard:new-users]]').map(({ ...stat }) => {
|
||||
delete stat.href;
|
||||
return stat;
|
||||
});
|
||||
const summary = {
|
||||
day: stats[0].today,
|
||||
week: stats[0].thisweek,
|
||||
month: stats[0].thismonth,
|
||||
};
|
||||
|
||||
res.render('admin/dashboard/users', {
|
||||
set: 'registrations',
|
||||
stats,
|
||||
dataset,
|
||||
summary,
|
||||
});
|
||||
};
|
||||
|
||||
dashboardController.getTopics = async (req, res) => {
|
||||
let stats = await getStats();
|
||||
const dataset = await analytics.getHourlyStatsForSet('analytics:topics', Date.now(), 24);
|
||||
stats = stats.filter(stat => stat.name === '[[admin/dashboard:topics]]').map(({ ...stat }) => {
|
||||
delete stat.href;
|
||||
return stat;
|
||||
});
|
||||
const summary = {
|
||||
day: stats[0].today,
|
||||
week: stats[0].thisweek,
|
||||
month: stats[0].thismonth,
|
||||
};
|
||||
|
||||
res.render('admin/dashboard/topics', {
|
||||
set: 'topics',
|
||||
stats,
|
||||
dataset,
|
||||
summary,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
const user = require('../../user');
|
||||
const meta = require('../../meta');
|
||||
const privileges = require('../../privileges');
|
||||
const analytics = require('../../analytics');
|
||||
|
||||
const helpers = require('../helpers');
|
||||
|
||||
@@ -17,3 +19,22 @@ Admin.updateSetting = async (req, res) => {
|
||||
await meta.configs.set(req.params.setting, req.body.value);
|
||||
helpers.formatApiResponse(200, res);
|
||||
};
|
||||
|
||||
Admin.getAnalytics = async (req, res) => {
|
||||
const ok = await user.isAdministrator(req.uid);
|
||||
|
||||
if (!ok) {
|
||||
return helpers.formatApiResponse(403, res);
|
||||
}
|
||||
|
||||
// Default returns views from past 24 hours, by hour
|
||||
if (!req.query.amount) {
|
||||
if (req.query.units === 'days') {
|
||||
req.query.amount = 30;
|
||||
} else {
|
||||
req.query.amount = 24;
|
||||
}
|
||||
}
|
||||
const getStats = req.query.units === 'days' ? analytics.getDailyStatsForSet : analytics.getHourlyStatsForSet;
|
||||
helpers.formatApiResponse(200, res, await getStats(`analytics:${req.params.set}`, parseInt(req.query.until, 10) || Date.now(), req.query.amount));
|
||||
};
|
||||
|
||||
@@ -8,6 +8,9 @@ module.exports = function (app, name, middleware, controllers) {
|
||||
helpers.setupAdminPageRoute(app, `/${name}`, middleware, middlewares, controllers.admin.routeIndex);
|
||||
|
||||
helpers.setupAdminPageRoute(app, `/${name}/dashboard`, middleware, middlewares, controllers.admin.dashboard.get);
|
||||
helpers.setupAdminPageRoute(app, `/${name}/dashboard/logins`, middleware, middlewares, controllers.admin.dashboard.getLogins);
|
||||
helpers.setupAdminPageRoute(app, `/${name}/dashboard/users`, middleware, middlewares, controllers.admin.dashboard.getUsers);
|
||||
helpers.setupAdminPageRoute(app, `/${name}/dashboard/topics`, middleware, middlewares, controllers.admin.dashboard.getTopics);
|
||||
|
||||
helpers.setupAdminPageRoute(app, `/${name}/manage/categories`, middleware, middlewares, controllers.admin.categories.getAll);
|
||||
helpers.setupAdminPageRoute(app, `/${name}/manage/categories/:category_id`, middleware, middlewares, controllers.admin.categories.get);
|
||||
|
||||
@@ -12,5 +12,7 @@ module.exports = function () {
|
||||
|
||||
setupApiRoute(router, 'put', '/settings/:setting', [...middlewares, middleware.checkRequired.bind(null, ['value'])], controllers.write.admin.updateSetting);
|
||||
|
||||
setupApiRoute(router, 'get', '/analytics/:set', [...middlewares], controllers.write.admin.getAnalytics);
|
||||
|
||||
return router;
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@ const slugify = require('../slugify');
|
||||
const plugins = require('../plugins');
|
||||
const groups = require('../groups');
|
||||
const meta = require('../meta');
|
||||
const analytics = require('../analytics');
|
||||
|
||||
module.exports = function (User) {
|
||||
User.create = async function (data) {
|
||||
@@ -108,6 +109,7 @@ module.exports = function (User) {
|
||||
|
||||
await Promise.all([
|
||||
db.incrObjectField('global', 'userCount'),
|
||||
analytics.increment('registrations'),
|
||||
db.sortedSetAddBulk(bulkAdd),
|
||||
groups.join(groupsToJoin, userData.uid),
|
||||
User.notifications.sendWelcomeNotification(userData.uid),
|
||||
|
||||
@@ -1,80 +1,7 @@
|
||||
<div class="row dashboard">
|
||||
<div class="col-lg-9">
|
||||
<div class="panel panel-default" id="analytics-panel">
|
||||
<div class="panel-heading">
|
||||
[[admin/dashboard:forum-traffic]]
|
||||
<div class="pull-right">
|
||||
<a id="view-as-json" href="{config.relative_path}/api/admin/analytics&type=hourly"><i class="fa fa-terminal"></i></a>
|
||||
<i class="fa fa-expand"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="graph-container" id="analytics-traffic-container">
|
||||
<canvas id="analytics-traffic" width="100%" height="400"></canvas>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 hidden-xs text-center pageview-stats">
|
||||
<div><strong id="pageViewsThirty">0</strong></div>
|
||||
<div><a href="#" class="updatePageviewsGraph" data-action="updateGraph" data-units="days" data-amount="30">[[admin/dashboard:page-views-thirty]]</a></div>
|
||||
</div>
|
||||
<div class="col-sm-3 text-center pageview-stats">
|
||||
<div><strong id="pageViewsSeven">0</strong></div>
|
||||
<div><a href="#" class="updatePageviewsGraph" data-action="updateGraph" data-units="days" data-amount="7">[[admin/dashboard:page-views-seven]]</a></div>
|
||||
</div>
|
||||
<div class="col-sm-3 hidden-xs text-center pageview-stats">
|
||||
<div><strong id="pageViewsPastDay">0</strong></div>
|
||||
<div><a href="#" class="updatePageviewsGraph active" data-action="updateGraph" data-units="hours">[[admin/dashboard:page-views-last-day]]</a></div>
|
||||
</div>
|
||||
<div class="col-sm-3 text-center pageview-stats">
|
||||
<div><strong><i class="fa fa-clock-o"></i></strong></div>
|
||||
<div><a href="#" class="updatePageviewsGraph" data-action="updateGraph" data-units="custom">[[admin/dashboard:page-views-custom]]</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th class="text-right">[[admin/dashboard:stats.yesterday]]</th>
|
||||
<th class="text-right">[[admin/dashboard:stats.today]]</th>
|
||||
<th></th>
|
||||
<th class="text-right">[[admin/dashboard:stats.last-week]]</th>
|
||||
<th class="text-right">[[admin/dashboard:stats.this-week]]</th>
|
||||
<th></th>
|
||||
<th class="text-right">[[admin/dashboard:stats.last-month]]</th>
|
||||
<th class="text-right">[[admin/dashboard:stats.this-month]]</th>
|
||||
<th></th>
|
||||
<th class="text-right">[[admin/dashboard:stats.all]]</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- BEGIN stats -->
|
||||
<tr>
|
||||
<td><strong>{stats.name}</strong></td>
|
||||
<td class="text-right formatted-number">{stats.yesterday}</td>
|
||||
<td class="text-right formatted-number">{stats.today}</td>
|
||||
<td class="{stats.dayTextClass}"><small>{stats.dayIncrease}%</small></td>
|
||||
|
||||
<td class="text-right formatted-number">{stats.lastweek}</td>
|
||||
<td class="text-right formatted-number">{stats.thisweek}</td>
|
||||
<td class="{stats.weekTextClass}"><small>{stats.weekIncrease}%</small></td>
|
||||
|
||||
<td class="text-right formatted-number">{stats.lastmonth}</td>
|
||||
<td class="text-right formatted-number">{stats.thismonth}</td>
|
||||
<td class="{stats.monthTextClass}"><small>{stats.monthIncrease}%</small></td>
|
||||
|
||||
<td class="text-right formatted-number">{stats.alltime}</td>
|
||||
</tr>
|
||||
<!-- END stats -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<!-- IMPORT admin/partials/dashboard/graph.tpl -->
|
||||
<!-- IMPORT admin/partials/dashboard/stats.tpl -->
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-4">
|
||||
|
||||
6
src/views/admin/dashboard/logins.tpl
Normal file
6
src/views/admin/dashboard/logins.tpl
Normal file
@@ -0,0 +1,6 @@
|
||||
<div class="row dashboard">
|
||||
<div class="col-xs-12">
|
||||
<!-- IMPORT admin/partials/dashboard/graph.tpl -->
|
||||
<!-- IMPORT admin/partials/dashboard/stats.tpl -->
|
||||
</div>
|
||||
</div>
|
||||
6
src/views/admin/dashboard/topics.tpl
Normal file
6
src/views/admin/dashboard/topics.tpl
Normal file
@@ -0,0 +1,6 @@
|
||||
<div class="row dashboard">
|
||||
<div class="col-xs-12">
|
||||
<!-- IMPORT admin/partials/dashboard/graph.tpl -->
|
||||
<!-- IMPORT admin/partials/dashboard/stats.tpl -->
|
||||
</div>
|
||||
</div>
|
||||
6
src/views/admin/dashboard/users.tpl
Normal file
6
src/views/admin/dashboard/users.tpl
Normal file
@@ -0,0 +1,6 @@
|
||||
<div class="row dashboard">
|
||||
<div class="col-xs-12">
|
||||
<!-- IMPORT admin/partials/dashboard/graph.tpl -->
|
||||
<!-- IMPORT admin/partials/dashboard/stats.tpl -->
|
||||
</div>
|
||||
</div>
|
||||
33
src/views/admin/partials/dashboard/graph.tpl
Normal file
33
src/views/admin/partials/dashboard/graph.tpl
Normal file
@@ -0,0 +1,33 @@
|
||||
<div class="panel panel-default" id="analytics-panel">
|
||||
<div class="panel-heading">
|
||||
[[admin/dashboard:forum-traffic]]
|
||||
<div class="pull-right">
|
||||
<a id="view-as-json" href="{config.relative_path}/api/v3/admin/analytics/{set}?type=hourly"><i class="fa fa-terminal"></i></a>
|
||||
<i class="fa fa-expand"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="graph-container" id="analytics-traffic-container">
|
||||
<canvas id="analytics-traffic" width="100%" height="400"></canvas>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 hidden-xs text-center pageview-stats">
|
||||
<div><strong id="pageViewsThirty">{{{ if summary.month }}}{./summary.month}{{{ else }}}0{{{ end }}}</strong></div>
|
||||
<div><a href="#" class="updatePageviewsGraph" data-action="updateGraph" data-units="days" data-amount="30">[[admin/dashboard:page-views-thirty]]</a></div>
|
||||
</div>
|
||||
<div class="col-sm-3 text-center pageview-stats">
|
||||
<div><strong id="pageViewsSeven">{{{ if summary.week }}}{./summary.week}{{{ else }}}0{{{ end }}}</strong></div>
|
||||
<div><a href="#" class="updatePageviewsGraph" data-action="updateGraph" data-units="days" data-amount="7">[[admin/dashboard:page-views-seven]]</a></div>
|
||||
</div>
|
||||
<div class="col-sm-3 hidden-xs text-center pageview-stats">
|
||||
<div><strong id="pageViewsPastDay">{{{ if summary.day }}}{./summary.day}{{{ else }}}0{{{ end }}}</strong></div>
|
||||
<div><a href="#" class="updatePageviewsGraph active" data-action="updateGraph" data-units="hours">[[admin/dashboard:page-views-last-day]]</a></div>
|
||||
</div>
|
||||
<div class="col-sm-3 text-center pageview-stats">
|
||||
<div><strong><i class="fa fa-clock-o"></i></strong></div>
|
||||
<div><a href="#" class="updatePageviewsGraph" data-action="updateGraph" data-units="custom">[[admin/dashboard:page-views-custom]]</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
49
src/views/admin/partials/dashboard/stats.tpl
Normal file
49
src/views/admin/partials/dashboard/stats.tpl
Normal file
@@ -0,0 +1,49 @@
|
||||
<div class="row">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th class="text-right">[[admin/dashboard:stats.yesterday]]</th>
|
||||
<th class="text-right">[[admin/dashboard:stats.today]]</th>
|
||||
<th></th>
|
||||
<th class="text-right">[[admin/dashboard:stats.last-week]]</th>
|
||||
<th class="text-right">[[admin/dashboard:stats.this-week]]</th>
|
||||
<th></th>
|
||||
<th class="text-right">[[admin/dashboard:stats.last-month]]</th>
|
||||
<th class="text-right">[[admin/dashboard:stats.this-month]]</th>
|
||||
<th></th>
|
||||
<th class="text-right">[[admin/dashboard:stats.all]]</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- BEGIN stats -->
|
||||
<tr>
|
||||
<td>
|
||||
<strong>
|
||||
{{{ if ../href }}}
|
||||
<a href="{../href}">{../name}</a>
|
||||
{{{ else }}}
|
||||
{../name}
|
||||
{{{ end }}}
|
||||
</strong>
|
||||
</td>
|
||||
<td class="text-right formatted-number">{stats.yesterday}</td>
|
||||
<td class="text-right formatted-number">{stats.today}</td>
|
||||
<td class="{stats.dayTextClass}"><small>{stats.dayIncrease}%</small></td>
|
||||
|
||||
<td class="text-right formatted-number">{stats.lastweek}</td>
|
||||
<td class="text-right formatted-number">{stats.thisweek}</td>
|
||||
<td class="{stats.weekTextClass}"><small>{stats.weekIncrease}%</small></td>
|
||||
|
||||
<td class="text-right formatted-number">{stats.lastmonth}</td>
|
||||
<td class="text-right formatted-number">{stats.thismonth}</td>
|
||||
<td class="{stats.monthTextClass}"><small>{stats.monthIncrease}%</small></td>
|
||||
|
||||
<td class="text-right formatted-number">{stats.alltime}</td>
|
||||
</tr>
|
||||
<!-- END stats -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user