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,16 +5,20 @@ | ||||
| 		max-width: 100% !important; | ||||
| 	} | ||||
|  | ||||
| 	.graph-container { | ||||
| 		padding-right: 50px; | ||||
| 		position: relative; | ||||
| 		background: @body-bg; | ||||
|  | ||||
| 		.fa-expand { | ||||
| 	#analytics-panel .panel-heading i { | ||||
| 		&.fa-expand { | ||||
| 			display: none; | ||||
| 			position: absolute; | ||||
| 			right: 20px; | ||||
| 			padding: 5px; | ||||
| 		} | ||||
|  | ||||
| 		&.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; | ||||
| @@ -25,6 +29,11 @@ | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	.graph-container { | ||||
| 		padding-right: 50px; | ||||
| 		position: relative; | ||||
| 		background: @body-bg; | ||||
|  | ||||
| 		&:hover { | ||||
| 			.fa-expand { | ||||
| 				color: @gray-lighter; | ||||
|   | ||||
| @@ -461,6 +461,15 @@ define('admin/general/dashboard', ['semver', 'Chart', 'translator', 'benchpress' | ||||
| 			currentGraph.units = units; | ||||
| 			currentGraph.until = until; | ||||
| 			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() { | ||||
| 		var container = document.getElementById('analytics-traffic-container'); | ||||
| 		var container = document.getElementById('analytics-panel'); | ||||
| 		var $container = $(container); | ||||
| 		var btn = $container.find('.fa-expand'); | ||||
| 		var fsMethod; | ||||
|   | ||||
| @@ -259,3 +259,5 @@ Analytics.getBlacklistAnalytics = function (callback) { | ||||
| 		hourly: async.apply(Analytics.getHourlyStatsForSet, 'analytics:blacklist', Date.now(), 24), | ||||
| 	}, callback); | ||||
| }; | ||||
|  | ||||
| Analytics.async = require('./promisify')(Analytics); | ||||
|   | ||||
| @@ -4,10 +4,12 @@ var async = require('async'); | ||||
| var nconf = require('nconf'); | ||||
| var semver = require('semver'); | ||||
| var winston = require('winston'); | ||||
| const _ = require('lodash'); | ||||
|  | ||||
| var versions = require('../../admin/versions'); | ||||
| var db = require('../../database'); | ||||
| var meta = require('../../meta'); | ||||
| const analytics = require('../../analytics').async; | ||||
| var plugins = require('../../plugins'); | ||||
| var user = require('../../user'); | ||||
| var utils = require('../../utils'); | ||||
| @@ -76,6 +78,40 @@ dashboardController.get = function (req, res, 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) { | ||||
| 	async.waterfall([ | ||||
| 		function (next) { | ||||
|   | ||||
| @@ -5,6 +5,7 @@ var express = require('express'); | ||||
|  | ||||
| function apiRoutes(router, middleware, controllers) { | ||||
| 	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 multipartMiddleware = multipart(); | ||||
|   | ||||
| @@ -1,10 +1,15 @@ | ||||
| <div class="row dashboard"> | ||||
| 	<div class="col-lg-9"> | ||||
| 		<div class="panel panel-default"> | ||||
| 			<div class="panel-heading">[[admin/general/dashboard:forum-traffic]]</div> | ||||
| 		<div class="panel panel-default" id="analytics-panel"> | ||||
| 			<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="graph-container" id="analytics-traffic-container"> | ||||
| 					<i class="fa fa-expand"></i> | ||||
| 					<canvas id="analytics-traffic" width="100%" height="400"></canvas> | ||||
| 				</div> | ||||
| 				<hr/> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user