diff --git a/public/less/admin/general/dashboard.less b/public/less/admin/general/dashboard.less index d47c14afe6..1cb207317d 100644 --- a/public/less/admin/general/dashboard.less +++ b/public/less/admin/general/dashboard.less @@ -5,26 +5,35 @@ 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 { padding-right: 50px; position: relative; 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 { .fa-expand { color: @gray-lighter; diff --git a/public/src/admin/general/dashboard.js b/public/src/admin/general/dashboard.js index 8009d7c7f1..a838397f11 100644 --- a/public/src/admin/general/dashboard.js +++ b/public/src/admin/general/dashboard.js @@ -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; diff --git a/src/analytics.js b/src/analytics.js index e6da10b44c..71f7110430 100644 --- a/src/analytics.js +++ b/src/analytics.js @@ -259,3 +259,5 @@ Analytics.getBlacklistAnalytics = function (callback) { hourly: async.apply(Analytics.getHourlyStatsForSet, 'analytics:blacklist', Date.now(), 24), }, callback); }; + +Analytics.async = require('./promisify')(Analytics); diff --git a/src/controllers/admin/dashboard.js b/src/controllers/admin/dashboard.js index 58aa633e69..9582d981cf 100644 --- a/src/controllers/admin/dashboard.js +++ b/src/controllers/admin/dashboard.js @@ -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) { diff --git a/src/routes/admin.js b/src/routes/admin.js index f0ec9038e3..f6a92eac61 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -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(); diff --git a/src/views/admin/general/dashboard.tpl b/src/views/admin/general/dashboard.tpl index 546462b82f..fac43fe73e 100644 --- a/src/views/admin/general/dashboard.tpl +++ b/src/views/admin/general/dashboard.tpl @@ -1,10 +1,15 @@
-
-
[[admin/general/dashboard:forum-traffic]]
+
+
+ [[admin/general/dashboard:forum-traffic]] +
+ + +
+
-