diff --git a/static/js/pandora/menu.js b/static/js/pandora/menu.js index 867c17d0..ed76cfc2 100644 --- a/static/js/pandora/menu.js +++ b/static/js/pandora/menu.js @@ -138,11 +138,8 @@ pandora.ui.mainMenu = function() { { id: 'places', title: 'Manage Places...', disabled: isGuest }, { id: 'events', title: 'Manage Events...', disabled: isGuest }, {}, - { id: 'users', title: 'Manage Users...', disabled: !isAdmin }/*, - { id: 'lists', title: 'Manage Lists...', disabled: !isAdmin }, - {}, - { id: 'news', title: 'Manage News...', disabled: !isAdmin }, - { id: 'tour', title: 'Manage Tour...', disabled: !isAdmin }*/ + { id: 'users', title: 'Manage Users...', disabled: !isAdmin }, + { id: 'statistics', title: 'Statistics...', disabled: !isAdmin } ] }, { id: 'helpMenu', title: 'Help', items: [ { id: 'help', title: pandora.site.site.name + ' Help', keyboard: 'control ?' } @@ -311,6 +308,8 @@ pandora.ui.mainMenu = function() { )).open(); } else if (data.id == 'users') { pandora.$ui.usersDialog = pandora.ui.usersDialog().open(); + } else if (data.id == 'statistics') { + pandora.$ui.statisticsDialog = pandora.ui.statisticsDialog().open(); } else if (data.id == 'resetfilters') { pandora.UI.set({ filters: pandora.site.user.ui.filters diff --git a/static/js/pandora/statisticsDialog.js b/static/js/pandora/statisticsDialog.js new file mode 100644 index 00000000..39084c26 --- /dev/null +++ b/static/js/pandora/statisticsDialog.js @@ -0,0 +1,447 @@ +// vim: et:ts=4:sw=4:sts=4:ft=javascript + +'use strict'; + +pandora.ui.statisticsDialog = function() { + + var colors = { + firstseen: [64, 192, 64], + lastseen: [64, 64, 192], + continent: [64, 192, 64], + region: [64, 192, 192], + country: [64, 64, 192], + city: [192, 64, 64], + system: [64, 192, 64], + browser: [64, 64, 192], + systemandbrowser: [64, 192, 192] + }, + dialogHeight = Math.round((window.innerHeight - 48) * 0.9), + dialogWidth = Math.round(window.innerWidth * 0.9), + names = { + system: [ + 'Android', 'iOS', 'Linux', 'Mac OS X', 'Windows' + ], + browser: [ + 'Chrome Frame', 'Chrome', 'Firefox', + 'Internet Explorer', 'Opera', 'Safari' + ] + }, + tabs = [ + {id: 'seen', title: 'First Seen and Last Seen', selected: true}, + {id: 'locations', title: 'Locations'}, + {id: 'systems', title: 'Operating Systems and Browsers'} + ], + + $dialog = Ox.Dialog({ + buttons: [ + Ox.Button({ + id: 'close', + title: 'Close' + }).bindEvent({ + click: function() { + $dialog.close(); + } + }) + ], + closeButton: true, + content: Ox.Element(), + height: dialogHeight, + minHeight: 256, + minWidth: 512, + removeOnClose: true, + title: 'Statistics', + width: dialogWidth + }); + + //Ox.getJSON('/static/json/deleteme.json', function(result) { + pandora.api.findUsers({ + keys: ['browser', 'firstseen', 'lastseen', 'location', 'system'], + range: [0, 1000000], + sort: [{key: 'username', operator: '+'}] + }, function(result) { + + var chartWidth = dialogWidth - 32 - Ox.UI.SCROLLBAR_SIZE, + data = {}, + flagCountry = {}, + $guestsCheckbox, + $tabPanel; + + ['all', 'registered'].forEach(function(mode) { + + data[mode] = { + year: {}, + month: {}, + continent: {}, + region: {}, + country: {}, + city: {}, + system: {}, + browser: {}, + systemandbrowser: {}, + systemversion: {}, + browserversion: {}, + systemandbrowserversion: {} + }; + + result.data.items.forEach(function(item) { + var city, continent, country, name = {}, region, split; + if (mode == 'all' || item.level != 'guest') { + ['firstseen', 'lastseen'].forEach(function(key, i) { + var year = item[key].substr(0, 4) + '-' + key, + month = item[key].substr(0, 7) + '-' + key; + data[mode].year[year] = (data[mode].year[year] || 0) + 1; + data[mode].month[month] = (data[mode].month[month] || 0) + 1; + }); + if (item.location) { + split = item.location.split(', ') + if (split.length == 1) { + country = item.location; + } else { + country = split[1]; + city = [split[1], split[0]].join(', '); + data[mode].city[city] = (data[mode].city[city] || 0) + 1; + } + data[mode].country[country] = (data[mode].country[country] || 0) + 1; + region = Ox.getCountryByName(country).region; + data[mode].region[region] = (data[mode].region[region] || 0) + 1; + continent = Ox.getCountryByName(country).continent; + data[mode].continent[continent] = (data[mode].continent[continent] || 0) + 1; + } + ['system', 'browser'].forEach(function(key) { + var version = item[key]; + if (version) { + name[key] = ''; + Ox.forEach(names[key], function(v) { + if (new RegExp('^' + v).test(version)) { + name[key] = v; + return false; + } + }); + if (name[key]) { + data[mode][key][name[key]] = (data[mode][key][name[key]] || 0) + 1; + key = key + 'version'; + data[mode][key][version] = (data[mode][key][version] || 0) + 1; + }/* else { + Ox.print("^^^^^^", version) + }*/ + } + }); + if (name.system && name.browser) { + name = name.system + ' / ' + name.browser; + data[mode].systemandbrowser[name] = (data[mode].systemandbrowser[name] || 0) + 1; + } + } + }); + + var keys, firstKey, lastKey; + keys = Object.keys(data[mode].month).map(function(key) { + return key.substr(0, 7) + }).sort(); + firstKey = keys[0].split('-').map(function(str) { + return parseInt(str, 10); + }); + lastKey = keys[keys.length - 1].split('-').map(function(str) { + return parseInt(str, 10); + }); + Ox.loop(firstKey[0], lastKey[0] + 1, function(year) { + ['firstseen', 'lastseen'].forEach(function(key) { + var key = [year, key].join('-'); + data[mode].year[key] = data[mode].year[key] || 0; + }); + Ox.loop( + year == firstKey[0] ? firstKey[1] : 1, + year == lastKey[0] ? lastKey[1] + 1 : 13, + function(month) { + ['firstseen', 'lastseen'].forEach(function(key) { + var key = [year, Ox.pad(month, 2), key].join('-'); + data[mode].month[key] = data[mode].month[key] || 0; + }); + } + ); + }); + + flagCountry[mode] = {}; + ['continent', 'region'].forEach(function(key) { + flagCountry[mode][key] = {}; + Ox.forEach(data[mode][key], function(regionValue, regionKey) { + var max = 0; + Ox.forEach(data[mode].country, function(countryValue, countryKey) { + if ( + Ox.getCountryByGeoname(countryKey)[key] == regionKey + && countryValue > max + ) { + flagCountry[mode][key][regionKey] = countryKey; + max = countryValue; + } + }); + }); + }); + + }); + + data.all.city['Other, Other'] = 0; + Ox.forEach(data.all.city, function(value, key) { + if (value < 2) { + data.all.city['Other, Other']++; + delete data.all.city[key]; + } + }); + + $guestsCheckbox = Ox.Checkbox({ + title: 'Include Guests', + value: false + }) + .css({float: 'left', margin: '4px'}) + .bindEvent({ + change: function() { + $tabPanel.$element.replaceElement(1, + $tabPanel.options('content')($tabPanel.selected()) + ); + } + }); + + $tabPanel = Ox.TabPanel({ + content: function(id) { + var mode = $guestsCheckbox.options('value') ? 'all' : 'registered', + top = 16, + $content = Ox.Element() + .css({ + padding: '16px', + overflowY: 'auto', + background: Ox.Theme() == 'classic' + ? 'rgb(240, 240, 240)' + : 'rgb(16, 16, 16)' + }); + if (id == 'seen') { + ['year', 'month'].forEach(function(key) { + Ox.Chart({ + color: function(key) { + return colors[key.split('-').pop()]; + }, + data: data[mode][key], + formatKey: function(value) { + var split = value.split('-'); + return (split.pop() == 'firstseen' ? 'First' : 'Last') + ': ' + + (key == 'year' ? '' : Ox.MONTHS[parseInt(split[1], 10) - 1]) + + ' ' + split[0]; + }, + keyAlign: 'right', + keyWidth: 128, + rows: 2, + sort: {key: 'key', operator: '-'}, + title: key == 'year' ? 'Years' : 'Months', + width: chartWidth + }) + .css({ + position: 'absolute', + left: '16px', + top: top + 'px' + }) + .appendTo($content); + top += Ox.len(data[mode][key]) * 16 + 32; + }); + } else if (id == 'locations') { + ['continent', 'region', 'country', 'city'].forEach(function(key) { + Ox.Chart({ + color: colors[key], + data: data[mode][key], + formatKey: function(value) { + var city, country, split; + if (key == 'city' || key == 'country') { + split = value.split(', '); + city = split.length == 2 ? split[1] : '' + country = split[0]; + } else { + country = flagCountry[mode][key][value]; + } + return $('