Skip to content

Commit 0a632de

Browse files
committed
🗿 Added a real-time Dashboard with Socket.io
1 parent ec4f29b commit 0a632de

File tree

8 files changed

+167
-7
lines changed

8 files changed

+167
-7
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ lib-cov
77
*.pid
88
*.gz
99
*.swp
10+
*.css
1011

1112
pids
1213
logs

app.js

+72-6
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ var path = require('path');
1010
var mongoose = require('mongoose');
1111
var passport = require('passport');
1212
var expressValidator = require('express-validator');
13-
13+
var http = require('http');
14+
var io = require('socket.io');
15+
var app = express()
16+
, server = require('http').createServer(app)
17+
, io = io.listen(server);
1418

1519
/**
1620
* Load controllers.
@@ -20,6 +24,7 @@ var homeController = require('./controllers/home');
2024
var userController = require('./controllers/user');
2125
var apiController = require('./controllers/api');
2226
var contactController = require('./controllers/contact');
27+
var dashboardController = require('./controllers/dashboard');
2328

2429
/**
2530
* API keys + Passport configuration.
@@ -37,11 +42,15 @@ mongoose.connection.on('error', function() {
3742
console.log('✗ MongoDB Connection Error. Please make sure MongoDB is running.'.red);
3843
});
3944

40-
var app = express();
41-
4245
/**
4346
* Express configuration.
4447
*/
48+
49+
var hour = 3600000; //milliseconds
50+
var day = (hour * 24);
51+
var week = (day * 7);
52+
var month = (day * 30);
53+
4554
app.locals.cacheBuster = Date.now();
4655
app.set('port', process.env.PORT || 3000);
4756
app.set('views', path.join(__dirname, 'views'));
@@ -69,17 +78,26 @@ app.use(function(req, res, next) {
6978
app.use(flash());
7079
app.use(less({ src: __dirname + '/public', compress: true }));
7180
app.use(app.router);
72-
app.use(express.static( path.join(__dirname, 'public'), { maxAge: 864000000 } ));
81+
app.use(express.static( path.join(__dirname, 'public'), { maxAge: week } ));
7382
app.use(function(req, res) {
7483
res.render('404', { status: 404 });
7584
});
7685
app.use(express.errorHandler());
7786

87+
/**
88+
* Start Server
89+
*/
90+
91+
server.listen(app.get('port'), function(){
92+
console.log("✔ Express server listening on port %d in %s mode", app.get('port'), app.settings.env);
93+
});
94+
7895
/**
7996
* Application routes.
8097
*/
8198

8299
app.get('/', homeController.index);
100+
app.get('/dashboard', dashboardController.getDashboard);
83101
app.get('/login', userController.getLogin);
84102
app.post('/login', userController.postLogin);
85103
app.get('/logout', userController.logout);
@@ -118,6 +136,54 @@ app.get('/auth/foursquare/callback', passport.authorize('foursquare', { failureR
118136
app.get('/auth/tumblr', passport.authorize('tumblr'));
119137
app.get('/auth/tumblr/callback', passport.authorize('tumblr', { failureRedirect: '/api' }), function(req, res) { res.redirect('/api/tumblr'); });
120138

121-
app.listen(app.get('port'), function() {
122-
console.log('✔ Express server listening on port ' + app.get('port'));
139+
/**
140+
* Emit Pageviews on Socket.io
141+
*/
142+
143+
io.configure('production', function(){
144+
io.enable('browser client minification'); // send minified client
145+
io.enable('browser client etag'); // apply etag caching logic based on version number
146+
io.enable('browser client gzip'); // gzip the file
147+
io.set('log level', 1); // reduce logging
148+
io.set("polling duration", 10); // increase polling frequency
149+
io.set('transports', [ // Manage transports
150+
'websocket'
151+
, 'htmlfile'
152+
, 'xhr-polling'
153+
, 'jsonp-polling'
154+
]);
155+
io.set('authorization', function (handshakeData, callback) {
156+
if (handshakeData.xdomain) {
157+
callback('Cross-domain connections are not allowed');
158+
} else {
159+
callback(null, true);
160+
}
161+
});
123162
});
163+
164+
io.configure('development', function(){
165+
io.set('log level', 1); // reduce logging
166+
io.set('transports', [
167+
'websocket' // Let's just use websockets for development
168+
]);
169+
io.set('authorization', function (handshakeData, callback) {
170+
if (handshakeData.xdomain) {
171+
callback('Cross-domain connections are not allowed');
172+
} else {
173+
callback(null, true);
174+
}
175+
});
176+
});
177+
178+
io.sockets.on('connection', function (socket) {
179+
socket.on('message', function (message) {
180+
console.log("Got message: " + message);
181+
var ip = socket.handshake.address.address;
182+
var url = message;
183+
io.sockets.emit('pageview', { 'connections': Object.keys(io.connected).length, 'ip': ip, 'url': url, 'xdomain': socket.handshake.xdomain, 'timestamp': new Date()});
184+
});
185+
socket.on('disconnect', function () {
186+
console.log("Socket disconnected");
187+
io.sockets.emit('pageview', { 'connections': Object.keys(io.connected).length});
188+
});
189+
});

controllers/dashboard.js

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/**
2+
* GET /
3+
* Home page.
4+
*/
5+
6+
exports.getDashboard = function(req, res) {
7+
res.render('dashboard', {
8+
title: 'Dashboard'
9+
});
10+
};

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"twit": "~1.1.12",
2929
"underscore": "~1.5.2",
3030
"paypal-rest-sdk": "~0.6.4",
31-
"connect-mongo": "~0.4.0"
31+
"connect-mongo": "~0.4.0",
32+
"socket.io": "0.9.16"
3233
}
3334
}

public/css/styles.less

+21
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,27 @@ body {
3030
border-top: 1px solid @navbar-default-border;
3131
}
3232

33+
// Dashboard
34+
// -------------------------
35+
36+
#connections {
37+
text-align: center;
38+
p {
39+
font-size: 96px;
40+
line-height: 96px;
41+
color: #ff6600;
42+
text-shadow: 1px 1px 1px #222;
43+
}
44+
}
45+
46+
#visits thead tr td {
47+
font-weight: bold;
48+
}
49+
50+
#pageViews thead tr td {
51+
font-weight: bold;
52+
}
53+
3354
// Navbar
3455
// -------------------------
3556

views/dashboard.jade

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
extends layout
2+
3+
block content
4+
5+
.row
6+
.well.col-md-2#connections
7+
h3 Right Now
8+
p 0
9+
h5 active visitors
10+
.col-md-10
11+
legend Real Time Activity
12+
table#visits.table.table-bordered.table-striped.table-condensed
13+
thead
14+
tr
15+
td URL
16+
td IP
17+
td Timestamp
18+
tbody
19+
legend Page Views
20+
table#pageViews.table.table-bordered.table-striped.table-condensed
21+
thead
22+
tr
23+
td URL
24+
td Page Views
25+
tbody
26+
27+
script.
28+
var pages = {};
29+
var lastPageId = 0;
30+
socket.on('connect', function () {
31+
console.log('Socket connected');
32+
socket.on('pageview', function (msg) {
33+
console.log('Connections: ' + msg.connections);
34+
$('#connections > p').html(msg.connections - 1); // -1 since we don't count our own dashboard connection
35+
if (msg.url) {
36+
if ($('#visits tr').length > 10) {
37+
$('#visits tr:last').remove();
38+
}
39+
$('#visits tbody').prepend('<tr><td>' + msg.url + '</td><td>' + msg.ip + '</td><td>' + msg.timestamp + '</td></tr>');
40+
if (pages[msg.url]) {
41+
pages[msg.url].views = pages[msg.url].views + 1;
42+
$('#page' + pages[msg.url].pageId).html(pages[msg.url].views);
43+
} else {
44+
pages[msg.url] = {views: 1, pageId: ++lastPageId};
45+
$('#pageViews tbody').append('<tr><td>' + msg.url + '</td><td id="page' + lastPageId + '">1</td></tr>');
46+
}
47+
}
48+
});
49+
});

views/layout.jade

+10
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ html
1717
script(src='/js/lib/jquery.js?v=#{cacheBuster}')
1818
script(src='/js/lib/bootstrap.js?v=#{cacheBuster}')
1919
script(src='/js/main.js?v=#{cacheBuster}')
20+
script(src='/socket.io/socket.io.js?v=#{cacheBuster}')
21+
//- For real-time monitoring
22+
script.
23+
var socket = io.connect();
24+
socket.on('connect', function () {
25+
socket.send(window.location.href);
26+
});
27+
window.onhashchange = function () {
28+
socket.send(window.location.href);
29+
}
2030
body
2131
#wrap
2232
include partials/navigation

views/partials/navigation.jade

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
a(href='/api') API Browser
1616
li(class=title=='Contact'?'active':undefined)
1717
a(href='/contact') Contact
18+
li(class=title=='Dashboard'?'active':undefined)
19+
a(href='/dashboard') Dashboard
1820
ul.nav.navbar-nav.navbar-right
1921
if !user
2022
li(class=title=='Login'?'active':undefined)

0 commit comments

Comments
 (0)