2014-10-08 15:36:47 -04:00
"use strict" ;
2016-02-26 11:29:17 -05:00
/*global define, ajaxify, app, socket, utils, bootbox, RELATIVE_PATH*/
2014-10-08 15:36:47 -04:00
2016-02-26 11:29:17 -05:00
define ( 'admin/general/dashboard' , [ 'semver' , 'Chart' ] , function ( semver , Chart ) {
2016-07-29 19:31:48 +00:00
var Admin = { } ;
var intervals = {
2014-10-08 15:36:47 -04:00
rooms : false ,
graphs : false
2016-07-29 19:31:48 +00:00
} ;
var isMobile = false ;
var isPrerelease = /^v?\d+\.\d+\.\d+-.+$/ ;
var graphData = {
2015-07-08 16:54:39 -04:00
rooms : { } ,
traffic : { }
2016-07-29 19:31:48 +00:00
} ;
var currentGraph = {
2015-11-25 14:48:32 -05:00
units : 'hours' ,
until : undefined
} ;
2014-10-08 15:36:47 -04:00
2015-07-08 16:35:59 -04:00
var DEFAULTS = {
roomInterval : 10000 ,
graphInterval : 15000 ,
realtimeInterval : 1500
} ;
2016-03-09 21:36:20 +02:00
$ ( window ) . on ( 'action:ajaxify.start' , function ( ev , data ) {
clearInterval ( intervals . rooms ) ;
clearInterval ( intervals . graphs ) ;
intervals . rooms = null ;
intervals . graphs = null ;
graphData . rooms = null ;
graphData . traffic = null ;
usedTopicColors . length = 0 ;
} ) ;
2014-10-08 15:36:47 -04:00
Admin . init = function ( ) {
app . enterRoom ( 'admin' ) ;
2015-11-04 17:43:43 -05:00
socket . emit ( 'admin.rooms.getAll' , Admin . updateRoomUsage ) ;
2014-10-08 15:36:47 -04:00
2014-10-17 23:14:17 -04:00
isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i . test ( navigator . userAgent ) ;
2014-10-08 15:36:47 -04:00
$ . get ( 'https://api.github.com/repos/NodeBB/NodeBB/tags' , function ( releases ) {
// Re-sort the releases, as they do not follow Semver (wrt pre-releases)
releases = releases . sort ( function ( a , b ) {
a = a . name . replace ( /^v/ , '' ) ;
b = b . name . replace ( /^v/ , '' ) ;
return semver . lt ( a , b ) ? 1 : - 1 ;
2016-03-12 11:27:07 -05:00
} ) . filter ( function ( version ) {
return ! isPrerelease . test ( version . name ) ; // filter out automated prerelease versions
2014-10-08 15:36:47 -04:00
} ) ;
var version = $ ( '#version' ) . html ( ) ,
latestVersion = releases [ 0 ] . name . slice ( 1 ) ,
checkEl = $ ( '.version-check' ) ;
// Alter box colour accordingly
if ( semver . eq ( latestVersion , version ) ) {
checkEl . removeClass ( 'alert-info' ) . addClass ( 'alert-success' ) ;
checkEl . append ( '<p>You are <strong>up-to-date</strong> <i class="fa fa-check"></i></p>' ) ;
} else if ( semver . gt ( latestVersion , version ) ) {
2016-03-12 11:27:07 -05:00
checkEl . removeClass ( 'alert-info' ) . addClass ( 'alert-warning' ) ;
if ( ! isPrerelease . test ( version ) ) {
checkEl . append ( '<p>A new version (v' + latestVersion + ') has been released. Consider <a href="https://docs.nodebb.org/en/latest/upgrading/index.html">upgrading your NodeBB</a>.</p>' ) ;
} else {
checkEl . append ( '<p>This is an outdated pre-release version of NodeBB. A new version (v' + latestVersion + ') has been released. Consider <a href="https://docs.nodebb.org/en/latest/upgrading/index.html">upgrading your NodeBB</a>.</p>' ) ;
}
} else if ( isPrerelease . test ( version ) ) {
checkEl . removeClass ( 'alert-info' ) . addClass ( 'alert-info' ) ;
checkEl . append ( '<p>This is a <strong>pre-release</strong> version of NodeBB. Unintended bugs may occur. <i class="fa fa-exclamation-triangle"></i>.</p>' ) ;
2014-10-08 15:36:47 -04:00
}
} ) ;
2015-05-29 16:47:12 -04:00
$ ( '[data-toggle="tooltip"]' ) . tooltip ( ) ;
2015-07-14 17:03:28 -04:00
2015-07-08 16:35:59 -04:00
setupRealtimeButton ( ) ;
setupGraphs ( ) ;
initiateDashboard ( ) ;
2014-10-08 15:36:47 -04:00
} ;
Admin . updateRoomUsage = function ( err , data ) {
if ( err ) {
return app . alertError ( err . message ) ;
}
2015-07-08 16:54:39 -04:00
if ( JSON . stringify ( graphData . rooms ) === JSON . stringify ( data ) ) {
return ;
}
graphData . rooms = data ;
2014-10-17 23:04:50 -04:00
var html = '<div class="text-center pull-left">' +
'<div>' + data . onlineRegisteredCount + '</div>' +
2014-10-08 15:36:47 -04:00
'<div>Users</div>' +
'</div>' +
2014-10-17 23:04:50 -04:00
'<div class="text-center pull-left">' +
'<div>' + data . onlineGuestCount + '</div>' +
2014-10-08 15:36:47 -04:00
'<div>Guests</div>' +
'</div>' +
2014-10-17 23:04:50 -04:00
'<div class="text-center pull-left">' +
'<div>' + ( data . onlineRegisteredCount + data . onlineGuestCount ) + '</div>' +
2014-10-08 15:36:47 -04:00
'<div>Total</div>' +
'</div>' +
2014-10-17 23:04:50 -04:00
'<div class="text-center pull-left">' +
'<div>' + data . socketCount + '</div>' +
2014-10-08 15:36:47 -04:00
'<div>Connections</div>' +
'</div>' ;
updateRegisteredGraph ( data . onlineRegisteredCount , data . onlineGuestCount ) ;
2015-03-26 16:32:17 -04:00
updatePresenceGraph ( data . users ) ;
2014-10-08 15:36:47 -04:00
updateTopicsGraph ( data . topics ) ;
$ ( '#active-users' ) . html ( html ) ;
} ;
var graphs = {
traffic : null ,
registered : null ,
presence : null ,
topics : null
} ;
2016-07-29 19:31:48 +00:00
var topicColors = [ "#bf616a" , "#5B90BF" , "#d08770" , "#ebcb8b" , "#a3be8c" , "#96b5b4" , "#8fa1b3" , "#b48ead" , "#ab7967" , "#46BFBD" ] ;
var usedTopicColors = [ ] ;
2014-10-08 15:36:47 -04:00
// from chartjs.org
function lighten ( col , amt ) {
var usePound = false ;
if ( col [ 0 ] == "#" ) {
col = col . slice ( 1 ) ;
usePound = true ;
}
var num = parseInt ( col , 16 ) ;
var r = ( num >> 16 ) + amt ;
if ( r > 255 ) r = 255 ;
else if ( r < 0 ) r = 0 ;
var b = ( ( num >> 8 ) & 0x00FF ) + amt ;
if ( b > 255 ) b = 255 ;
else if ( b < 0 ) b = 0 ;
var g = ( num & 0x0000FF ) + amt ;
if ( g > 255 ) g = 255 ;
else if ( g < 0 ) g = 0 ;
return ( usePound ? "#" : "" ) + ( g | ( b << 8 ) | ( r << 16 ) ) . toString ( 16 ) ;
}
function setupGraphs ( ) {
var trafficCanvas = document . getElementById ( 'analytics-traffic' ) ,
registeredCanvas = document . getElementById ( 'analytics-registered' ) ,
presenceCanvas = document . getElementById ( 'analytics-presence' ) ,
topicsCanvas = document . getElementById ( 'analytics-topics' ) ,
trafficCtx = trafficCanvas . getContext ( '2d' ) ,
registeredCtx = registeredCanvas . getContext ( '2d' ) ,
presenceCtx = presenceCanvas . getContext ( '2d' ) ,
topicsCtx = topicsCanvas . getContext ( '2d' ) ,
2016-02-25 16:12:50 -05:00
trafficLabels = utils . getHoursArray ( ) ;
2014-10-08 15:36:47 -04:00
if ( isMobile ) {
Chart . defaults . global . showTooltips = false ;
2016-03-09 13:58:08 -05:00
Chart . defaults . global . animation = false ;
2014-10-08 15:36:47 -04:00
}
var data = {
labels : trafficLabels ,
datasets : [
{
label : "Page Views" ,
2016-07-29 19:31:48 +00:00
backgroundColor : "rgba(220,220,220,0.2)" ,
borderColor : "rgba(220,220,220,1)" ,
pointBackgroundColor : "rgba(220,220,220,1)" ,
pointHoverBackgroundColor : "#fff" ,
pointBorderColor : "#fff" ,
pointHoverBorderColor : "rgba(220,220,220,1)" ,
2014-12-20 20:34:46 -05:00
data : [ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ]
2014-10-08 15:36:47 -04:00
} ,
{
label : "Unique Visitors" ,
2016-07-29 19:31:48 +00:00
backgroundColor : "rgba(151,187,205,0.2)" ,
borderColor : "rgba(151,187,205,1)" ,
pointBackgroundColor : "rgba(151,187,205,1)" ,
pointHoverBackgroundColor : "#fff" ,
pointBorderColor : "#fff" ,
pointHoverBorderColor : "rgba(151,187,205,1)" ,
2014-12-20 20:34:46 -05:00
data : [ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ]
2014-10-08 15:36:47 -04:00
}
]
} ;
2016-03-09 13:58:08 -05:00
trafficCanvas . width = $ ( trafficCanvas ) . parent ( ) . width ( ) ;
2016-07-29 19:31:48 +00:00
graphs . traffic = new Chart ( trafficCtx , {
type : 'line' ,
data : data ,
options : {
responsive : true ,
legend : {
display : false
} ,
scales : {
yAxes : [ {
ticks : {
beginAtZero : true
}
} ]
}
}
2014-10-08 15:36:47 -04:00
} ) ;
2016-07-29 19:31:48 +00:00
graphs . registered = new Chart ( registeredCtx , {
type : 'doughnut' ,
data : {
labels : [ "Registered Users" , "Anonymous Users" ] ,
datasets : [ {
data : [ 1 , 1 ] ,
backgroundColor : [ "#F7464A" , "#46BFBD" ] ,
hoverBackgroundColor : [ "#FF5A5E" , "#5AD3D1" ]
} ]
2015-03-26 16:32:17 -04:00
} ,
2016-07-29 19:31:48 +00:00
options : {
responsive : true ,
legend : {
display : false
}
2015-04-10 16:45:37 -04:00
}
2016-07-29 19:31:48 +00:00
} ) ;
2014-10-08 15:36:47 -04:00
2016-07-29 19:31:48 +00:00
graphs . presence = new Chart ( presenceCtx , {
type : 'doughnut' ,
data : {
labels : [ "On categories list" , "Reading posts" , "Browsing topics" , "Recent" , "Unread" ] ,
datasets : [ {
data : [ 1 , 1 , 1 , 1 , 1 ] ,
backgroundColor : [ "#F7464A" , "#46BFBD" , "#FDB45C" , "#949FB1" , "#9FB194" ] ,
hoverBackgroundColor : [ "#FF5A5E" , "#5AD3D1" , "#FFC870" , "#A8B3C5" , "#A8B3C5" ]
} ]
} ,
options : {
responsive : true ,
legend : {
display : false
}
}
} ) ;
graphs . topics = new Chart ( topicsCtx , {
type : 'doughnut' ,
data : {
labels : [ ] ,
datasets : [ {
data : [ ] ,
backgroundColor : [ ] ,
hoverBackgroundColor : [ ]
} ]
} ,
options : {
responsive : true ,
legend : {
display : false
}
}
} ) ;
2014-10-08 15:36:47 -04:00
updateTrafficGraph ( ) ;
$ ( window ) . on ( 'resize' , adjustPieCharts ) ;
adjustPieCharts ( ) ;
2015-11-25 14:22:32 -05:00
$ ( '[data-action="updateGraph"]' ) . on ( 'click' , function ( ) {
2015-11-25 14:48:32 -05:00
var until = undefined ;
switch ( $ ( this ) . attr ( 'data-until' ) ) {
case 'last-month' :
var lastMonth = new Date ( ) ;
lastMonth . setDate ( lastMonth . getDate ( ) - 30 ) ;
until = lastMonth . getTime ( ) ;
}
updateTrafficGraph ( $ ( this ) . attr ( 'data-units' ) , until ) ;
} ) ;
2014-10-08 15:36:47 -04:00
}
function adjustPieCharts ( ) {
$ ( '.pie-chart.legend-up' ) . each ( function ( ) {
var $this = $ ( this ) ;
if ( $this . width ( ) < 320 ) {
$this . addClass ( 'compact' ) ;
} else {
$this . removeClass ( 'compact' ) ;
}
} ) ;
}
2015-11-25 14:48:32 -05:00
function updateTrafficGraph ( units , until ) {
2014-10-19 16:13:10 -04:00
if ( ! app . isFocused ) {
2014-10-08 15:36:47 -04:00
return ;
}
2015-11-25 14:22:32 -05:00
socket . emit ( 'admin.analytics.get' , {
graph : 'traffic' ,
2015-11-25 14:48:32 -05:00
units : units || 'hours' ,
until : until
2015-11-25 14:22:32 -05:00
} , function ( err , data ) {
2015-07-08 16:54:39 -04:00
if ( JSON . stringify ( graphData . traffic ) === JSON . stringify ( data ) ) {
return ;
}
graphData . traffic = data ;
2015-11-25 14:22:32 -05:00
if ( units === 'days' ) {
2016-07-29 19:31:48 +00:00
graphs . traffic . data . xLabels = utils . getDaysArray ( until ) ;
2015-11-25 14:22:32 -05:00
} else {
2016-07-29 19:31:48 +00:00
graphs . traffic . data . xLabels = utils . getHoursArray ( ) ;
2015-11-25 16:44:52 -05:00
$ ( '#pageViewsThisMonth' ) . html ( data . monthlyPageViews . thisMonth ) ;
$ ( '#pageViewsLastMonth' ) . html ( data . monthlyPageViews . lastMonth ) ;
$ ( '#pageViewsPastDay' ) . html ( data . pastDay ) ;
utils . addCommasToNumbers ( $ ( '#pageViewsThisMonth' ) ) ;
utils . addCommasToNumbers ( $ ( '#pageViewsLastMonth' ) ) ;
utils . addCommasToNumbers ( $ ( '#pageViewsPastDay' ) ) ;
2014-10-08 15:36:47 -04:00
}
2016-07-29 19:31:48 +00:00
graphs . traffic . data . datasets [ 0 ] . data = data . pageviews ;
graphs . traffic . data . datasets [ 1 ] . data = data . uniqueVisitors ;
graphs . traffic . data . labels = graphs . traffic . data . xLabels ;
2014-10-08 15:36:47 -04:00
graphs . traffic . update ( ) ;
2015-11-25 14:48:32 -05:00
currentGraph . units = units ;
currentGraph . until = until ;
2014-10-08 15:36:47 -04:00
} ) ;
}
function updateRegisteredGraph ( registered , anonymous ) {
2016-07-29 19:31:48 +00:00
graphs . registered . data . datasets [ 0 ] . data [ 0 ] = registered ;
graphs . registered . data . datasets [ 0 ] . data [ 1 ] = anonymous ;
2014-10-08 15:36:47 -04:00
graphs . registered . update ( ) ;
}
2015-03-26 16:32:17 -04:00
function updatePresenceGraph ( users ) {
2016-07-29 19:31:48 +00:00
graphs . presence . data . datasets [ 0 ] . data [ 0 ] = users . categories ;
graphs . presence . data . datasets [ 0 ] . data [ 1 ] = users . topics ;
graphs . presence . data . datasets [ 0 ] . data [ 2 ] = users . category ;
graphs . presence . data . datasets [ 0 ] . data [ 3 ] = users . recent ;
graphs . presence . data . datasets [ 0 ] . data [ 4 ] = users . unread ;
2015-03-26 16:32:17 -04:00
2014-10-08 15:36:47 -04:00
graphs . presence . update ( ) ;
}
2016-07-29 19:31:48 +00:00
2014-10-08 15:36:47 -04:00
function updateTopicsGraph ( topics ) {
if ( ! Object . keys ( topics ) . length ) {
topics = { "0" : {
title : "No users browsing" ,
value : 1
} } ;
}
2016-07-29 19:31:48 +00:00
var tids = Object . keys ( topics ) ;
graphs . topics . data . labels = [ ] ;
graphs . topics . data . datasets [ 0 ] . data = [ ] ;
graphs . topics . data . datasets [ 0 ] . backgroundColor = [ ] ;
graphs . topics . data . datasets [ 0 ] . hoverBackgroundColor = [ ] ;
for ( var i = 0 , ii = tids . length ; i < ii ; i ++ ) {
graphs . topics . data . labels . push ( topics [ tids [ i ] ] . title ) ;
graphs . topics . data . datasets [ 0 ] . data . push ( topics [ tids [ i ] ] . value ) ;
graphs . topics . data . datasets [ 0 ] . backgroundColor . push ( topicColors [ i ] ) ;
graphs . topics . data . datasets [ 0 ] . hoverBackgroundColor . push ( lighten ( topicColors [ i ] , 10 ) ) ;
2014-10-08 15:36:47 -04:00
}
2016-07-29 19:31:48 +00:00
2014-10-08 15:36:47 -04:00
function buildTopicsLegend ( ) {
var legend = $ ( '#topics-legend' ) . html ( '' ) ;
2016-07-29 19:31:48 +00:00
for ( var i = 0 , ii = tids . length ; i < ii ; i ++ ) {
var topic = topics [ tids [ i ] ] ;
var label = topic . value === '0' ? topic . title : '<a title="' + topic . title + '"href="' + RELATIVE _PATH + '/topic/' + tids [ i ] + '" target="_blank"> ' + topic . title + '</a>' ;
2014-10-08 15:36:47 -04:00
legend . append (
'<li>' +
2016-07-29 19:31:48 +00:00
'<div style="background-color: ' + topicColors [ i ] + ';"></div>' +
'<span>' + label + '</span>' +
2014-10-08 15:36:47 -04:00
'</li>' ) ;
}
}
buildTopicsLegend ( ) ;
graphs . topics . update ( ) ;
}
2015-07-08 16:35:59 -04:00
function setupRealtimeButton ( ) {
$ ( '#toggle-realtime .fa' ) . on ( 'click' , function ( ) {
var $this = $ ( this ) ;
if ( $this . hasClass ( 'fa-toggle-on' ) ) {
$this . removeClass ( 'fa-toggle-on' ) . addClass ( 'fa-toggle-off' ) ;
$this . parent ( ) . find ( 'strong' ) . html ( 'OFF' ) ;
initiateDashboard ( false ) ;
} else {
$this . removeClass ( 'fa-toggle-off' ) . addClass ( 'fa-toggle-on' ) ;
$this . parent ( ) . find ( 'strong' ) . html ( 'ON' ) ;
initiateDashboard ( true ) ;
}
} ) ;
}
function initiateDashboard ( realtime ) {
clearInterval ( intervals . rooms ) ;
clearInterval ( intervals . graphs ) ;
intervals . rooms = setInterval ( function ( ) {
if ( app . isFocused && app . isConnected ) {
2015-11-04 17:43:43 -05:00
socket . emit ( 'admin.rooms.getAll' , Admin . updateRoomUsage ) ;
2015-07-08 16:35:59 -04:00
}
} , realtime ? DEFAULTS . realtimeInterval : DEFAULTS . roomInterval ) ;
2015-11-25 14:48:32 -05:00
intervals . graphs = setInterval ( function ( ) {
updateTrafficGraph ( currentGraph . units , currentGraph . until ) ;
} , realtime ? DEFAULTS . realtimeInterval : DEFAULTS . graphInterval ) ;
2015-07-08 16:35:59 -04:00
}
2014-10-08 15:36:47 -04:00
return Admin ;
2016-07-29 19:31:48 +00:00
} ) ;