mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-31 11:05:54 +01:00 
			
		
		
		
	refactor: abstract out some client side dashboard code into modules, analytics subpages for users, topics, and logins
This commit is contained in:
		| @@ -1,5 +1,10 @@ | ||||
| { | ||||
| 	"dashboard": "Dashboard", | ||||
|  | ||||
| 	"section-dashboard": "Dashboard", | ||||
| 	"dashboard/logins": "Logins", | ||||
| 	"dashboard/users": "Users", | ||||
| 	"dashboard/topics": "Topics", | ||||
| 	"section-general": "General", | ||||
|  | ||||
| 	"section-manage": "Manage", | ||||
|   | ||||
							
								
								
									
										14
									
								
								public/src/admin/dashboard/logins.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								public/src/admin/dashboard/logins.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| define('admin/dashboard/logins', ['admin/modules/dashboard-line-graph'], (graph) => { | ||||
| 	const ACP = {}; | ||||
|  | ||||
| 	ACP.init = () => { | ||||
| 		graph.init({ | ||||
| 			set: 'logins', | ||||
| 			dataset: ajaxify.data.dataset, | ||||
| 		}); | ||||
| 	}; | ||||
|  | ||||
| 	return ACP; | ||||
| }); | ||||
							
								
								
									
										14
									
								
								public/src/admin/dashboard/topics.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								public/src/admin/dashboard/topics.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| define('admin/dashboard/topics', ['admin/modules/dashboard-line-graph'], (graph) => { | ||||
| 	const ACP = {}; | ||||
|  | ||||
| 	ACP.init = () => { | ||||
| 		graph.init({ | ||||
| 			set: 'topics', | ||||
| 			dataset: ajaxify.data.dataset, | ||||
| 		}); | ||||
| 	}; | ||||
|  | ||||
| 	return ACP; | ||||
| }); | ||||
							
								
								
									
										14
									
								
								public/src/admin/dashboard/users.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								public/src/admin/dashboard/users.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| define('admin/dashboard/users', ['admin/modules/dashboard-line-graph'], (graph) => { | ||||
| 	const ACP = {}; | ||||
|  | ||||
| 	ACP.init = () => { | ||||
| 		graph.init({ | ||||
| 			set: 'registrations', | ||||
| 			dataset: ajaxify.data.dataset, | ||||
| 		}); | ||||
| 	}; | ||||
|  | ||||
| 	return ACP; | ||||
| }); | ||||
							
								
								
									
										177
									
								
								public/src/admin/modules/dashboard-line-graph.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								public/src/admin/modules/dashboard-line-graph.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,177 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| define('admin/modules/dashboard-line-graph', ['Chart', 'translator', 'benchpress', 'api'], function (Chart, translator, Benchpress, api) { | ||||
| 	const Graph = { | ||||
| 		_current: null, | ||||
| 	}; | ||||
| 	let isMobile = false; | ||||
|  | ||||
| 	Graph.init = ({ set, dataset }) => { | ||||
| 		const canvas = document.getElementById('analytics-traffic'); | ||||
| 		const canvasCtx = canvas.getContext('2d'); | ||||
| 		const trafficLabels = utils.getHoursArray(); | ||||
|  | ||||
| 		isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); | ||||
| 		if (isMobile) { | ||||
| 			Chart.defaults.global.tooltips.enabled = false; | ||||
| 		} | ||||
|  | ||||
| 		var t = translator.Translator.create(); | ||||
| 		t.translateKey(`admin/menu:${ajaxify.data.template.name.replace('admin/', '')}`, []).then((key) => { | ||||
| 			const data = { | ||||
| 				labels: trafficLabels, | ||||
| 				datasets: [ | ||||
| 					{ | ||||
| 						label: key, | ||||
| 						backgroundColor: 'rgba(151,187,205,0.2)', | ||||
| 						borderColor: 'rgba(151,187,205,1)', | ||||
| 						pointBackgroundColor: 'rgba(151,187,205,1)', | ||||
| 						pointHoverBackgroundColor: 'rgba(151,187,205,1)', | ||||
| 						pointBorderColor: '#fff', | ||||
| 						pointHoverBorderColor: 'rgba(151,187,205,1)', | ||||
| 						data: dataset, | ||||
| 					}, | ||||
| 				], | ||||
| 			}; | ||||
|  | ||||
| 			canvas.width = $(canvas).parent().width(); | ||||
|  | ||||
| 			data.datasets[0].yAxisID = 'left-y-axis'; | ||||
|  | ||||
| 			Graph._current = new Chart(canvasCtx, { | ||||
| 				type: 'line', | ||||
| 				data: data, | ||||
| 				options: { | ||||
| 					responsive: true, | ||||
| 					legend: { | ||||
| 						display: true, | ||||
| 					}, | ||||
| 					scales: { | ||||
| 						yAxes: [{ | ||||
| 							id: 'left-y-axis', | ||||
| 							ticks: { | ||||
| 								beginAtZero: true, | ||||
| 								precision: 0, | ||||
| 							}, | ||||
| 							type: 'linear', | ||||
| 							position: 'left', | ||||
| 							scaleLabel: { | ||||
| 								display: true, | ||||
| 								labelString: key, | ||||
| 							}, | ||||
| 						}], | ||||
| 					}, | ||||
| 					tooltips: { | ||||
| 						mode: 'x', | ||||
| 					}, | ||||
| 				}, | ||||
| 			}); | ||||
| 		}); | ||||
|  | ||||
| 		$('[data-action="updateGraph"]:not([data-units="custom"])').on('click', function () { | ||||
| 			var until = new Date(); | ||||
| 			var amount = $(this).attr('data-amount'); | ||||
| 			if ($(this).attr('data-units') === 'days') { | ||||
| 				until.setHours(0, 0, 0, 0); | ||||
| 			} | ||||
| 			until = until.getTime(); | ||||
| 			Graph.update(set, $(this).attr('data-units'), until, amount); | ||||
| 			$('[data-action="updateGraph"]').removeClass('active'); | ||||
| 			$(this).addClass('active'); | ||||
|  | ||||
| 			require(['translator'], function (translator) { | ||||
| 				translator.translate('[[admin/dashboard:page-views-custom]]', function (translated) { | ||||
| 					$('[data-action="updateGraph"][data-units="custom"]').text(translated); | ||||
| 				}); | ||||
| 			}); | ||||
| 		}); | ||||
|  | ||||
| 		$('[data-action="updateGraph"][data-units="custom"]').on('click', function () { | ||||
| 			var targetEl = $(this); | ||||
|  | ||||
| 			Benchpress.render('admin/partials/pageviews-range-select', {}).then(function (html) { | ||||
| 				var modal = bootbox.dialog({ | ||||
| 					title: '[[admin/dashboard:page-views-custom]]', | ||||
| 					message: html, | ||||
| 					buttons: { | ||||
| 						submit: { | ||||
| 							label: '[[global:search]]', | ||||
| 							className: 'btn-primary', | ||||
| 							callback: submit, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}).on('shown.bs.modal', function () { | ||||
| 					var date = new Date(); | ||||
| 					var today = date.toISOString().substr(0, 10); | ||||
| 					date.setDate(date.getDate() - 1); | ||||
| 					var yesterday = date.toISOString().substr(0, 10); | ||||
|  | ||||
| 					modal.find('#startRange').val(targetEl.attr('data-startRange') || yesterday); | ||||
| 					modal.find('#endRange').val(targetEl.attr('data-endRange') || today); | ||||
| 				}); | ||||
|  | ||||
| 				function submit() { | ||||
| 					// NEED TO ADD VALIDATION HERE FOR YYYY-MM-DD | ||||
| 					var formData = modal.find('form').serializeObject(); | ||||
| 					var validRegexp = /\d{4}-\d{2}-\d{2}/; | ||||
|  | ||||
| 					// Input validation | ||||
| 					if (!formData.startRange && !formData.endRange) { | ||||
| 						// No range? Assume last 30 days | ||||
| 						Graph.update(set, 'days'); | ||||
| 						$('[data-action="updateGraph"]').removeClass('active'); | ||||
| 						$('[data-action="updateGraph"][data-units="days"]').addClass('active'); | ||||
| 						return; | ||||
| 					} else if (!validRegexp.test(formData.startRange) || !validRegexp.test(formData.endRange)) { | ||||
| 						// Invalid Input | ||||
| 						modal.find('.alert-danger').removeClass('hidden'); | ||||
| 						return false; | ||||
| 					} | ||||
|  | ||||
| 					var until = new Date(formData.endRange); | ||||
| 					until.setDate(until.getDate() + 1); | ||||
| 					until = until.getTime(); | ||||
| 					var amount = (until - new Date(formData.startRange).getTime()) / (1000 * 60 * 60 * 24); | ||||
|  | ||||
| 					Graph.update(set, 'days', until, amount); | ||||
| 					$('[data-action="updateGraph"]').removeClass('active'); | ||||
| 					targetEl.addClass('active'); | ||||
|  | ||||
| 					// Update "custom range" label | ||||
| 					targetEl.attr('data-startRange', formData.startRange); | ||||
| 					targetEl.attr('data-endRange', formData.endRange); | ||||
| 					targetEl.html(formData.startRange + ' – ' + formData.endRange); | ||||
| 				} | ||||
| 			}); | ||||
| 		}); | ||||
| 	}; | ||||
|  | ||||
| 	Graph.update = (set, units, until, amount) => { | ||||
| 		if (!Graph._current) { | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		api.get(`/admin/analytics/${set}`, { units, until, amount }).then((dataset) => { | ||||
| 			if (units === 'days') { | ||||
| 				Graph._current.data.xLabels = utils.getDaysArray(until, amount); | ||||
| 			} else { | ||||
| 				Graph._current.data.xLabels = utils.getHoursArray(); | ||||
| 			} | ||||
|  | ||||
| 			Graph._current.data.datasets[0].data = dataset; | ||||
| 			Graph._current.data.labels = Graph._current.data.xLabels; | ||||
| 			Graph._current.update(); | ||||
|  | ||||
| 			// Update the View as JSON button url | ||||
| 			var apiEl = $('#view-as-json'); | ||||
| 			var newHref = $.param({ | ||||
| 				units: units || 'hours', | ||||
| 				until: until, | ||||
| 				count: amount, | ||||
| 			}); | ||||
| 			apiEl.attr('href', `${config.relative_path}/api/v3/admin/analytics/${ajaxify.data.set}?${newHref}`); | ||||
| 		}); | ||||
| 	}; | ||||
|  | ||||
| 	return Graph; | ||||
| }); | ||||
| @@ -123,10 +123,17 @@ async function getStats() { | ||||
| 		getStatsForSet('topics:tid', 'topicCount'), | ||||
| 	]); | ||||
| 	results[0].name = '[[admin/dashboard:unique-visitors]]'; | ||||
|  | ||||
| 	results[1].name = '[[admin/dashboard:logins]]'; | ||||
| 	results[1].href = `${nconf.get('relative_path')}/admin/dashboard/logins`; | ||||
|  | ||||
| 	results[2].name = '[[admin/dashboard:new-users]]'; | ||||
| 	results[2].href = `${nconf.get('relative_path')}/admin/dashboard/users`; | ||||
|  | ||||
| 	results[3].name = '[[admin/dashboard:posts]]'; | ||||
|  | ||||
| 	results[4].name = '[[admin/dashboard:topics]]'; | ||||
| 	results[4].href = `${nconf.get('relative_path')}/admin/dashboard/topics`; | ||||
|  | ||||
| 	({ results } = await plugins.hooks.fire('filter:admin.getStats', { | ||||
| 		results, | ||||
| @@ -221,3 +228,66 @@ async function getLastRestart() { | ||||
| 	lastrestart.timestampISO = utils.toISOString(lastrestart.timestamp); | ||||
| 	return lastrestart; | ||||
| } | ||||
|  | ||||
| dashboardController.getLogins = async (req, res) => { | ||||
| 	let stats = await getStats(); | ||||
| 	const dataset = await analytics.getHourlyStatsForSet('analytics:logins', Date.now(), 24); | ||||
| 	stats = stats.filter(stat => stat.name === '[[admin/dashboard:logins]]').map(({ ...stat }) => { | ||||
| 		delete stat.href; | ||||
| 		return stat; | ||||
| 	}); | ||||
| 	const summary = { | ||||
| 		day: stats[0].today, | ||||
| 		week: stats[0].thisweek, | ||||
| 		month: stats[0].thismonth, | ||||
| 	}; | ||||
|  | ||||
| 	res.render('admin/dashboard/logins', { | ||||
| 		set: 'logins', | ||||
| 		stats, | ||||
| 		dataset, | ||||
| 		summary, | ||||
| 	}); | ||||
| }; | ||||
|  | ||||
| dashboardController.getUsers = async (req, res) => { | ||||
| 	let stats = await getStats(); | ||||
| 	const dataset = await analytics.getHourlyStatsForSet('analytics:registrations', Date.now(), 24); | ||||
| 	stats = stats.filter(stat => stat.name === '[[admin/dashboard:new-users]]').map(({ ...stat }) => { | ||||
| 		delete stat.href; | ||||
| 		return stat; | ||||
| 	}); | ||||
| 	const summary = { | ||||
| 		day: stats[0].today, | ||||
| 		week: stats[0].thisweek, | ||||
| 		month: stats[0].thismonth, | ||||
| 	}; | ||||
|  | ||||
| 	res.render('admin/dashboard/users', { | ||||
| 		set: 'registrations', | ||||
| 		stats, | ||||
| 		dataset, | ||||
| 		summary, | ||||
| 	}); | ||||
| }; | ||||
|  | ||||
| dashboardController.getTopics = async (req, res) => { | ||||
| 	let stats = await getStats(); | ||||
| 	const dataset = await analytics.getHourlyStatsForSet('analytics:topics', Date.now(), 24); | ||||
| 	stats = stats.filter(stat => stat.name === '[[admin/dashboard:topics]]').map(({ ...stat }) => { | ||||
| 		delete stat.href; | ||||
| 		return stat; | ||||
| 	}); | ||||
| 	const summary = { | ||||
| 		day: stats[0].today, | ||||
| 		week: stats[0].thisweek, | ||||
| 		month: stats[0].thismonth, | ||||
| 	}; | ||||
|  | ||||
| 	res.render('admin/dashboard/topics', { | ||||
| 		set: 'topics', | ||||
| 		stats, | ||||
| 		dataset, | ||||
| 		summary, | ||||
| 	}); | ||||
| }; | ||||
|   | ||||
| @@ -1,7 +1,9 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const user = require('../../user'); | ||||
| const meta = require('../../meta'); | ||||
| const privileges = require('../../privileges'); | ||||
| const analytics = require('../../analytics'); | ||||
|  | ||||
| const helpers = require('../helpers'); | ||||
|  | ||||
| @@ -17,3 +19,22 @@ Admin.updateSetting = async (req, res) => { | ||||
| 	await meta.configs.set(req.params.setting, req.body.value); | ||||
| 	helpers.formatApiResponse(200, res); | ||||
| }; | ||||
|  | ||||
| Admin.getAnalytics = async (req, res) => { | ||||
| 	const ok = await user.isAdministrator(req.uid); | ||||
|  | ||||
| 	if (!ok) { | ||||
| 		return helpers.formatApiResponse(403, res); | ||||
| 	} | ||||
|  | ||||
| 	// Default returns views from past 24 hours, by hour | ||||
| 	if (!req.query.amount) { | ||||
| 		if (req.query.units === 'days') { | ||||
| 			req.query.amount = 30; | ||||
| 		} else { | ||||
| 			req.query.amount = 24; | ||||
| 		} | ||||
| 	} | ||||
| 	const getStats = req.query.units === 'days' ? analytics.getDailyStatsForSet : analytics.getHourlyStatsForSet; | ||||
| 	helpers.formatApiResponse(200, res, await getStats(`analytics:${req.params.set}`, parseInt(req.query.until, 10) || Date.now(), req.query.amount)); | ||||
| }; | ||||
|   | ||||
| @@ -8,6 +8,9 @@ module.exports = function (app, name, middleware, controllers) { | ||||
| 	helpers.setupAdminPageRoute(app, `/${name}`, middleware, middlewares, controllers.admin.routeIndex); | ||||
|  | ||||
| 	helpers.setupAdminPageRoute(app, `/${name}/dashboard`, middleware, middlewares, controllers.admin.dashboard.get); | ||||
| 	helpers.setupAdminPageRoute(app, `/${name}/dashboard/logins`, middleware, middlewares, controllers.admin.dashboard.getLogins); | ||||
| 	helpers.setupAdminPageRoute(app, `/${name}/dashboard/users`, middleware, middlewares, controllers.admin.dashboard.getUsers); | ||||
| 	helpers.setupAdminPageRoute(app, `/${name}/dashboard/topics`, middleware, middlewares, controllers.admin.dashboard.getTopics); | ||||
|  | ||||
| 	helpers.setupAdminPageRoute(app, `/${name}/manage/categories`, middleware, middlewares, controllers.admin.categories.getAll); | ||||
| 	helpers.setupAdminPageRoute(app, `/${name}/manage/categories/:category_id`, middleware, middlewares, controllers.admin.categories.get); | ||||
|   | ||||
| @@ -12,5 +12,7 @@ module.exports = function () { | ||||
|  | ||||
| 	setupApiRoute(router, 'put', '/settings/:setting', [...middlewares, middleware.checkRequired.bind(null, ['value'])], controllers.write.admin.updateSetting); | ||||
|  | ||||
| 	setupApiRoute(router, 'get', '/analytics/:set', [...middlewares], controllers.write.admin.getAnalytics); | ||||
|  | ||||
| 	return router; | ||||
| }; | ||||
|   | ||||
| @@ -9,6 +9,7 @@ const slugify = require('../slugify'); | ||||
| const plugins = require('../plugins'); | ||||
| const groups = require('../groups'); | ||||
| const meta = require('../meta'); | ||||
| const analytics = require('../analytics'); | ||||
|  | ||||
| module.exports = function (User) { | ||||
| 	User.create = async function (data) { | ||||
| @@ -108,6 +109,7 @@ module.exports = function (User) { | ||||
|  | ||||
| 		await Promise.all([ | ||||
| 			db.incrObjectField('global', 'userCount'), | ||||
| 			analytics.increment('registrations'), | ||||
| 			db.sortedSetAddBulk(bulkAdd), | ||||
| 			groups.join(groupsToJoin, userData.uid), | ||||
| 			User.notifications.sendWelcomeNotification(userData.uid), | ||||
|   | ||||
| @@ -1,80 +1,7 @@ | ||||
| <div class="row dashboard"> | ||||
| 	<div class="col-lg-9"> | ||||
| 		<div class="panel panel-default" id="analytics-panel"> | ||||
| 			<div class="panel-heading"> | ||||
| 				[[admin/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"> | ||||
| 					<canvas id="analytics-traffic" width="100%" height="400"></canvas> | ||||
| 				</div> | ||||
| 				<hr/> | ||||
| 				<div class="row"> | ||||
| 					<div class="col-sm-3 hidden-xs text-center pageview-stats"> | ||||
| 						<div><strong id="pageViewsThirty">0</strong></div> | ||||
| 						<div><a href="#" class="updatePageviewsGraph" data-action="updateGraph" data-units="days" data-amount="30">[[admin/dashboard:page-views-thirty]]</a></div> | ||||
| 					</div> | ||||
| 					<div class="col-sm-3 text-center pageview-stats"> | ||||
| 						<div><strong id="pageViewsSeven">0</strong></div> | ||||
| 						<div><a href="#" class="updatePageviewsGraph" data-action="updateGraph" data-units="days" data-amount="7">[[admin/dashboard:page-views-seven]]</a></div> | ||||
| 					</div> | ||||
| 					<div class="col-sm-3 hidden-xs text-center pageview-stats"> | ||||
| 						<div><strong id="pageViewsPastDay">0</strong></div> | ||||
| 						<div><a href="#" class="updatePageviewsGraph active" data-action="updateGraph" data-units="hours">[[admin/dashboard:page-views-last-day]]</a></div> | ||||
| 					</div> | ||||
| 					<div class="col-sm-3 text-center pageview-stats"> | ||||
| 						<div><strong><i class="fa fa-clock-o"></i></strong></div> | ||||
| 						<div><a href="#" class="updatePageviewsGraph" data-action="updateGraph" data-units="custom">[[admin/dashboard:page-views-custom]]</a></div> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 		</div> | ||||
|  | ||||
| 		<div class="row"> | ||||
| 			<div class="table-responsive"> | ||||
| 				<table class="table table-striped"> | ||||
| 					<thead> | ||||
| 						<tr> | ||||
| 							<th></th> | ||||
| 							<th class="text-right">[[admin/dashboard:stats.yesterday]]</th> | ||||
| 							<th class="text-right">[[admin/dashboard:stats.today]]</th> | ||||
| 							<th></th> | ||||
| 							<th class="text-right">[[admin/dashboard:stats.last-week]]</th> | ||||
| 							<th class="text-right">[[admin/dashboard:stats.this-week]]</th> | ||||
| 							<th></th> | ||||
| 							<th class="text-right">[[admin/dashboard:stats.last-month]]</th> | ||||
| 							<th class="text-right">[[admin/dashboard:stats.this-month]]</th> | ||||
| 							<th></th> | ||||
| 							<th class="text-right">[[admin/dashboard:stats.all]]</th> | ||||
| 						</tr> | ||||
| 					</thead> | ||||
| 					<tbody> | ||||
| 						<!-- BEGIN stats --> | ||||
| 						<tr> | ||||
| 							<td><strong>{stats.name}</strong></td> | ||||
| 							<td class="text-right formatted-number">{stats.yesterday}</td> | ||||
| 							<td class="text-right formatted-number">{stats.today}</td> | ||||
| 							<td class="{stats.dayTextClass}"><small>{stats.dayIncrease}%</small></td> | ||||
|  | ||||
| 							<td class="text-right formatted-number">{stats.lastweek}</td> | ||||
| 							<td class="text-right formatted-number">{stats.thisweek}</td> | ||||
| 							<td class="{stats.weekTextClass}"><small>{stats.weekIncrease}%</small></td> | ||||
|  | ||||
| 							<td class="text-right formatted-number">{stats.lastmonth}</td> | ||||
| 							<td class="text-right formatted-number">{stats.thismonth}</td> | ||||
| 							<td class="{stats.monthTextClass}"><small>{stats.monthIncrease}%</small></td> | ||||
|  | ||||
| 							<td class="text-right formatted-number">{stats.alltime}</td> | ||||
| 						</tr> | ||||
| 						<!-- END stats --> | ||||
| 					</tbody> | ||||
| 				</table> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 		<!-- IMPORT admin/partials/dashboard/graph.tpl --> | ||||
| 		<!-- IMPORT admin/partials/dashboard/stats.tpl --> | ||||
|  | ||||
| 		<div class="row"> | ||||
| 			<div class="col-lg-4"> | ||||
|   | ||||
							
								
								
									
										6
									
								
								src/views/admin/dashboard/logins.tpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/views/admin/dashboard/logins.tpl
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| <div class="row dashboard"> | ||||
| 	<div class="col-xs-12"> | ||||
| 		<!-- IMPORT admin/partials/dashboard/graph.tpl --> | ||||
| 		<!-- IMPORT admin/partials/dashboard/stats.tpl --> | ||||
| 	</div> | ||||
| </div> | ||||
							
								
								
									
										6
									
								
								src/views/admin/dashboard/topics.tpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/views/admin/dashboard/topics.tpl
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| <div class="row dashboard"> | ||||
| 	<div class="col-xs-12"> | ||||
| 		<!-- IMPORT admin/partials/dashboard/graph.tpl --> | ||||
| 		<!-- IMPORT admin/partials/dashboard/stats.tpl --> | ||||
| 	</div> | ||||
| </div> | ||||
							
								
								
									
										6
									
								
								src/views/admin/dashboard/users.tpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/views/admin/dashboard/users.tpl
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| <div class="row dashboard"> | ||||
| 	<div class="col-xs-12"> | ||||
| 		<!-- IMPORT admin/partials/dashboard/graph.tpl --> | ||||
| 		<!-- IMPORT admin/partials/dashboard/stats.tpl --> | ||||
| 	</div> | ||||
| </div> | ||||
							
								
								
									
										33
									
								
								src/views/admin/partials/dashboard/graph.tpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/views/admin/partials/dashboard/graph.tpl
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| <div class="panel panel-default" id="analytics-panel"> | ||||
| 	<div class="panel-heading"> | ||||
| 		[[admin/dashboard:forum-traffic]] | ||||
| 		<div class="pull-right"> | ||||
| 			<a id="view-as-json" href="{config.relative_path}/api/v3/admin/analytics/{set}?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"> | ||||
| 			<canvas id="analytics-traffic" width="100%" height="400"></canvas> | ||||
| 		</div> | ||||
| 		<hr/> | ||||
| 		<div class="row"> | ||||
| 			<div class="col-sm-3 hidden-xs text-center pageview-stats"> | ||||
| 				<div><strong id="pageViewsThirty">{{{ if summary.month }}}{./summary.month}{{{ else }}}0{{{ end }}}</strong></div> | ||||
| 				<div><a href="#" class="updatePageviewsGraph" data-action="updateGraph" data-units="days" data-amount="30">[[admin/dashboard:page-views-thirty]]</a></div> | ||||
| 			</div> | ||||
| 			<div class="col-sm-3 text-center pageview-stats"> | ||||
| 				<div><strong id="pageViewsSeven">{{{ if summary.week }}}{./summary.week}{{{ else }}}0{{{ end }}}</strong></div> | ||||
| 				<div><a href="#" class="updatePageviewsGraph" data-action="updateGraph" data-units="days" data-amount="7">[[admin/dashboard:page-views-seven]]</a></div> | ||||
| 			</div> | ||||
| 			<div class="col-sm-3 hidden-xs text-center pageview-stats"> | ||||
| 				<div><strong id="pageViewsPastDay">{{{ if summary.day }}}{./summary.day}{{{ else }}}0{{{ end }}}</strong></div> | ||||
| 				<div><a href="#" class="updatePageviewsGraph active" data-action="updateGraph" data-units="hours">[[admin/dashboard:page-views-last-day]]</a></div> | ||||
| 			</div> | ||||
| 			<div class="col-sm-3 text-center pageview-stats"> | ||||
| 				<div><strong><i class="fa fa-clock-o"></i></strong></div> | ||||
| 				<div><a href="#" class="updatePageviewsGraph" data-action="updateGraph" data-units="custom">[[admin/dashboard:page-views-custom]]</a></div> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </div> | ||||
							
								
								
									
										49
									
								
								src/views/admin/partials/dashboard/stats.tpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/views/admin/partials/dashboard/stats.tpl
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| <div class="row"> | ||||
| 	<div class="table-responsive"> | ||||
| 		<table class="table table-striped"> | ||||
| 			<thead> | ||||
| 				<tr> | ||||
| 					<th></th> | ||||
| 					<th class="text-right">[[admin/dashboard:stats.yesterday]]</th> | ||||
| 					<th class="text-right">[[admin/dashboard:stats.today]]</th> | ||||
| 					<th></th> | ||||
| 					<th class="text-right">[[admin/dashboard:stats.last-week]]</th> | ||||
| 					<th class="text-right">[[admin/dashboard:stats.this-week]]</th> | ||||
| 					<th></th> | ||||
| 					<th class="text-right">[[admin/dashboard:stats.last-month]]</th> | ||||
| 					<th class="text-right">[[admin/dashboard:stats.this-month]]</th> | ||||
| 					<th></th> | ||||
| 					<th class="text-right">[[admin/dashboard:stats.all]]</th> | ||||
| 				</tr> | ||||
| 			</thead> | ||||
| 			<tbody> | ||||
| 				<!-- BEGIN stats --> | ||||
| 				<tr> | ||||
| 					<td> | ||||
| 						<strong> | ||||
| 							{{{ if ../href }}} | ||||
| 								<a href="{../href}">{../name}</a> | ||||
| 							{{{ else }}} | ||||
| 								{../name} | ||||
| 							{{{ end }}} | ||||
| 						</strong> | ||||
| 					</td> | ||||
| 					<td class="text-right formatted-number">{stats.yesterday}</td> | ||||
| 					<td class="text-right formatted-number">{stats.today}</td> | ||||
| 					<td class="{stats.dayTextClass}"><small>{stats.dayIncrease}%</small></td> | ||||
|  | ||||
| 					<td class="text-right formatted-number">{stats.lastweek}</td> | ||||
| 					<td class="text-right formatted-number">{stats.thisweek}</td> | ||||
| 					<td class="{stats.weekTextClass}"><small>{stats.weekIncrease}%</small></td> | ||||
|  | ||||
| 					<td class="text-right formatted-number">{stats.lastmonth}</td> | ||||
| 					<td class="text-right formatted-number">{stats.thismonth}</td> | ||||
| 					<td class="{stats.monthTextClass}"><small>{stats.monthIncrease}%</small></td> | ||||
|  | ||||
| 					<td class="text-right formatted-number">{stats.alltime}</td> | ||||
| 				</tr> | ||||
| 				<!-- END stats --> | ||||
| 			</tbody> | ||||
| 		</table> | ||||
| 	</div> | ||||
| </div> | ||||
		Reference in New Issue
	
	Block a user