├── .bowerrc ├── .gitignore ├── LICENSE ├── README.md ├── bower.json ├── index.js ├── package.json └── public ├── capture.html ├── capture2.html ├── dashboard ├── css │ └── style.css ├── index.html └── js │ └── dashboard.js └── index.html /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "public/bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Deployed apps should consider commenting this line out: 24 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 25 | node_modules 26 | public/bower_components 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Phil Leggetter 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SocketAlytics 2 | 3 | The code for a Real-Time Analytics dashboard powered by Socket.IO. 4 | 5 | ## Tutorial 6 | 7 | [Build a real-time app with Socket.IO](http://www.creativebloq.com/web-design/build-real-time-app-socketio-11514083). 8 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "socketalytics", 3 | "main": "index.js", 4 | "version": "0.0.1", 5 | "homepage": "https://github.com/leggetter/socketalytics", 6 | "authors": [ 7 | "leggetter " 8 | ], 9 | "keywords": [ 10 | "analytics", 11 | "socket.io" 12 | ], 13 | "license": "MIT", 14 | "private": true, 15 | "ignore": [ 16 | "**/.*", 17 | "node_modules", 18 | "bower_components", 19 | "public/bower_components", 20 | "test", 21 | "tests" 22 | ], 23 | "dependencies": { 24 | "jquery": "~2.1.1", 25 | "bootstrap": "~3.1.1", 26 | "epoch": "~0.5.2", 27 | "d3": "~3.4.9", 28 | "socket.io-client": "~1.0.6", 29 | "modernizer": "~2.8.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var express = require( 'express' ); 2 | var app = express(); 3 | app.use( express.static( __dirname + '/public') ); 4 | 5 | var server = require( 'http' ).Server( app ); 6 | var io = require( 'socket.io' )( server ); 7 | 8 | // Store data 9 | var stats = { 10 | connections: 0, 11 | touch: 0, 12 | video: 0, 13 | pages: {} 14 | }; 15 | 16 | // Map of Socket.id to Socket object 17 | var socketData = {}; 18 | 19 | // Namespace use when capturing data 20 | var capture = io.of( '/capture' ); 21 | 22 | capture.on( 'connection', function( socket ) { 23 | ++stats.connections; 24 | 25 | socket.on( 'client-data', function( data ) { 26 | socketData[ socket.id ] = data; 27 | stats.touch += ( data.touch? 1 : 0 ); 28 | stats.video += ( data.video? 1 : 0 ); 29 | 30 | var pageCount = stats.pages[ data.url ] || 0; 31 | stats.pages[ data.url ] = ++pageCount; 32 | 33 | console.log( stats ); 34 | dashboard.emit( 'stats-updated', stats ); 35 | } ); 36 | 37 | socket.on( 'disconnect', function() { 38 | // Clear down stats for lost socket 39 | --stats.connections; 40 | 41 | stats.touch -= ( socketData[ socket.id ].touch? 1 : 0 ); 42 | stats.video -= ( socketData[ socket.id ].video? 1 : 0 ); 43 | --stats.pages[ socketData[ socket.id ].url ]; 44 | delete socketData[ socket.id ]; 45 | 46 | console.log( stats ); 47 | dashboard.emit( 'stats-updated', stats ); 48 | } ); 49 | 50 | } ); 51 | 52 | var dashboard = io.of( '/dashboard' ); 53 | dashboard.on( 'connection', function( socket ) { 54 | // Send an update to the newly connected dashboard socket 55 | socket.emit( 'stats-updated', stats ); 56 | } ); 57 | 58 | server.listen( 3000, function(){ 59 | console.log( 'listening on *:3000' ); 60 | } ); 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "socketalytics", 3 | "version": "0.0.0", 4 | "description": "Example realtime analytics using Socket.IO", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/leggetter/socketalytics" 12 | }, 13 | "keywords": [ 14 | "socket.io", 15 | "analytics" 16 | ], 17 | "author": "Phil Leggetter (http://www.leggetter.co.uk)", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/leggetter/socketalytics/issues" 21 | }, 22 | "homepage": "https://github.com/leggetter/socketalytics", 23 | "dependencies": { 24 | "socket.io": "~1.0.4", 25 | "express": "~4.4.3" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /public/capture.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

Capture

5 | 6 | 7 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /public/capture2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

Capture

