├── .gitignore ├── README.md ├── app.py ├── dbsetup.py ├── static ├── css │ ├── sb-admin.css │ └── sb-admin.min.css └── js │ ├── app.js │ └── sb-admin.js └── templates ├── about.html ├── dashboard-single.html ├── dashboard.html └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | /.venv 2 | /__pycache__ 3 | /pythonsqlite.db 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Build a realtime traffic monitor using Python and Pusher 2 | 3 | This is a demo application showing how to build a realtime website traffic monitor using [Python](https://www.python.org/) and [Pusher](https://pusher.com/). You can read about how it was created on [Pusher's blog](https://pusher.com/tutorials/web-traffic-monitor-python). 4 | 5 | 6 | ## Prerequisites 7 | 8 | What things you need to install the software. 9 | 10 | * Git. 11 | * Python. 12 | * Pip. 13 | 14 | ## Install 15 | 16 | Clone the git repository on your computer 17 | 18 | ``` 19 | $ git clone https://github.com/neoighodaro/python-pusher-traffic-monitor 20 | ``` 21 | 22 | You can also download the entire repository as a zip file and unpack in on your computer if you do not have git. 23 | 24 | After cloning the application, you need to install it's dependencies. 25 | 26 | ``` 27 | $ cd path/to/project 28 | $ python3 -m venv .venv # activate virtual environment (you can use the other command too) 29 | $ source .venv/bin/activate # windows has their own method 30 | $ python dbsetup.py 31 | $ pip install flask 32 | $ pip install httpagentparser 33 | $ pip install pusher 34 | $ export FLASK_ENVIRONMENT=development # not necessary 35 | $ flask run 36 | ``` 37 | 38 | You should see the application in action. 39 | 40 | ## Built With 41 | 42 | * [Pusher](https://pusher.com/) - Hosted APIs to build realtime apps with less code 43 | * [Python](https://www.python.org/) - a programming language that lets you work quickly and integrate systems more effectively 44 | * [Flask](http://flask.pocoo.org/) - a microframework for Python based on Werkzeug, Jinja 2 and good intentions 45 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | 2 | from flask import Flask, render_template, request, session, jsonify 3 | import urllib.request 4 | from pusher import Pusher 5 | from datetime import datetime 6 | import httpagentparser 7 | import json 8 | import os 9 | import hashlib 10 | from dbsetup import create_connection, create_session, update_or_create_page, select_all_sessions, select_all_user_visits, select_all_pages 11 | 12 | app = Flask(__name__) 13 | app.secret_key = os.urandom(24) 14 | 15 | pusher = Pusher(app_id=u'PUSHER_APP_ID', key=u'PUSHER_APP_KEY', secret=u'PUSHER_APP_SECRER', cluster=u'PUSHER_APP_CLUSTER') 16 | 17 | database = "./pythonsqlite.db" 18 | conn = create_connection(database) 19 | c = conn.cursor() 20 | 21 | userOS = None 22 | userIP = None 23 | userCity = None 24 | userBrowser = None 25 | userCountry = None 26 | userContinent = None 27 | sessionID = None 28 | 29 | def main(): 30 | global conn, c 31 | 32 | def parseVisitor(data): 33 | update_or_create_page(c,data) 34 | pusher.trigger(u'pageview', u'new', { 35 | u'page': data[0], 36 | u'session': sessionID, 37 | u'ip': userIP 38 | }) 39 | pusher.trigger(u'numbers', u'update', { 40 | u'page': data[0], 41 | u'session': sessionID, 42 | u'ip': userIP 43 | }) 44 | 45 | @app.before_request 46 | def getAnalyticsData(): 47 | global userOS, userBrowser, userIP, userContinent, userCity, userCountry,sessionID 48 | userInfo = httpagentparser.detect(request.headers.get('User-Agent')) 49 | userOS = userInfo['platform']['name'] 50 | userBrowser = userInfo['browser']['name'] 51 | userIP = "72.229.28.185" if request.remote_addr == '127.0.0.1' else request.remote_addr 52 | api = "https://www.iplocate.io/api/lookup/" + userIP 53 | try: 54 | resp = urllib.request.urlopen(api) 55 | result = resp.read() 56 | result = json.loads(result.decode("utf-8")) 57 | userCountry = result["country"] 58 | userContinent = result["continent"] 59 | userCity = result["city"] 60 | except: 61 | print("Could not find: ", userIP) 62 | getSession() 63 | 64 | def getSession(): 65 | global sessionID 66 | time = datetime.now().replace(microsecond=0) 67 | if 'user' not in session: 68 | lines = (str(time)+userIP).encode('utf-8') 69 | session['user'] = hashlib.md5(lines).hexdigest() 70 | sessionID = session['user'] 71 | pusher.trigger(u'session', u'new', { 72 | u'ip': userIP, 73 | u'continent': userContinent, 74 | u'country': userCountry, 75 | u'city': userCity, 76 | u'os': userOS, 77 | u'browser': userBrowser, 78 | u'session': sessionID, 79 | u'time': str(time), 80 | }) 81 | data = [userIP, userContinent, userCountry, userCity, userOS, userBrowser, sessionID, time] 82 | create_session(c,data) 83 | else: 84 | sessionID = session['user'] 85 | 86 | @app.route('/') 87 | def index(): 88 | data = ['home', sessionID, str(datetime.now().replace(microsecond=0))] 89 | parseVisitor(data) 90 | return render_template('index.html') 91 | 92 | @app.route('/about') 93 | def about(): 94 | data = ['about',sessionID, str(datetime.now().replace(microsecond=0))] 95 | parseVisitor(data) 96 | return render_template('about.html') 97 | 98 | @app.route('/dashboard') 99 | def dashboard(): 100 | return render_template('dashboard.html') 101 | 102 | @app.route('/dashboard/', methods=['GET']) 103 | def sessionPages(session_id): 104 | result = select_all_user_visits(c,session_id) 105 | return render_template("dashboard-single.html",data=result) 106 | 107 | @app.route('/get-all-sessions') 108 | def get_all_sessions(): 109 | data = [] 110 | dbRows = select_all_sessions(c) 111 | for row in dbRows: 112 | data.append({ 113 | 'ip' : row['ip'], 114 | 'continent' : row['continent'], 115 | 'country' : row['country'], 116 | 'city' : row['city'], 117 | 'os' : row['os'], 118 | 'browser' : row['browser'], 119 | 'session' : row['session'], 120 | 'time' : row['created_at'] 121 | }) 122 | return jsonify(data) 123 | 124 | if __name__ == '__main__': 125 | main() 126 | app.run(debug=True) 127 | -------------------------------------------------------------------------------- /dbsetup.py: -------------------------------------------------------------------------------- 1 | 2 | import sqlite3 3 | from sqlite3 import Error 4 | 5 | def create_connection(database): 6 | try: 7 | conn = sqlite3.connect(database, isolation_level=None, check_same_thread = False) 8 | conn.row_factory = lambda c, r: dict(zip([col[0] for col in c.description], r)) 9 | 10 | return conn 11 | except Error as e: 12 | print(e) 13 | 14 | def create_table(c,sql): 15 | c.execute(sql) 16 | 17 | def update_or_create_page(c,data): 18 | sql = "SELECT * FROM pages where name=? and session=?" 19 | c.execute(sql,data[:-1]) 20 | result = c.fetchone() 21 | if result == None: 22 | create_pages(c,data) 23 | else: 24 | print(result) 25 | update_pages(c, result['id']) 26 | 27 | def create_pages(c, data): 28 | print(data) 29 | sql = ''' INSERT INTO pages(name,session,first_visited) 30 | VALUES (?,?,?) ''' 31 | c.execute(sql, data) 32 | 33 | def update_pages(c, pageId): 34 | print(pageId) 35 | sql = ''' UPDATE pages 36 | SET visits = visits+1 37 | WHERE id = ?''' 38 | c.execute(sql, [pageId]) 39 | 40 | def create_session(c, data): 41 | sql = ''' INSERT INTO sessions(ip, continent, country, city, os, browser, session, created_at) 42 | VALUES (?,?,?,?,?,?,?,?) ''' 43 | c.execute(sql, data) 44 | 45 | def select_all_sessions(c): 46 | sql = "SELECT * FROM sessions" 47 | c.execute(sql) 48 | rows = c.fetchall() 49 | return rows 50 | 51 | def select_all_pages(c): 52 | sql = "SELECT * FROM pages" 53 | c.execute(sql) 54 | rows = c.fetchall() 55 | return rows 56 | 57 | def select_all_user_visits(c, session_id): 58 | sql = "SELECT * FROM pages where session =?" 59 | c.execute(sql,[session_id]) 60 | rows = c.fetchall() 61 | return rows 62 | 63 | def main(): 64 | database = "./pythonsqlite.db" 65 | sql_create_pages = """ 66 | CREATE TABLE IF NOT EXISTS pages ( 67 | id integer PRIMARY KEY, 68 | name varchar(225) NOT NULL, 69 | session varchar(255) NOT NULL, 70 | first_visited datetime NOT NULL, 71 | visits integer NOT NULL Default 1 72 | ); 73 | """ 74 | sql_create_session = """ 75 | CREATE TABLE IF NOT EXISTS sessions ( 76 | id integer PRIMARY KEY, 77 | ip varchar(225) NOT NULL, 78 | continent varchar(225) NOT NULL, 79 | country varchar(225) NOT NULL, 80 | city varchar(225) NOT NULL, 81 | os varchar(225) NOT NULL, 82 | browser varchar(225) NOT NULL, 83 | session varchar(225) NOT NULL, 84 | created_at datetime NOT NULL 85 | ); 86 | """ 87 | 88 | # create a database connection 89 | conn = create_connection(database) 90 | if conn is not None: 91 | # create tables 92 | create_table(conn, sql_create_pages) 93 | create_table(conn, sql_create_session) 94 | print("Connection established!") 95 | else: 96 | print("Could not establish connection") 97 | 98 | if __name__ == '__main__': 99 | main() -------------------------------------------------------------------------------- /static/css/sb-admin.css: -------------------------------------------------------------------------------- 1 | html { 2 | position: relative; 3 | min-height: 100%; 4 | } 5 | 6 | body { 7 | overflow-x: hidden; 8 | } 9 | 10 | body.sticky-footer { 11 | margin-bottom: 56px; 12 | } 13 | 14 | body.sticky-footer .content-wrapper { 15 | min-height: calc(100vh - 56px - 56px); 16 | } 17 | 18 | body.fixed-nav { 19 | padding-top: 56px; 20 | } 21 | 22 | .content-wrapper { 23 | min-height: calc(100vh - 56px); 24 | padding-top: 1rem; 25 | } 26 | 27 | .scroll-to-top { 28 | position: fixed; 29 | right: 15px; 30 | bottom: 3px; 31 | display: none; 32 | width: 50px; 33 | height: 50px; 34 | text-align: center; 35 | color: white; 36 | background: rgba(52, 58, 64, 0.5); 37 | line-height: 45px; 38 | } 39 | 40 | .scroll-to-top:focus, .scroll-to-top:hover { 41 | color: white; 42 | } 43 | 44 | .scroll-to-top:hover { 45 | background: #343a40; 46 | } 47 | 48 | .scroll-to-top i { 49 | font-weight: 800; 50 | } 51 | 52 | .smaller { 53 | font-size: 0.7rem; 54 | } 55 | 56 | .o-hidden { 57 | overflow: hidden !important; 58 | } 59 | 60 | .z-0 { 61 | z-index: 0; 62 | } 63 | 64 | .z-1 { 65 | z-index: 1; 66 | } 67 | 68 | #mainNav .navbar-collapse { 69 | overflow: auto; 70 | max-height: 75vh; 71 | } 72 | 73 | #mainNav .navbar-collapse .navbar-nav .nav-item .nav-link { 74 | cursor: pointer; 75 | } 76 | 77 | #mainNav .navbar-collapse .navbar-sidenav .nav-link-collapse:after { 78 | float: right; 79 | content: '\f107'; 80 | font-family: 'FontAwesome'; 81 | } 82 | 83 | #mainNav .navbar-collapse .navbar-sidenav .nav-link-collapse.collapsed:after { 84 | content: '\f105'; 85 | } 86 | 87 | #mainNav .navbar-collapse .navbar-sidenav .sidenav-second-level, 88 | #mainNav .navbar-collapse .navbar-sidenav .sidenav-third-level { 89 | padding-left: 0; 90 | } 91 | 92 | #mainNav .navbar-collapse .navbar-sidenav .sidenav-second-level > li > a, 93 | #mainNav .navbar-collapse .navbar-sidenav .sidenav-third-level > li > a { 94 | display: block; 95 | padding: 0.5em 0; 96 | } 97 | 98 | #mainNav .navbar-collapse .navbar-sidenav .sidenav-second-level > li > a:focus, #mainNav .navbar-collapse .navbar-sidenav .sidenav-second-level > li > a:hover, 99 | #mainNav .navbar-collapse .navbar-sidenav .sidenav-third-level > li > a:focus, 100 | #mainNav .navbar-collapse .navbar-sidenav .sidenav-third-level > li > a:hover { 101 | text-decoration: none; 102 | } 103 | 104 | #mainNav .navbar-collapse .navbar-sidenav .sidenav-second-level > li > a { 105 | padding-left: 1em; 106 | } 107 | 108 | #mainNav .navbar-collapse .navbar-sidenav .sidenav-third-level > li > a { 109 | padding-left: 2em; 110 | } 111 | 112 | #mainNav .navbar-collapse .sidenav-toggler { 113 | display: none; 114 | } 115 | 116 | #mainNav .navbar-collapse .navbar-nav > .nav-item.dropdown > .nav-link { 117 | position: relative; 118 | min-width: 45px; 119 | } 120 | 121 | #mainNav .navbar-collapse .navbar-nav > .nav-item.dropdown > .nav-link:after { 122 | float: right; 123 | width: auto; 124 | content: '\f105'; 125 | border: none; 126 | font-family: 'FontAwesome'; 127 | } 128 | 129 | #mainNav .navbar-collapse .navbar-nav > .nav-item.dropdown > .nav-link .indicator { 130 | position: absolute; 131 | top: 5px; 132 | left: 21px; 133 | font-size: 10px; 134 | } 135 | 136 | #mainNav .navbar-collapse .navbar-nav > .nav-item.dropdown.show > .nav-link:after { 137 | content: '\f107'; 138 | } 139 | 140 | #mainNav .navbar-collapse .navbar-nav > .nav-item.dropdown .dropdown-menu > .dropdown-item > .dropdown-message { 141 | overflow: hidden; 142 | max-width: none; 143 | text-overflow: ellipsis; 144 | } 145 | 146 | @media (min-width: 992px) { 147 | #mainNav .navbar-brand { 148 | width: 250px; 149 | } 150 | #mainNav .navbar-collapse { 151 | overflow: visible; 152 | max-height: none; 153 | } 154 | #mainNav .navbar-collapse .navbar-sidenav { 155 | position: absolute; 156 | top: 0; 157 | left: 0; 158 | -webkit-flex-direction: column; 159 | -ms-flex-direction: column; 160 | flex-direction: column; 161 | margin-top: 56px; 162 | } 163 | #mainNav .navbar-collapse .navbar-sidenav > .nav-item { 164 | width: 250px; 165 | padding: 0; 166 | } 167 | #mainNav .navbar-collapse .navbar-sidenav > .nav-item > .nav-link { 168 | padding: 1em; 169 | } 170 | #mainNav .navbar-collapse .navbar-sidenav > .nav-item .sidenav-second-level, 171 | #mainNav .navbar-collapse .navbar-sidenav > .nav-item .sidenav-third-level { 172 | padding-left: 0; 173 | list-style: none; 174 | } 175 | #mainNav .navbar-collapse .navbar-sidenav > .nav-item .sidenav-second-level > li, 176 | #mainNav .navbar-collapse .navbar-sidenav > .nav-item .sidenav-third-level > li { 177 | width: 250px; 178 | } 179 | #mainNav .navbar-collapse .navbar-sidenav > .nav-item .sidenav-second-level > li > a, 180 | #mainNav .navbar-collapse .navbar-sidenav > .nav-item .sidenav-third-level > li > a { 181 | padding: 1em; 182 | } 183 | #mainNav .navbar-collapse .navbar-sidenav > .nav-item .sidenav-second-level > li > a { 184 | padding-left: 2.75em; 185 | } 186 | #mainNav .navbar-collapse .navbar-sidenav > .nav-item .sidenav-third-level > li > a { 187 | padding-left: 3.75em; 188 | } 189 | #mainNav .navbar-collapse .navbar-nav > .nav-item.dropdown > .nav-link { 190 | min-width: 0; 191 | } 192 | #mainNav .navbar-collapse .navbar-nav > .nav-item.dropdown > .nav-link:after { 193 | width: 24px; 194 | text-align: center; 195 | } 196 | #mainNav .navbar-collapse .navbar-nav > .nav-item.dropdown .dropdown-menu > .dropdown-item > .dropdown-message { 197 | max-width: 300px; 198 | } 199 | } 200 | 201 | #mainNav.fixed-top .sidenav-toggler { 202 | display: none; 203 | } 204 | 205 | @media (min-width: 992px) { 206 | #mainNav.fixed-top .navbar-sidenav { 207 | height: calc(100vh - 112px); 208 | } 209 | #mainNav.fixed-top .sidenav-toggler { 210 | position: absolute; 211 | top: 0; 212 | left: 0; 213 | display: flex; 214 | -webkit-flex-direction: column; 215 | -ms-flex-direction: column; 216 | flex-direction: column; 217 | margin-top: calc(100vh - 56px); 218 | } 219 | #mainNav.fixed-top .sidenav-toggler > .nav-item { 220 | width: 250px; 221 | padding: 0; 222 | } 223 | #mainNav.fixed-top .sidenav-toggler > .nav-item > .nav-link { 224 | padding: 1em; 225 | } 226 | } 227 | 228 | #mainNav.fixed-top.navbar-dark .sidenav-toggler { 229 | background-color: #212529; 230 | } 231 | 232 | #mainNav.fixed-top.navbar-dark .sidenav-toggler a i { 233 | color: #adb5bd; 234 | } 235 | 236 | #mainNav.fixed-top.navbar-light .sidenav-toggler { 237 | background-color: #dee2e6; 238 | } 239 | 240 | #mainNav.fixed-top.navbar-light .sidenav-toggler a i { 241 | color: rgba(0, 0, 0, 0.5); 242 | } 243 | 244 | body.sidenav-toggled #mainNav.fixed-top .sidenav-toggler { 245 | overflow-x: hidden; 246 | width: 55px; 247 | } 248 | 249 | body.sidenav-toggled #mainNav.fixed-top .sidenav-toggler .nav-item, 250 | body.sidenav-toggled #mainNav.fixed-top .sidenav-toggler .nav-link { 251 | width: 55px !important; 252 | } 253 | 254 | body.sidenav-toggled #mainNav.fixed-top #sidenavToggler i { 255 | -webkit-transform: scaleX(-1); 256 | -moz-transform: scaleX(-1); 257 | -o-transform: scaleX(-1); 258 | transform: scaleX(-1); 259 | filter: FlipH; 260 | -ms-filter: 'FlipH'; 261 | } 262 | 263 | #mainNav.static-top .sidenav-toggler { 264 | display: none; 265 | } 266 | 267 | @media (min-width: 992px) { 268 | #mainNav.static-top .sidenav-toggler { 269 | display: flex; 270 | } 271 | } 272 | 273 | body.sidenav-toggled #mainNav.static-top #sidenavToggler i { 274 | -webkit-transform: scaleX(-1); 275 | -moz-transform: scaleX(-1); 276 | -o-transform: scaleX(-1); 277 | transform: scaleX(-1); 278 | filter: FlipH; 279 | -ms-filter: 'FlipH'; 280 | } 281 | 282 | .content-wrapper { 283 | overflow-x: hidden; 284 | background: white; 285 | } 286 | 287 | @media (min-width: 992px) { 288 | .content-wrapper { 289 | margin-left: 250px; 290 | } 291 | } 292 | 293 | #sidenavToggler i { 294 | font-weight: 800; 295 | } 296 | 297 | .navbar-sidenav-tooltip.show { 298 | display: none; 299 | } 300 | 301 | @media (min-width: 992px) { 302 | body.sidenav-toggled .content-wrapper { 303 | margin-left: 55px; 304 | } 305 | } 306 | 307 | body.sidenav-toggled .navbar-sidenav { 308 | width: 55px; 309 | } 310 | 311 | body.sidenav-toggled .navbar-sidenav .nav-link-text { 312 | display: none; 313 | } 314 | 315 | body.sidenav-toggled .navbar-sidenav .nav-item, 316 | body.sidenav-toggled .navbar-sidenav .nav-link { 317 | width: 55px !important; 318 | } 319 | 320 | body.sidenav-toggled .navbar-sidenav .nav-item:after, 321 | body.sidenav-toggled .navbar-sidenav .nav-link:after { 322 | display: none; 323 | } 324 | 325 | body.sidenav-toggled .navbar-sidenav .nav-item { 326 | white-space: nowrap; 327 | } 328 | 329 | body.sidenav-toggled .navbar-sidenav-tooltip.show { 330 | display: flex; 331 | } 332 | 333 | #mainNav.navbar-dark .navbar-collapse .navbar-sidenav .nav-link-collapse:after { 334 | color: #868e96; 335 | } 336 | 337 | #mainNav.navbar-dark .navbar-collapse .navbar-sidenav > .nav-item > .nav-link { 338 | color: #868e96; 339 | } 340 | 341 | #mainNav.navbar-dark .navbar-collapse .navbar-sidenav > .nav-item > .nav-link:hover { 342 | color: #adb5bd; 343 | } 344 | 345 | #mainNav.navbar-dark .navbar-collapse .navbar-sidenav > .nav-item .sidenav-second-level > li > a, 346 | #mainNav.navbar-dark .navbar-collapse .navbar-sidenav > .nav-item .sidenav-third-level > li > a { 347 | color: #868e96; 348 | } 349 | 350 | #mainNav.navbar-dark .navbar-collapse .navbar-sidenav > .nav-item .sidenav-second-level > li > a:focus, #mainNav.navbar-dark .navbar-collapse .navbar-sidenav > .nav-item .sidenav-second-level > li > a:hover, 351 | #mainNav.navbar-dark .navbar-collapse .navbar-sidenav > .nav-item .sidenav-third-level > li > a:focus, 352 | #mainNav.navbar-dark .navbar-collapse .navbar-sidenav > .nav-item .sidenav-third-level > li > a:hover { 353 | color: #adb5bd; 354 | } 355 | 356 | #mainNav.navbar-dark .navbar-collapse .navbar-nav > .nav-item.dropdown > .nav-link:after { 357 | color: #adb5bd; 358 | } 359 | 360 | @media (min-width: 992px) { 361 | #mainNav.navbar-dark .navbar-collapse .navbar-sidenav { 362 | background: #343a40; 363 | } 364 | #mainNav.navbar-dark .navbar-collapse .navbar-sidenav li.active a { 365 | color: white !important; 366 | background-color: #495057; 367 | } 368 | #mainNav.navbar-dark .navbar-collapse .navbar-sidenav li.active a:focus, #mainNav.navbar-dark .navbar-collapse .navbar-sidenav li.active a:hover { 369 | color: white; 370 | } 371 | #mainNav.navbar-dark .navbar-collapse .navbar-sidenav > .nav-item .sidenav-second-level, 372 | #mainNav.navbar-dark .navbar-collapse .navbar-sidenav > .nav-item .sidenav-third-level { 373 | background: #343a40; 374 | } 375 | } 376 | 377 | #mainNav.navbar-light .navbar-collapse .navbar-sidenav .nav-link-collapse:after { 378 | color: rgba(0, 0, 0, 0.5); 379 | } 380 | 381 | #mainNav.navbar-light .navbar-collapse .navbar-sidenav > .nav-item > .nav-link { 382 | color: rgba(0, 0, 0, 0.5); 383 | } 384 | 385 | #mainNav.navbar-light .navbar-collapse .navbar-sidenav > .nav-item > .nav-link:hover { 386 | color: rgba(0, 0, 0, 0.7); 387 | } 388 | 389 | #mainNav.navbar-light .navbar-collapse .navbar-sidenav > .nav-item .sidenav-second-level > li > a, 390 | #mainNav.navbar-light .navbar-collapse .navbar-sidenav > .nav-item .sidenav-third-level > li > a { 391 | color: rgba(0, 0, 0, 0.5); 392 | } 393 | 394 | #mainNav.navbar-light .navbar-collapse .navbar-sidenav > .nav-item .sidenav-second-level > li > a:focus, #mainNav.navbar-light .navbar-collapse .navbar-sidenav > .nav-item .sidenav-second-level > li > a:hover, 395 | #mainNav.navbar-light .navbar-collapse .navbar-sidenav > .nav-item .sidenav-third-level > li > a:focus, 396 | #mainNav.navbar-light .navbar-collapse .navbar-sidenav > .nav-item .sidenav-third-level > li > a:hover { 397 | color: rgba(0, 0, 0, 0.7); 398 | } 399 | 400 | #mainNav.navbar-light .navbar-collapse .navbar-nav > .nav-item.dropdown > .nav-link:after { 401 | color: rgba(0, 0, 0, 0.5); 402 | } 403 | 404 | @media (min-width: 992px) { 405 | #mainNav.navbar-light .navbar-collapse .navbar-sidenav { 406 | background: #f8f9fa; 407 | } 408 | #mainNav.navbar-light .navbar-collapse .navbar-sidenav li.active a { 409 | color: #000 !important; 410 | background-color: #e9ecef; 411 | } 412 | #mainNav.navbar-light .navbar-collapse .navbar-sidenav li.active a:focus, #mainNav.navbar-light .navbar-collapse .navbar-sidenav li.active a:hover { 413 | color: #000; 414 | } 415 | #mainNav.navbar-light .navbar-collapse .navbar-sidenav > .nav-item .sidenav-second-level, 416 | #mainNav.navbar-light .navbar-collapse .navbar-sidenav > .nav-item .sidenav-third-level { 417 | background: #f8f9fa; 418 | } 419 | } 420 | 421 | .card-body-icon { 422 | position: absolute; 423 | z-index: 0; 424 | top: -25px; 425 | right: -25px; 426 | font-size: 5rem; 427 | -webkit-transform: rotate(15deg); 428 | -ms-transform: rotate(15deg); 429 | transform: rotate(15deg); 430 | } 431 | 432 | @media (min-width: 576px) { 433 | .card-columns { 434 | column-count: 1; 435 | } 436 | } 437 | 438 | @media (min-width: 768px) { 439 | .card-columns { 440 | column-count: 2; 441 | } 442 | } 443 | 444 | @media (min-width: 1200px) { 445 | .card-columns { 446 | column-count: 2; 447 | } 448 | } 449 | 450 | .card-login { 451 | max-width: 25rem; 452 | } 453 | 454 | .card-register { 455 | max-width: 40rem; 456 | } 457 | 458 | footer.sticky-footer { 459 | position: absolute; 460 | right: 0; 461 | bottom: 0; 462 | width: 100%; 463 | height: 56px; 464 | background-color: #e9ecef; 465 | line-height: 55px; 466 | } 467 | 468 | @media (min-width: 992px) { 469 | footer.sticky-footer { 470 | width: calc(100% - 250px); 471 | } 472 | } 473 | 474 | @media (min-width: 992px) { 475 | body.sidenav-toggled footer.sticky-footer { 476 | width: calc(100% - 55px); 477 | } 478 | } -------------------------------------------------------------------------------- /static/css/sb-admin.min.css: -------------------------------------------------------------------------------- 1 | html{position:relative;min-height:100%}body{overflow-x:hidden}body.sticky-footer{margin-bottom:56px}body.sticky-footer .content-wrapper{min-height:calc(100vh - 56px - 56px)}body.fixed-nav{padding-top:56px}.content-wrapper{min-height:calc(100vh - 56px);padding-top:1rem}.scroll-to-top{position:fixed;right:15px;bottom:3px;display:none;width:50px;height:50px;text-align:center;color:#fff;background:rgba(52,58,64,.5);line-height:45px}.scroll-to-top:focus,.scroll-to-top:hover{color:#fff}.scroll-to-top:hover{background:#343a40}.scroll-to-top i{font-weight:800}.smaller{font-size:.7rem}.o-hidden{overflow:hidden!important}.z-0{z-index:0}.z-1{z-index:1}#mainNav .navbar-collapse{overflow:auto;max-height:75vh}#mainNav .navbar-collapse .navbar-nav .nav-item .nav-link{cursor:pointer}#mainNav .navbar-collapse .navbar-sidenav .nav-link-collapse:after{float:right;content:'\f107';font-family:FontAwesome}#mainNav .navbar-collapse .navbar-sidenav .nav-link-collapse.collapsed:after{content:'\f105'}#mainNav .navbar-collapse .navbar-sidenav .sidenav-second-level,#mainNav .navbar-collapse .navbar-sidenav .sidenav-third-level{padding-left:0}#mainNav .navbar-collapse .navbar-sidenav .sidenav-second-level>li>a,#mainNav .navbar-collapse .navbar-sidenav .sidenav-third-level>li>a{display:block;padding:.5em 0}#mainNav .navbar-collapse .navbar-sidenav .sidenav-second-level>li>a:focus,#mainNav .navbar-collapse .navbar-sidenav .sidenav-second-level>li>a:hover,#mainNav .navbar-collapse .navbar-sidenav .sidenav-third-level>li>a:focus,#mainNav .navbar-collapse .navbar-sidenav .sidenav-third-level>li>a:hover{text-decoration:none}#mainNav .navbar-collapse .navbar-sidenav .sidenav-second-level>li>a{padding-left:1em}#mainNav .navbar-collapse .navbar-sidenav .sidenav-third-level>li>a{padding-left:2em}#mainNav .navbar-collapse .sidenav-toggler{display:none}#mainNav .navbar-collapse .navbar-nav>.nav-item.dropdown>.nav-link{position:relative;min-width:45px}#mainNav .navbar-collapse .navbar-nav>.nav-item.dropdown>.nav-link:after{float:right;width:auto;content:'\f105';border:none;font-family:FontAwesome}#mainNav .navbar-collapse .navbar-nav>.nav-item.dropdown>.nav-link .indicator{position:absolute;top:5px;left:21px;font-size:10px}#mainNav .navbar-collapse .navbar-nav>.nav-item.dropdown.show>.nav-link:after{content:'\f107'}#mainNav .navbar-collapse .navbar-nav>.nav-item.dropdown .dropdown-menu>.dropdown-item>.dropdown-message{overflow:hidden;max-width:none;text-overflow:ellipsis}@media (min-width:992px){#mainNav .navbar-brand{width:250px}#mainNav .navbar-collapse{overflow:visible;max-height:none}#mainNav .navbar-collapse .navbar-sidenav{position:absolute;top:0;left:0;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;margin-top:56px}#mainNav .navbar-collapse .navbar-sidenav>.nav-item{width:250px;padding:0}#mainNav .navbar-collapse .navbar-sidenav>.nav-item>.nav-link{padding:1em}#mainNav .navbar-collapse .navbar-sidenav>.nav-item .sidenav-second-level,#mainNav .navbar-collapse .navbar-sidenav>.nav-item .sidenav-third-level{padding-left:0;list-style:none}#mainNav .navbar-collapse .navbar-sidenav>.nav-item .sidenav-second-level>li,#mainNav .navbar-collapse .navbar-sidenav>.nav-item .sidenav-third-level>li{width:250px}#mainNav .navbar-collapse .navbar-sidenav>.nav-item .sidenav-second-level>li>a,#mainNav .navbar-collapse .navbar-sidenav>.nav-item .sidenav-third-level>li>a{padding:1em}#mainNav .navbar-collapse .navbar-sidenav>.nav-item .sidenav-second-level>li>a{padding-left:2.75em}#mainNav .navbar-collapse .navbar-sidenav>.nav-item .sidenav-third-level>li>a{padding-left:3.75em}#mainNav .navbar-collapse .navbar-nav>.nav-item.dropdown>.nav-link{min-width:0}#mainNav .navbar-collapse .navbar-nav>.nav-item.dropdown>.nav-link:after{width:24px;text-align:center}#mainNav .navbar-collapse .navbar-nav>.nav-item.dropdown .dropdown-menu>.dropdown-item>.dropdown-message{max-width:300px}}#mainNav.fixed-top .sidenav-toggler{display:none}@media (min-width:992px){#mainNav.fixed-top .navbar-sidenav{height:calc(100vh - 112px)}#mainNav.fixed-top .sidenav-toggler{position:absolute;top:0;left:0;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;margin-top:calc(100vh - 56px)}#mainNav.fixed-top .sidenav-toggler>.nav-item{width:250px;padding:0}#mainNav.fixed-top .sidenav-toggler>.nav-item>.nav-link{padding:1em}}#mainNav.fixed-top.navbar-dark .sidenav-toggler{background-color:#212529}#mainNav.fixed-top.navbar-dark .sidenav-toggler a i{color:#adb5bd}#mainNav.fixed-top.navbar-light .sidenav-toggler{background-color:#dee2e6}#mainNav.fixed-top.navbar-light .sidenav-toggler a i{color:rgba(0,0,0,.5)}body.sidenav-toggled #mainNav.fixed-top .sidenav-toggler{overflow-x:hidden;width:55px}body.sidenav-toggled #mainNav.fixed-top .sidenav-toggler .nav-item,body.sidenav-toggled #mainNav.fixed-top .sidenav-toggler .nav-link{width:55px!important}body.sidenav-toggled #mainNav.fixed-top #sidenavToggler i{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1);filter:FlipH;-ms-filter:FlipH}#mainNav.static-top .sidenav-toggler{display:none}@media (min-width:992px){#mainNav.static-top .sidenav-toggler{display:flex}}body.sidenav-toggled #mainNav.static-top #sidenavToggler i{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1);filter:FlipH;-ms-filter:FlipH}.content-wrapper{overflow-x:hidden;background:#fff}@media (min-width:992px){.content-wrapper{margin-left:250px}}#sidenavToggler i{font-weight:800}.navbar-sidenav-tooltip.show{display:none}@media (min-width:992px){body.sidenav-toggled .content-wrapper{margin-left:55px}}body.sidenav-toggled .navbar-sidenav{width:55px}body.sidenav-toggled .navbar-sidenav .nav-link-text{display:none}body.sidenav-toggled .navbar-sidenav .nav-item,body.sidenav-toggled .navbar-sidenav .nav-link{width:55px!important}body.sidenav-toggled .navbar-sidenav .nav-item:after,body.sidenav-toggled .navbar-sidenav .nav-link:after{display:none}body.sidenav-toggled .navbar-sidenav .nav-item{white-space:nowrap}body.sidenav-toggled .navbar-sidenav-tooltip.show{display:flex}#mainNav.navbar-dark .navbar-collapse .navbar-sidenav .nav-link-collapse:after{color:#868e96}#mainNav.navbar-dark .navbar-collapse .navbar-sidenav>.nav-item>.nav-link{color:#868e96}#mainNav.navbar-dark .navbar-collapse .navbar-sidenav>.nav-item>.nav-link:hover{color:#adb5bd}#mainNav.navbar-dark .navbar-collapse .navbar-sidenav>.nav-item .sidenav-second-level>li>a,#mainNav.navbar-dark .navbar-collapse .navbar-sidenav>.nav-item .sidenav-third-level>li>a{color:#868e96}#mainNav.navbar-dark .navbar-collapse .navbar-sidenav>.nav-item .sidenav-second-level>li>a:focus,#mainNav.navbar-dark .navbar-collapse .navbar-sidenav>.nav-item .sidenav-second-level>li>a:hover,#mainNav.navbar-dark .navbar-collapse .navbar-sidenav>.nav-item .sidenav-third-level>li>a:focus,#mainNav.navbar-dark .navbar-collapse .navbar-sidenav>.nav-item .sidenav-third-level>li>a:hover{color:#adb5bd}#mainNav.navbar-dark .navbar-collapse .navbar-nav>.nav-item.dropdown>.nav-link:after{color:#adb5bd}@media (min-width:992px){#mainNav.navbar-dark .navbar-collapse .navbar-sidenav{background:#343a40}#mainNav.navbar-dark .navbar-collapse .navbar-sidenav li.active a{color:#fff!important;background-color:#495057}#mainNav.navbar-dark .navbar-collapse .navbar-sidenav li.active a:focus,#mainNav.navbar-dark .navbar-collapse .navbar-sidenav li.active a:hover{color:#fff}#mainNav.navbar-dark .navbar-collapse .navbar-sidenav>.nav-item .sidenav-second-level,#mainNav.navbar-dark .navbar-collapse .navbar-sidenav>.nav-item .sidenav-third-level{background:#343a40}}#mainNav.navbar-light .navbar-collapse .navbar-sidenav .nav-link-collapse:after{color:rgba(0,0,0,.5)}#mainNav.navbar-light .navbar-collapse .navbar-sidenav>.nav-item>.nav-link{color:rgba(0,0,0,.5)}#mainNav.navbar-light .navbar-collapse .navbar-sidenav>.nav-item>.nav-link:hover{color:rgba(0,0,0,.7)}#mainNav.navbar-light .navbar-collapse .navbar-sidenav>.nav-item .sidenav-second-level>li>a,#mainNav.navbar-light .navbar-collapse .navbar-sidenav>.nav-item .sidenav-third-level>li>a{color:rgba(0,0,0,.5)}#mainNav.navbar-light .navbar-collapse .navbar-sidenav>.nav-item .sidenav-second-level>li>a:focus,#mainNav.navbar-light .navbar-collapse .navbar-sidenav>.nav-item .sidenav-second-level>li>a:hover,#mainNav.navbar-light .navbar-collapse .navbar-sidenav>.nav-item .sidenav-third-level>li>a:focus,#mainNav.navbar-light .navbar-collapse .navbar-sidenav>.nav-item .sidenav-third-level>li>a:hover{color:rgba(0,0,0,.7)}#mainNav.navbar-light .navbar-collapse .navbar-nav>.nav-item.dropdown>.nav-link:after{color:rgba(0,0,0,.5)}@media (min-width:992px){#mainNav.navbar-light .navbar-collapse .navbar-sidenav{background:#f8f9fa}#mainNav.navbar-light .navbar-collapse .navbar-sidenav li.active a{color:#000!important;background-color:#e9ecef}#mainNav.navbar-light .navbar-collapse .navbar-sidenav li.active a:focus,#mainNav.navbar-light .navbar-collapse .navbar-sidenav li.active a:hover{color:#000}#mainNav.navbar-light .navbar-collapse .navbar-sidenav>.nav-item .sidenav-second-level,#mainNav.navbar-light .navbar-collapse .navbar-sidenav>.nav-item .sidenav-third-level{background:#f8f9fa}}.card-body-icon{position:absolute;z-index:0;top:-25px;right:-25px;font-size:5rem;-webkit-transform:rotate(15deg);-ms-transform:rotate(15deg);transform:rotate(15deg)}@media (min-width:576px){.card-columns{column-count:1}}@media (min-width:768px){.card-columns{column-count:2}}@media (min-width:1200px){.card-columns{column-count:2}}.card-login{max-width:25rem}.card-register{max-width:40rem}footer.sticky-footer{position:absolute;right:0;bottom:0;width:100%;height:56px;background-color:#e9ecef;line-height:55px}@media (min-width:992px){footer.sticky-footer{width:calc(100% - 250px)}}@media (min-width:992px){body.sidenav-toggled footer.sticky-footer{width:calc(100% - 55px)}} -------------------------------------------------------------------------------- /static/js/app.js: -------------------------------------------------------------------------------- 1 | 2 | // Configure Pusher instance 3 | var pusher = new Pusher('3a2a219040583d8ee1b4', { 4 | cluster: 'mt1', 5 | encrypted: true 6 | }); 7 | 8 | var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; 9 | 10 | $(document).ready(function(){ 11 | var dataTable = $("#dataTable").DataTable() 12 | // var userSessions = $("#userSessions").DataTable() 13 | var pages = $("#pages").DataTable() 14 | 15 | axios.get('/get-all-sessions') 16 | .then(response => { 17 | response.data.forEach((data) => { 18 | insertDatatable(data) 19 | }) 20 | var d = new Date(); 21 | var updatedAt = `${d.getFullYear()}/${months[d.getMonth()]}/${d.getDay()} ${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}` 22 | document.getElementById('session-update-time').innerText = updatedAt 23 | }) 24 | 25 | var sessionChannel = pusher.subscribe('session'); 26 | sessionChannel.bind('new', function(data) { 27 | insertDatatable(data) 28 | }); 29 | 30 | var d = new Date(); 31 | var updatedAt = `${d.getFullYear()}/${months[d.getMonth()]}/${d.getDay()} ${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}` 32 | document.getElementById('session-update-time').innerText = updatedAt 33 | }); 34 | 35 | function insertDatatable(data){ 36 | var dataTable = $("#dataTable").DataTable() 37 | dataTable.row.add([ 38 | data.time, 39 | data.ip, 40 | data.continent, 41 | data.country, 42 | data.city, 43 | data.os, 44 | data.browser, 45 | `View pages visited` 46 | ]); 47 | dataTable.order([0, 'desc']).draw(); 48 | } -------------------------------------------------------------------------------- /static/js/sb-admin.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | "use strict"; // Start of use strict 3 | // Configure tooltips for collapsed side navigation 4 | $('.navbar-sidenav [data-toggle="tooltip"]').tooltip({ 5 | template: '' 6 | }) 7 | // Configure tooltips globally 8 | $('[data-toggle="tooltip"]').tooltip() 9 | // Smooth scrolling using jQuery easing 10 | $(document).on('click', 'a.scroll-to-top', function(event) { 11 | var $anchor = $(this); 12 | $('html, body').stop().animate({ 13 | scrollTop: ($($anchor.attr('href')).offset().top) 14 | }, 1000, 'easeInOutExpo'); 15 | event.preventDefault(); 16 | }); 17 | })(jQuery); // End of use strict -------------------------------------------------------------------------------- /templates/about.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Pusher Python Realtime Traffic Monitoring 6 | 7 | 8 |

This is the about page

9 | 10 | -------------------------------------------------------------------------------- /templates/dashboard-single.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | SB Admin - Start Bootstrap Template 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 43 |
44 |
45 | 46 | 52 | 53 |
54 |
55 | User Sessions
56 |
57 |
58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | {% for row in data %} 79 | 80 | 81 | 82 | 83 | 84 | 85 | {% endfor %} 86 | 87 |
TimePageSession_idNo of Visits
TimePageSession_idNo of Visits
{{ row.first_visited }}{{ row.name }}{{ row.session }}{{ row.visits }}
88 |
89 |
90 | 91 |
92 |
93 | 94 | 95 |
96 |
97 |
98 | Copyright © Your Website 2018 99 |
100 |
101 |
102 | 103 | 104 | 105 | 106 | 107 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 |
137 | 138 | -------------------------------------------------------------------------------- /templates/dashboard.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | SB Admin - Start Bootstrap Template 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 43 |
44 |
45 | 46 | 47 | 53 | 54 | 55 |
56 |
57 |
58 |
59 |
Total session 26
60 |
61 | 62 | View Details 63 | 64 | 65 | 66 | 67 |
68 |
69 |
70 |
71 |
72 |
Total visitors 26
73 |
74 | 75 | View Details 76 | 77 | 78 | 79 | 80 |
81 |
82 |
83 |
84 |
85 |
Total Page Views 123
86 |
87 | 88 | View Details 89 | 90 | 91 | 92 | 93 |
94 |
95 |
96 |
97 |
98 |
Unique Page Views 123
99 |
100 | 101 | View Details 102 | 103 | 104 | 105 | 106 |
107 |
108 |
109 | 110 | 111 |
112 |
113 | User Sessions
114 |
115 |
116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 |
TimeIP AddressContinentCountryCityOpertating SystemBrowser
TimeIP AddressContinentCountryCityOpertating SystemBrowser
144 |
145 |
146 | 147 |
148 |
149 | 150 | 151 | 152 | 153 |
154 |
155 |
156 | Copyright © Your Website 2018 157 |
158 |
159 |
160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 |
199 | 200 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Pusher Python Realtime Traffic Monitoring 6 | 7 | 8 |

This is the homepage

9 | 10 | --------------------------------------------------------------------------------