Files
NodeBB/public/src/admin/general/dashboard.js

500 lines
13 KiB
JavaScript
Raw Normal View History

"use strict";
2015-02-25 19:03:32 -05:00
/*global define, ajaxify, app, socket, utils, bootbox, Chart, RELATIVE_PATH*/
2014-10-08 16:08:35 -04:00
define('admin/general/dashboard', ['semver'], function(semver) {
var Admin = {},
intervals = {
rooms: false,
graphs: false
},
2015-07-08 16:54:39 -04:00
isMobile = false,
graphData = {
rooms: {},
traffic: {}
},
currentGraph = {
units: 'hours',
until: undefined
};
2015-07-08 16:35:59 -04:00
var DEFAULTS = {
roomInterval: 10000,
graphInterval: 15000,
realtimeInterval: 1500
};
Admin.init = function() {
app.enterRoom('admin');
2015-11-04 17:43:43 -05:00
socket.emit('admin.rooms.getAll', Admin.updateRoomUsage);
2014-10-17 23:14:17 -04:00
isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
$(window).on('action:ajaxify.start', function(ev, data) {
clearInterval(intervals.rooms);
clearInterval(intervals.graphs);
intervals.rooms = null;
intervals.graphs = null;
2015-09-27 13:42:36 -04:00
graphData.rooms = null;
graphData.traffic = null;
usedTopicColors.length = 0;
});
$.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;
});
var version = $('#version').html(),
latestVersion = releases[0].name.slice(1),
checkEl = $('.version-check');
checkEl.html($('.version-check').html().replace('<i class="fa fa-spinner fa-spin"></i>', 'v' + latestVersion));
// 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)) {
checkEl.removeClass('alert-info').addClass('alert-danger');
2015-11-28 23:26:51 -05:00
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>');
}
});
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();
};
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>' +
'<div>Users</div>' +
'</div>' +
2014-10-17 23:04:50 -04:00
'<div class="text-center pull-left">' +
'<div>'+ data.onlineGuestCount +'</div>' +
'<div>Guests</div>' +
'</div>' +
2014-10-17 23:04:50 -04:00
'<div class="text-center pull-left">' +
'<div>'+ (data.onlineRegisteredCount + data.onlineGuestCount) +'</div>' +
'<div>Total</div>' +
'</div>' +
2014-10-17 23:04:50 -04:00
'<div class="text-center pull-left">' +
'<div>'+ data.socketCount +'</div>' +
'<div>Connections</div>' +
'</div>';
updateRegisteredGraph(data.onlineRegisteredCount, data.onlineGuestCount);
updatePresenceGraph(data.users);
updateTopicsGraph(data.topics);
$('#active-users').html(html);
};
var graphs = {
traffic: null,
registered: null,
presence: null,
topics: null
};
var topicColors = ["#bf616a","#5B90BF","#d08770","#ebcb8b","#a3be8c","#96b5b4","#8fa1b3","#b48ead","#ab7967","#46BFBD"],
usedTopicColors = [];
// 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 getHoursArray() {
var currentHour = new Date().getHours(),
labels = [];
2014-12-20 20:34:46 -05:00
for (var i = currentHour, ii = currentHour - 24; i > ii; i--) {
var hour = i < 0 ? 24 + i : i;
labels.push(hour + ':00');
}
return labels.reverse();
}
function getDaysArray(from) {
var currentDay = new Date(from || Date.now()).getTime(),
months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
labels = [],
tmpDate;
for(var x=29;x>=0;x--) {
tmpDate = new Date(currentDay - (1000*60*60*24*x));
labels.push(months[tmpDate.getMonth()] + ' ' + tmpDate.getDate());
}
return labels;
}
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'),
trafficLabels = getHoursArray();
if (isMobile) {
Chart.defaults.global.showTooltips = false;
}
var data = {
labels: trafficLabels,
datasets: [
{
label: "Page Views",
fillColor: "rgba(220,220,220,0.2)",
strokeColor: "rgba(220,220,220,1)",
pointColor: "rgba(220,220,220,1)",
pointStrokeColor: "#fff",
pointHighlightFill: "#fff",
pointHighlightStroke: "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]
},
{
label: "Unique Visitors",
fillColor: "rgba(151,187,205,0.2)",
strokeColor: "rgba(151,187,205,1)",
pointColor: "rgba(151,187,205,1)",
pointStrokeColor: "#fff",
pointHighlightFill: "#fff",
pointHighlightStroke: "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]
}
]
};
trafficCanvas.width = $(trafficCanvas).parent().width(); // is this necessary
graphs.traffic = new Chart(trafficCtx).Line(data, {
responsive: true
});
graphs.registered = new Chart(registeredCtx).Doughnut([{
value: 1,
color:"#F7464A",
highlight: "#FF5A5E",
label: "Registered Users"
},
{
value: 1,
color: "#46BFBD",
highlight: "#5AD3D1",
label: "Anonymous Users"
}], {
responsive: true
});
graphs.presence = new Chart(presenceCtx).Doughnut([{
value: 1,
color:"#F7464A",
highlight: "#FF5A5E",
label: "On categories list"
},
{
value: 1,
color: "#46BFBD",
highlight: "#5AD3D1",
label: "Reading posts"
},
{
value: 1,
color: "#FDB45C",
highlight: "#FFC870",
label: "Browsing topics"
},
{
value: 1,
color: "#949FB1",
highlight: "#A8B3C5",
2015-07-14 17:03:28 -04:00
label: "Recent"
},
{
value: 1,
2015-07-14 17:03:28 -04:00
color: "#9FB194",
highlight: "#A8B3C5",
2015-07-14 17:03:28 -04:00
label: "Unread"
2015-04-10 16:45:37 -04:00
}
], {
responsive: true
});
graphs.topics = new Chart(topicsCtx).Doughnut([], {responsive: true});
topicsCanvas.onclick = function(evt){
var obj = graphs.topics.getSegmentsAtEvent(evt);
if (obj && obj[0]) {
window.open(RELATIVE_PATH + '/topic/' + obj[0].tid);
}
};
updateTrafficGraph();
$(window).on('resize', adjustPieCharts);
adjustPieCharts();
$('[data-action="updateGraph"]').on('click', function() {
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);
});
}
function adjustPieCharts() {
$('.pie-chart.legend-up').each(function() {
var $this = $(this);
if ($this.width() < 320) {
$this.addClass('compact');
} else {
$this.removeClass('compact');
}
});
}
function updateTrafficGraph(units, until) {
2014-10-19 16:13:10 -04:00
if (!app.isFocused) {
return;
}
socket.emit('admin.analytics.get', {
graph: 'traffic',
units: units || 'hours',
until: until
}, function (err, data) {
2015-07-08 16:54:39 -04:00
if (JSON.stringify(graphData.traffic) === JSON.stringify(data)) {
return;
}
graphData.traffic = data;
// If new data set contains fewer points than currently shown, truncate
while(graphs.traffic.datasets[0].points.length > data.pageviews.length) {
graphs.traffic.removeData();
}
if (units === 'days') {
graphs.traffic.scale.xLabels = getDaysArray(until);
} else {
graphs.traffic.scale.xLabels = getHoursArray();
$('#pageViewsThisMonth').html(data.monthlyPageViews.thisMonth);
$('#pageViewsLastMonth').html(data.monthlyPageViews.lastMonth);
$('#pageViewsPastDay').html(data.pastDay);
utils.addCommasToNumbers($('#pageViewsThisMonth'));
utils.addCommasToNumbers($('#pageViewsLastMonth'));
utils.addCommasToNumbers($('#pageViewsPastDay'));
}
for (var i = 0, ii = data.pageviews.length; i < ii; i++) {
if (graphs.traffic.datasets[0].points[i]) {
graphs.traffic.datasets[0].points[i].value = data.pageviews[i];
graphs.traffic.datasets[0].points[i].label = graphs.traffic.scale.xLabels[i];
graphs.traffic.datasets[1].points[i].value = data.uniqueVisitors[i];
graphs.traffic.datasets[1].points[i].label = graphs.traffic.scale.xLabels[i];
} else {
// No points to replace? Add data.
graphs.traffic.addData([data.pageviews[i], data.uniqueVisitors[i]], graphs.traffic.scale.xLabels[i]);
}
}
graphs.traffic.update();
currentGraph.units = units;
currentGraph.until = until;
});
}
function updateRegisteredGraph(registered, anonymous) {
graphs.registered.segments[0].value = registered;
graphs.registered.segments[1].value = anonymous;
graphs.registered.update();
}
function updatePresenceGraph(users) {
graphs.presence.segments[0].value = users.categories;
graphs.presence.segments[1].value = users.topics;
graphs.presence.segments[2].value = users.category;
graphs.presence.segments[3].value = users.recent;
2015-07-14 17:03:28 -04:00
graphs.presence.segments[4].value = users.unread;
graphs.presence.update();
}
function updateTopicsGraph(topics) {
if (!Object.keys(topics).length) {
topics = {"0": {
title: "No users browsing",
value: 1
}};
}
var tids = Object.keys(topics),
segments = graphs.topics.segments;
function reassignExistingTopics() {
2014-10-17 23:04:50 -04:00
for (var i = segments.length - 1; i >= 0; i--) {
if (!segments[i]) {
continue;
}
var tid = segments[i].tid;
if ($.inArray(tid, tids) === -1) {
usedTopicColors.splice($.inArray(segments[i].fillColor, usedTopicColors), 1);
graphs.topics.removeData(i);
} else {
graphs.topics.segments[i].value = topics[tid].value;
delete topics[tid];
}
}
}
function assignNewTopics() {
while (segments.length < 10 && tids.length > 0) {
var tid = tids.pop(),
data = topics[tid],
color = null;
if (!data) {
continue;
}
if (tid === '0') {
color = '#4D5360';
} else {
do {
for (var i = 0, ii = topicColors.length; i < ii; i++) {
var chosenColor = topicColors[i];
if ($.inArray(chosenColor, usedTopicColors) === -1) {
color = chosenColor;
usedTopicColors.push(color);
break;
}
}
} while (color === null && usedTopicColors.length < topicColors.length);
}
if (color) {
graphs.topics.addData({
value: data.value,
color: color,
highlight: lighten(color, 10),
label: data.title
});
segments[segments.length - 1].tid = tid;
}
}
}
function buildTopicsLegend() {
var legend = $('#topics-legend').html('');
segments.sort(function(a, b) {
return b.value - a.value;
});
for (var i = 0, ii = segments.length; i < ii; i++) {
var topic = segments[i],
label = topic.tid === '0' ? topic.label : '<a title="' + topic.label + '"href="' + RELATIVE_PATH + '/topic/' + topic.tid + '" target="_blank"> ' + topic.label + '</a>';
legend.append(
'<li>' +
'<div style="background-color: ' + topic.highlightColor + '; border-color: ' + topic.strokeColor + '"></div>' +
'<span>' + label + '</span>' +
'</li>');
}
}
reassignExistingTopics();
assignNewTopics();
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);
intervals.graphs = setInterval(function() {
updateTrafficGraph(currentGraph.units, currentGraph.until);
}, realtime ? DEFAULTS.realtimeInterval : DEFAULTS.graphInterval);
2015-07-08 16:35:59 -04:00
}
return Admin;
});