From 0a632def1ce8da446709d92812423d337c977d75 Mon Sep 17 00:00:00 2001 From: Dan Stroot Date: Wed, 29 Jan 2014 15:41:47 -0800 Subject: [PATCH] :moyai: Added a real-time Dashboard with Socket.io --- .gitignore | 1 + app.js | 78 +++++++++++++++++++++++++++++++--- controllers/dashboard.js | 10 +++++ package.json | 3 +- public/css/styles.less | 21 +++++++++ views/dashboard.jade | 49 +++++++++++++++++++++ views/layout.jade | 10 +++++ views/partials/navigation.jade | 2 + 8 files changed, 167 insertions(+), 7 deletions(-) create mode 100644 controllers/dashboard.js create mode 100644 views/dashboard.jade diff --git a/.gitignore b/.gitignore index 0f0478e67d..5344e83603 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ lib-cov *.pid *.gz *.swp +*.css pids logs diff --git a/app.js b/app.js index b9c8f8d238..8cc011ab74 100755 --- a/app.js +++ b/app.js @@ -10,7 +10,11 @@ var path = require('path'); var mongoose = require('mongoose'); var passport = require('passport'); var expressValidator = require('express-validator'); - +var http = require('http'); +var io = require('socket.io'); +var app = express() + , server = require('http').createServer(app) + , io = io.listen(server); /** * Load controllers. @@ -20,6 +24,7 @@ var homeController = require('./controllers/home'); var userController = require('./controllers/user'); var apiController = require('./controllers/api'); var contactController = require('./controllers/contact'); +var dashboardController = require('./controllers/dashboard'); /** * API keys + Passport configuration. @@ -37,11 +42,15 @@ mongoose.connection.on('error', function() { console.log('✗ MongoDB Connection Error. Please make sure MongoDB is running.'.red); }); -var app = express(); - /** * Express configuration. */ + +var hour = 3600000; //milliseconds +var day = (hour * 24); +var week = (day * 7); +var month = (day * 30); + app.locals.cacheBuster = Date.now(); app.set('port', process.env.PORT || 3000); app.set('views', path.join(__dirname, 'views')); @@ -69,17 +78,26 @@ app.use(function(req, res, next) { app.use(flash()); app.use(less({ src: __dirname + '/public', compress: true })); app.use(app.router); -app.use(express.static( path.join(__dirname, 'public'), { maxAge: 864000000 } )); +app.use(express.static( path.join(__dirname, 'public'), { maxAge: week } )); app.use(function(req, res) { res.render('404', { status: 404 }); }); app.use(express.errorHandler()); +/** + * Start Server + */ + +server.listen(app.get('port'), function(){ + console.log("✔ Express server listening on port %d in %s mode", app.get('port'), app.settings.env); +}); + /** * Application routes. */ app.get('/', homeController.index); +app.get('/dashboard', dashboardController.getDashboard); app.get('/login', userController.getLogin); app.post('/login', userController.postLogin); app.get('/logout', userController.logout); @@ -118,6 +136,54 @@ app.get('/auth/foursquare/callback', passport.authorize('foursquare', { failureR app.get('/auth/tumblr', passport.authorize('tumblr')); app.get('/auth/tumblr/callback', passport.authorize('tumblr', { failureRedirect: '/api' }), function(req, res) { res.redirect('/api/tumblr'); }); -app.listen(app.get('port'), function() { - console.log('✔ Express server listening on port ' + app.get('port')); +/** + * Emit Pageviews on Socket.io + */ + +io.configure('production', function(){ + io.enable('browser client minification'); // send minified client + io.enable('browser client etag'); // apply etag caching logic based on version number + io.enable('browser client gzip'); // gzip the file + io.set('log level', 1); // reduce logging + io.set("polling duration", 10); // increase polling frequency + io.set('transports', [ // Manage transports + 'websocket' + , 'htmlfile' + , 'xhr-polling' + , 'jsonp-polling' + ]); + io.set('authorization', function (handshakeData, callback) { + if (handshakeData.xdomain) { + callback('Cross-domain connections are not allowed'); + } else { + callback(null, true); + } + }); }); + +io.configure('development', function(){ + io.set('log level', 1); // reduce logging + io.set('transports', [ + 'websocket' // Let's just use websockets for development + ]); + io.set('authorization', function (handshakeData, callback) { + if (handshakeData.xdomain) { + callback('Cross-domain connections are not allowed'); + } else { + callback(null, true); + } + }); +}); + +io.sockets.on('connection', function (socket) { + socket.on('message', function (message) { + console.log("Got message: " + message); + var ip = socket.handshake.address.address; + var url = message; + io.sockets.emit('pageview', { 'connections': Object.keys(io.connected).length, 'ip': ip, 'url': url, 'xdomain': socket.handshake.xdomain, 'timestamp': new Date()}); + }); + socket.on('disconnect', function () { + console.log("Socket disconnected"); + io.sockets.emit('pageview', { 'connections': Object.keys(io.connected).length}); + }); +}); \ No newline at end of file diff --git a/controllers/dashboard.js b/controllers/dashboard.js new file mode 100644 index 0000000000..02110e459b --- /dev/null +++ b/controllers/dashboard.js @@ -0,0 +1,10 @@ +/** + * GET / + * Home page. + */ + +exports.getDashboard = function(req, res) { + res.render('dashboard', { + title: 'Dashboard' + }); +}; diff --git a/package.json b/package.json index 3d71ab59ed..c2700188f1 100755 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "twit": "~1.1.12", "underscore": "~1.5.2", "paypal-rest-sdk": "~0.6.4", - "connect-mongo": "~0.4.0" + "connect-mongo": "~0.4.0", + "socket.io": "0.9.16" } } diff --git a/public/css/styles.less b/public/css/styles.less index 16a133b667..9c62615c10 100644 --- a/public/css/styles.less +++ b/public/css/styles.less @@ -30,6 +30,27 @@ body { border-top: 1px solid @navbar-default-border; } +// Dashboard +// ------------------------- + +#connections { + text-align: center; + p { + font-size: 96px; + line-height: 96px; + color: #ff6600; + text-shadow: 1px 1px 1px #222; + } +} + +#visits thead tr td { + font-weight: bold; +} + +#pageViews thead tr td { + font-weight: bold; +} + // Navbar // ------------------------- diff --git a/views/dashboard.jade b/views/dashboard.jade new file mode 100644 index 0000000000..0607e32743 --- /dev/null +++ b/views/dashboard.jade @@ -0,0 +1,49 @@ +extends layout + +block content + + .row + .well.col-md-2#connections + h3 Right Now + p 0 + h5 active visitors + .col-md-10 + legend Real Time Activity + table#visits.table.table-bordered.table-striped.table-condensed + thead + tr + td URL + td IP + td Timestamp + tbody + legend Page Views + table#pageViews.table.table-bordered.table-striped.table-condensed + thead + tr + td URL + td Page Views + tbody + + script. + var pages = {}; + var lastPageId = 0; + socket.on('connect', function () { + console.log('Socket connected'); + socket.on('pageview', function (msg) { + console.log('Connections: ' + msg.connections); + $('#connections > p').html(msg.connections - 1); // -1 since we don't count our own dashboard connection + if (msg.url) { + if ($('#visits tr').length > 10) { + $('#visits tr:last').remove(); + } + $('#visits tbody').prepend('' + msg.url + '' + msg.ip + '' + msg.timestamp + ''); + if (pages[msg.url]) { + pages[msg.url].views = pages[msg.url].views + 1; + $('#page' + pages[msg.url].pageId).html(pages[msg.url].views); + } else { + pages[msg.url] = {views: 1, pageId: ++lastPageId}; + $('#pageViews tbody').append('' + msg.url + '1'); + } + } + }); + }); \ No newline at end of file diff --git a/views/layout.jade b/views/layout.jade index 348eedf2f9..aeb141ce86 100644 --- a/views/layout.jade +++ b/views/layout.jade @@ -17,6 +17,16 @@ html script(src='/js/lib/jquery.js?v=#{cacheBuster}') script(src='/js/lib/bootstrap.js?v=#{cacheBuster}') script(src='/js/main.js?v=#{cacheBuster}') + script(src='/socket.io/socket.io.js?v=#{cacheBuster}') + //- For real-time monitoring + script. + var socket = io.connect(); + socket.on('connect', function () { + socket.send(window.location.href); + }); + window.onhashchange = function () { + socket.send(window.location.href); + } body #wrap include partials/navigation diff --git a/views/partials/navigation.jade b/views/partials/navigation.jade index de614c440d..475cf1ad9a 100644 --- a/views/partials/navigation.jade +++ b/views/partials/navigation.jade @@ -15,6 +15,8 @@ a(href='/api') API Browser li(class=title=='Contact'?'active':undefined) a(href='/contact') Contact + li(class=title=='Dashboard'?'active':undefined) + a(href='/dashboard') Dashboard ul.nav.navbar-nav.navbar-right if !user li(class=title=='Login'?'active':undefined)