mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-31 02:55:58 +01:00 
			
		
		
		
	feat: ACP analytics API route (#7725)
* feat: added API route for retrieving analytics via REST API * feat: sets is now optional, can pass in multiple sets * fix: moved expand and added json button to panel header * fix: matching api params to socket method * fix: update json api button url on graph change * fix: updated default counts based on passed in units
This commit is contained in:
		| @@ -5,26 +5,35 @@ | |||||||
| 		max-width: 100% !important; | 		max-width: 100% !important; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	#analytics-panel .panel-heading i { | ||||||
|  | 		&.fa-expand { | ||||||
|  | 			display: none; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		&.fa-terminal::after { | ||||||
|  | 			content: 'JSON'; | ||||||
|  | 			font-family: @font-family-sans-serif; | ||||||
|  | 			font-weight: 600; | ||||||
|  | 			color: @gray-dark; | ||||||
|  | 			padding-left: .5em; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		padding: .75em; | ||||||
|  | 		background-color: @gray-lighter; | ||||||
|  | 		color: @gray-base; | ||||||
|  | 		cursor: pointer; | ||||||
|  | 		.transition(all .4s); | ||||||
|  |  | ||||||
|  | 		&.active { | ||||||
|  | 			display: inline; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	.graph-container { | 	.graph-container { | ||||||
| 		padding-right: 50px; | 		padding-right: 50px; | ||||||
| 		position: relative; | 		position: relative; | ||||||
| 		background: @body-bg; | 		background: @body-bg; | ||||||
|  |  | ||||||
| 		.fa-expand { |  | ||||||
| 			display: none; |  | ||||||
| 			position: absolute; |  | ||||||
| 			right: 20px; |  | ||||||
| 			padding: 5px; |  | ||||||
| 			background-color: @gray-lighter; |  | ||||||
| 			color: @gray-base; |  | ||||||
| 			cursor: pointer; |  | ||||||
| 			.transition(all .4s); |  | ||||||
|  |  | ||||||
| 			&.active { |  | ||||||
| 				display: inline; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		&:hover { | 		&:hover { | ||||||
| 			.fa-expand { | 			.fa-expand { | ||||||
| 				color: @gray-lighter; | 				color: @gray-lighter; | ||||||
|   | |||||||
| @@ -461,6 +461,15 @@ define('admin/general/dashboard', ['semver', 'Chart', 'translator', 'benchpress' | |||||||
| 			currentGraph.units = units; | 			currentGraph.units = units; | ||||||
| 			currentGraph.until = until; | 			currentGraph.until = until; | ||||||
| 			currentGraph.amount = amount; | 			currentGraph.amount = amount; | ||||||
|  |  | ||||||
|  | 			// Update the View as JSON button url | ||||||
|  | 			var apiEl = $('#view-as-json'); | ||||||
|  | 			var newHref = $.param({ | ||||||
|  | 				units: units, | ||||||
|  | 				until: until, | ||||||
|  | 				count: amount, | ||||||
|  | 			}); | ||||||
|  | 			apiEl.attr('href', config.relative_path + '/api/admin/analytics?' + newHref); | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -556,7 +565,7 @@ define('admin/general/dashboard', ['semver', 'Chart', 'translator', 'benchpress' | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	function setupFullscreen() { | 	function setupFullscreen() { | ||||||
| 		var container = document.getElementById('analytics-traffic-container'); | 		var container = document.getElementById('analytics-panel'); | ||||||
| 		var $container = $(container); | 		var $container = $(container); | ||||||
| 		var btn = $container.find('.fa-expand'); | 		var btn = $container.find('.fa-expand'); | ||||||
| 		var fsMethod; | 		var fsMethod; | ||||||
|   | |||||||
| @@ -259,3 +259,5 @@ Analytics.getBlacklistAnalytics = function (callback) { | |||||||
| 		hourly: async.apply(Analytics.getHourlyStatsForSet, 'analytics:blacklist', Date.now(), 24), | 		hourly: async.apply(Analytics.getHourlyStatsForSet, 'analytics:blacklist', Date.now(), 24), | ||||||
| 	}, callback); | 	}, callback); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | Analytics.async = require('./promisify')(Analytics); | ||||||
|   | |||||||
| @@ -4,10 +4,12 @@ var async = require('async'); | |||||||
| var nconf = require('nconf'); | var nconf = require('nconf'); | ||||||
| var semver = require('semver'); | var semver = require('semver'); | ||||||
| var winston = require('winston'); | var winston = require('winston'); | ||||||
|  | const _ = require('lodash'); | ||||||
|  |  | ||||||
| var versions = require('../../admin/versions'); | var versions = require('../../admin/versions'); | ||||||
| var db = require('../../database'); | var db = require('../../database'); | ||||||
| var meta = require('../../meta'); | var meta = require('../../meta'); | ||||||
|  | const analytics = require('../../analytics').async; | ||||||
| var plugins = require('../../plugins'); | var plugins = require('../../plugins'); | ||||||
| var user = require('../../user'); | var user = require('../../user'); | ||||||
| var utils = require('../../utils'); | var utils = require('../../utils'); | ||||||
| @@ -76,6 +78,40 @@ dashboardController.get = function (req, res, next) { | |||||||
| 	], next); | 	], next); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | dashboardController.getAnalytics = async (req, res, next) => { | ||||||
|  | 	// Basic validation | ||||||
|  | 	const validUnits = ['days', 'hours']; | ||||||
|  | 	const validSets = ['uniquevisitors', 'pageviews', 'pageviews:registered', 'pageviews:bot', 'pageviews:guest']; | ||||||
|  | 	const until = req.query.until ? new Date(parseInt(req.query.until, 10)) : Date.now(); | ||||||
|  | 	const count = req.query.count || (req.query.units === 'hours' ? 24 : 30); | ||||||
|  | 	if (isNaN(until) || !validUnits.includes(req.query.units)) { | ||||||
|  | 		return next(new Error('[[error:invalid-data]]')); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Filter out invalid sets, if no sets, assume all sets | ||||||
|  | 	let sets; | ||||||
|  | 	if (req.query.sets) { | ||||||
|  | 		sets = Array.isArray(req.query.sets) ? req.query.sets : [req.query.sets]; | ||||||
|  | 		sets = sets.filter(set => validSets.includes(set)); | ||||||
|  | 	} else { | ||||||
|  | 		sets = validSets; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	const method = req.query.units === 'days' ? analytics.getDailyStatsForSet : analytics.getHourlyStatsForSet; | ||||||
|  | 	let payload = await Promise.all(sets.map(async set => method('analytics:' + set, until, count))); | ||||||
|  | 	payload = _.zipObject(sets, payload); | ||||||
|  |  | ||||||
|  | 	res.json({ | ||||||
|  | 		query: { | ||||||
|  | 			set: req.query.set, | ||||||
|  | 			units: req.query.units, | ||||||
|  | 			until: until, | ||||||
|  | 			count: count, | ||||||
|  | 		}, | ||||||
|  | 		result: payload, | ||||||
|  | 	}); | ||||||
|  | }; | ||||||
|  |  | ||||||
| function getStats(callback) { | function getStats(callback) { | ||||||
| 	async.waterfall([ | 	async.waterfall([ | ||||||
| 		function (next) { | 		function (next) { | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ var express = require('express'); | |||||||
|  |  | ||||||
| function apiRoutes(router, middleware, controllers) { | function apiRoutes(router, middleware, controllers) { | ||||||
| 	router.get('/users/csv', middleware.authenticate, controllers.admin.users.getCSV); | 	router.get('/users/csv', middleware.authenticate, controllers.admin.users.getCSV); | ||||||
|  | 	router.get('/analytics', middleware.authenticate, controllers.admin.dashboard.getAnalytics); | ||||||
|  |  | ||||||
| 	var multipart = require('connect-multiparty'); | 	var multipart = require('connect-multiparty'); | ||||||
| 	var multipartMiddleware = multipart(); | 	var multipartMiddleware = multipart(); | ||||||
|   | |||||||
| @@ -1,10 +1,15 @@ | |||||||
| <div class="row dashboard"> | <div class="row dashboard"> | ||||||
| 	<div class="col-lg-9"> | 	<div class="col-lg-9"> | ||||||
| 		<div class="panel panel-default"> | 		<div class="panel panel-default" id="analytics-panel"> | ||||||
| 			<div class="panel-heading">[[admin/general/dashboard:forum-traffic]]</div> | 			<div class="panel-heading"> | ||||||
|  | 				[[admin/general/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="panel-body"> | ||||||
| 				<div class="graph-container" id="analytics-traffic-container"> | 				<div class="graph-container" id="analytics-traffic-container"> | ||||||
| 					<i class="fa fa-expand"></i> |  | ||||||
| 					<canvas id="analytics-traffic" width="100%" height="400"></canvas> | 					<canvas id="analytics-traffic" width="100%" height="400"></canvas> | ||||||
| 				</div> | 				</div> | ||||||
| 				<hr/> | 				<hr/> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user