5 | 6 | 7 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /public/dashboard/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font: 400 0.95em/1 "Proxima Nova", Helvetica,sans-serif; 3 | font-size: .875em; 4 | background-color: #f0f0f0; 5 | 6 | padding-top: 90px; 7 | } 8 | 9 | .widget { 10 | -webkit-box-shadow: #f0f0f0 0 0 8px; 11 | -moz-box-shadow: #f0f0f0 0 0 8px; 12 | box-shadow: #f0f0f0 0 0 8px; 13 | background-color: #f0f0f0; 14 | 15 | margin-bottom: 30px; 16 | } 17 | 18 | .widget h1 { 19 | font-size: 1.0em; 20 | margin: 0 0 .4em; 21 | font-weight: bold; 22 | } 23 | 24 | .widget .widget-inner>header, .widget .widget-inner>footer { 25 | font-size: 12px; 26 | text-shadow: 1px 1px #0e0e0e; 27 | } 28 | 29 | .widget .widget-inner>header { 30 | background-color: #272727; 31 | text-transform: uppercase; 32 | padding: 16px 12px 16px 26px; 33 | font-weight: 700; 34 | } 35 | 36 | .widget .widget-inner { 37 | border: solid 1px #e5e5e5; 38 | background-color: #fff; 39 | } 40 | 41 | .widget .widget-inner>header { 42 | background-color: #f5f5f5; 43 | } 44 | 45 | .widget .widget-inner>header h1 { 46 | color: #8b8b8b; 47 | text-shadow: 1px 1px #fff; 48 | margin-bottom: 0; 49 | } 50 | 51 | .widget .widget-body { 52 | color: #666; 53 | 54 | height: 225px 55 | } 56 | 57 | .widget .widget-body { 58 | padding: 16px; 59 | color: #d3d4d4; 60 | font-family: Helvetica, Arial, sans-serif; 61 | z-index: 1; 62 | } 63 | 64 | .widget .widget-inner>footer { 65 | color: #8b8b8b; 66 | background-color: #f5f5f5; 67 | text-shadow: 1px 1px #fff; 68 | } 69 | 70 | .dash-unit { 71 | margin-bottom: 30px; 72 | padding-bottom: 10px; 73 | border: 1px solid #e5e5e5; 74 | /*background-image: url('../img/sep-half.png');*/ 75 | background-color: #f5f5f5; 76 | color: #8b8b8b; 77 | height: 290px; 78 | text-align: center; 79 | } 80 | 81 | .dash-unit dtitle { 82 | font-size: 11px; 83 | text-transform: uppercase; 84 | margin: 8px; 85 | padding: 0px; 86 | height: inherit; 87 | } 88 | 89 | .dash-unit hr { 90 | border: 0; 91 | border-top: 1px solid #151515; 92 | border-top-style: dashed; 93 | margin-top: 3px; 94 | } 95 | -------------------------------------------------------------------------------- /public/dashboard/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SocketAlytics 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 36 | 37 |
38 | 39 |
40 | 41 |
42 |
43 |
44 | 45 |
46 |

Visitors (30D)

47 |
48 | 49 |
50 |
51 |
52 | 53 |
54 | 55 |
56 |
57 | 58 |
59 | 60 |
61 | 62 |
63 |
64 |
65 | 66 |
67 |

Popular Pages

68 |
69 | 70 |
71 |
72 |
73 | 74 |
75 | 76 |
77 |
78 | 79 | 80 |
81 |
82 |
83 | 84 |
85 |

Touch Support

86 |
87 | 88 |
89 |
90 |
91 | 92 |
93 | 94 |
95 |
96 | 97 |
98 |
99 |
100 | 101 |
102 |

Video Support

103 |
104 | 105 |
106 |
107 |
108 | 109 |
110 | 111 |
112 |
113 | 114 |
115 | 116 |
117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /public/dashboard/js/dashboard.js: -------------------------------------------------------------------------------- 1 | $( function() { 2 | localStorage.debug = 'socket.io-client:socket'; 3 | 4 | var visitors = $('#visitors').epoch( { 5 | type: 'time.area', axes: ['left', 'bottom', 'right'], 6 | data: [ { values: [ { time: Date.now()/1000, y: 0 } ] } ] 7 | } ); 8 | var pages = $( '#pages' ).epoch( { type: 'bar' } ); 9 | var touch = $( '#touch' ).epoch( { type: 'time.gauge' } ); 10 | var video = $( '#video' ).epoch( { type: 'time.gauge' } ); 11 | 12 | var dashboard = io( 'localhost:3000/dashboard' ); 13 | dashboard.on( 'stats-updated', function( update ) { 14 | 15 | // Convert to percentages 16 | touch.update( ( update.touch / update.connections ) || 0 ); 17 | video.update( ( update.video / update.connections ) || 0 ); 18 | 19 | var pagesData = []; 20 | for( var url in update.pages ) { 21 | pagesData.push( { x: url, y: update.pages[ url ] } ); 22 | } 23 | pages.update( [ { values: pagesData } ] ); 24 | 25 | visitors.push( [ { time: Date.now()/1000, y: update.connections } ] ); 26 | 27 | } ); 28 | 29 | } ); 30 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SocketAlytics 5 | 6 | 7 |

SocketAlytics

8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 56 | 57 | 58 | --------------------------------------------------------------------------------