In today’s tech world, it is important to know where the traffic on your website comes from because it helps content creators and website owners understand their visitors better. Website owners can further use this information to improve their website, render better advertisements or generally improve their content based on this data.
In this tutorial, we will build a simple website and a traffic monitor, the traffic monitor will display details about visitors on the website. For every visitor, there will multiple columns such as time of visit, continent, country, city, operating system, browser, and pages visited.
Here is a display of what the final application will look like:
From the image above, we can see an incognito window to the left and the traffic monitor dashboard window to the right. In the incognito window, we visit our website on the local address 127.0.0.1:5000 and it updates in realtime on the traffic monitor dashboard. It registers that a new user has visited the website and displays the associated data. The user data is also saved to the database so that whenever we open the traffic monitor dashboard, the data persists.
We will build the backend server for this application using the Python framework called Flask. The source code for this tutorial is available here on GitHub.
Prerequisites
To follow along with this tutorial, a basic knowledge of Python, Flask, and JavaScript (ES6 syntax) is required. You will also need the following installed:
Virtualenv is great for creating isolated Python environments, so we can install dependencies in an isolated environment, and not pollute our global packages directory.
Let’s install virtualenv
with this command:
$ pip install virtualenv
Setting up the app environment
Let’s create our project folder, and activate a virtual environment in it. Run the commands below:
$ mkdir python-pusher-traffic-monitor
$ cd python-pusher-traffic-monitor
$ virtualenv .venv
$ source .venv/bin/activate # Linux based systems
$ pathtoenvScriptsactivate # Windows users
Now that we have the virtual environment setup, we can install Flask within it with this command:
$ pip install flask
Let’s also install httpagentparser
:
$ pip install httpagentparser
Before we do anything else, we need to install the Pusher library as we will need that for realtime updates.
Setting up Pusher
The first step will be to get a Pusher Channels application. We will need the application credentials for our realtime features to work.
Go to the Pusher website and create an account. After creating an account, you should create a new application. Follow the application creation wizard and then you should be given your application credentials, we will use this later in the article.
We also need to install the Pusher Python Library to send events to Pusher. Install this using the command below:
$ pip install pusher
File and Folder Structure
We don’t need to create so many files and folders for this application since it’s a simple one. Here’s the file/folder structure:
├── python-pusher-traffic-monitor
├── app.py
├── dbsetup.py
├── static
└── templates
The static
folder will contain the static files to be used as is defined by Flask standards. The templates
folder will contain the HTML templates. In our application, app.py
is the main entry point and will contain our server-side code. To keep things modular, we will write all the code that we need to interact with the database in dbsetup.py
.
Create the app.py
and dbsetup.py
files, and then the static
and templates
folders.
Building the backend
Let’s start by working on the file that will handle the database operations. In the dbsetup.py
file, we will write all the code that is needed for creating a database and interacting with it. Open the dbsetup.py
file and paste the following:
import sqlite3
from sqlite3 import Error
def create_connection(database):
try:
conn = sqlite3.connect(database, isolation_level=None, check_same_thread = False)
conn.row_factory = lambda c, r: dict(zip([col[0] for col in c.description], r))
return conn
except Error as e:
print(e)
def create_table(c,sql):
c.execute(sql)
def update_or_create_page(c,data):
sql = "SELECT * FROM pages where name=? and session=?"
c.execute(sql,data[:-1])
result = c.fetchone()
if result == None:
create_pages(c,data)
else:
print(result)
update_pages(c, result['id'])
def create_pages(c, data):
print(data)
sql = ''' INSERT INTO pages(name,session,first_visited)
VALUES (?,?,?) '''
c.execute(sql, data)
def update_pages(c, pageId):
print(pageId)
sql = ''' UPDATE pages
SET visits = visits+1
WHERE id = ?'''
c.execute(sql, [pageId])
def create_session(c, data):
sql = ''' INSERT INTO sessions(ip, continent, country, city, os, browser, session, created_at)
VALUES (?,?,?,?,?,?,?,?) '''
c.execute(sql, data)
def select_all_sessions(c):
sql = "SELECT * FROM sessions"
c.execute(sql)
rows = c.fetchall()
return rows
def select_all_pages(c):
sql = "SELECT * FROM pages"
c.execute(sql)
rows = c.fetchall()
return rows
def select_all_user_visits(c, session_id):
sql = "SELECT * FROM pages where session =?"
c.execute(sql,[session_id])
rows = c.fetchall()
return rows
def main():
database = "./pythonsqlite.db"
sql_create_pages = """
CREATE TABLE IF NOT EXISTS pages (
id integer PRIMARY KEY,
name varchar(225) NOT NULL,
session varchar(255) NOT NULL,
first_visited datetime NOT NULL,
visits integer NOT NULL Default 1
);
"""
sql_create_session = """
CREATE TABLE IF NOT EXISTS sessions (
id integer PRIMARY KEY,
ip varchar(225) NOT NULL,
continent varchar(225) NOT NULL,
country varchar(225) NOT NULL,
city varchar(225) NOT NULL,
os varchar(225) NOT NULL,
browser varchar(225) NOT NULL,
session varchar(225) NOT NULL,
created_at datetime NOT NULL
);
"""
# create a database connection
conn = create_connection(database)
if conn is not None:
# create tables
create_table(conn, sql_create_pages)
create_table(conn, sql_create_session)
print("Connection established!")
else:
print("Could not establish connection")
if __name__ == '__main__':
main()
Next, run the dbsetup.py
file so that it creates a new SQLite database for us. We can run it with this command:
$ python dbsetup.py
We should see this text logged to the terminal — ‘Connection established!’ — and there should be a new file — pythonsqlite.db
— added to the project’s root directory.
Next, let’s open the app.py
file and start writing the backend code that will handle incoming requests. We are going to register multiple routes here. Four of these routes will load a webpage each while the other routes will process submitted data and return a JSON response.
We will also create a Pusher instance and use it to broadcast data through two channels that we will shortly define — pageview and numbers — in the application. After that, we will import the database handler methods we defined in dbsetup.py
so that we can use them in the app.py
file. Open the app.py
file and paste the following:
from flask import Flask, render_template, request, session, jsonify
import urllib.request
from pusher import Pusher
from datetime import datetime
import httpagentparser
import json
import os
import hashlib
from dbsetup import create_connection, create_session, update_or_create_page, select_all_sessions, select_all_user_visits, select_all_pages
app = Flask(__name__)
app.secret_key = os.urandom(24)
# configure pusher object
pusher = Pusher(
app_id='PUSHER_APP_ID',
key='PUSHER_APP_KEY',
secret='PUSHER_APP_SECRET',
cluster='PUSHER_APP_CLUSTER',
ssl=True)
database = "./pythonsqlite.db"
conn = create_connection(database)
c = conn.cursor()
userOS = None
userIP = None
userCity = None
userBrowser = None
userCountry = None
userContinent = None
sessionID = None
def main():
global conn, c
def parseVisitor(data):
update_or_create_page(c,data)
pusher.trigger(u'pageview', u'new', {
u'page': data[0],
u'session': sessionID,
u'ip': userIP
})
pusher.trigger(u'numbers', u'update', {
u'page': data[0],
u'session': sessionID,
u'ip': userIP
})
@app.before_request
def getAnalyticsData():
global userOS, userBrowser, userIP, userContinent, userCity, userCountry,sessionID
userInfo = httpagentparser.detect(request.headers.get('User-Agent'))
userOS = userInfo['platform']['name']
userBrowser = userInfo['browser']['name']
userIP = "72.229.28.185" if request.remote_addr == '127.0.0.1' else request.remote_addr
api = "https://www.iplocate.io/api/lookup/" + userIP
try:
resp = urllib.request.urlopen(api)
result = resp.read()
result = json.loads(result.decode("utf-8"))
userCountry = result["country"]
userContinent = result["continent"]
userCity = result["city"]
except:
print("Could not find: ", userIP)
getSession()
def getSession():
global sessionID
time = datetime.now().replace(microsecond=0)
if 'user' not in session:
lines = (str(time)+userIP).encode('utf-8')
session['user'] = hashlib.md5(lines).hexdigest()
sessionID = session['user']
pusher.trigger(u'session', u'new', {
u'ip': userIP,
u'continent': userContinent,
u'country': userCountry,
u'city': userCity,
u'os': userOS,
u'browser': userBrowser,
u'session': sessionID,
u'time': str(time),
})
data = [userIP, userContinent, userCountry, userCity, userOS, userBrowser, sessionID, time]
create_session(c,data)
else:
sessionID = session['user']
@app.route('/')
def index():
data = ['home', sessionID, str(datetime.now().replace(microsecond=0))]
parseVisitor(data)
return render_template('index.html')
@app.route('/about')
def about():
data = ['about',sessionID, str(datetime.now().replace(microsecond=0))]
parseVisitor(data)
return render_template('about.html')
@app.route('/dashboard')
def dashboard():
return render_template('dashboard.html')
@app.route('/dashboard/<session_id>', methods=['GET'])
def sessionPages(session_id):
result = select_all_user_visits(c,session_id)
return render_template("dashboard-single.html",data=result)
@app.route('/get-all-sessions')
def get_all_sessions():
data = []
dbRows = select_all_sessions(c)
for row in dbRows:
data.append({
'ip' : row['ip'],
'continent' : row['continent'],
'country' : row['country'],
'city' : row['city'],
'os' : row['os'],
'browser' : row['browser'],
'session' : row['session'],
'time' : row['created_at']
})
return jsonify(data)
if __name__ == '__main__':
main()
app.run(debug=True)
In the code above, we imported the required modules and objects, then we initialized a new Flask application. We also initialized and configured Pusher before defining all the routes and their handler functions.
Replace the PUSHERAPP* keys with the values on your Pusher dashboard.
With the Pusher
object instantiated, we can trigger events on whatever channels we define.
The trigger method has the following syntax:
pusher.trigger("a_channel", "an_event", {key: "data to pass with event"})
You can find the docs for the Pusher Python library here, to get more information on configuring and using Pusher in Python.
The getAnalyticsData
function gets the visitor’s IP address and retrieves data using the urllib module to read information from the Iplocate API.
In the code above, we have defined four routes that will render web pages when visited, let’s create these web pages in the next section and use the frontend to communicate with our Python backend.
Setting up the app view
To begin, we need to create four files in the templates
directory. These files will be named:
index.html
— this is the home page for the websiteabout.html
— this is the about page for the websitedashboard.html
— this is the traffic monitor dashboarddashboard-single.html
— this is the dashboard page that displays more info (i.e. the types of pages a user has visited) on a single visitor.
Open the ./templates/index.html
file and paste the following:
<!DOCTYPE html>
<html>
<head>
<title>Pusher Python Realtime Traffic Monitoring</title>
</head>
<body>
<h1>This is the homepage</h1>
</body>
</html>
Open the ./templates/about.html
file and paste the following:
<!DOCTYPE html>
<html>
<head>
<title>Pusher Python Realtime Traffic Monitoring</title>
</head>
<body>
<h1>This is the about page</h1>
</body>
</html>
Before we write the code for dashboard-single.html
and dashboard
files, we will pull in some CSS and JS from https://startbootstrap.com. Go to the URL and click Download. Unzip the file and copy the css
and js
directories into the static
directory of our project.
Now, let’s continue building the frontend of our application, so open the ./templates/dashboard.html
file and paste the following:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>SB Admin - Start Bootstrap Template</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" type="text/css">
<link href="https://cdn.datatables.net/1.10.16/css/dataTables.bootstrap4.min.css" rel="stylesheet">
<link href="{{ url_for('static', filename='css/sb-admin.css') }}" rel="stylesheet">
</head>
<body class="fixed-nav sticky-footer bg-dark" id="page-top">
<!-- Navigation-->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top" id="mainNav">
<a class="navbar-brand" href="index.html">Start Bootstrap</a>
<button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav navbar-sidenav" id="exampleAccordion">
<li class="nav-item" data-toggle="tooltip" data-placement="right" title="Dashboard">
<a class="nav-link" href="/dashboard">
<i class="fa fa-fw fa-dashboard"></i>
<span class="nav-link-text">Dashboard</span>
</a>
</li>
</ul>
</div>
</nav>
<div class="content-wrapper">
<div class="container-fluid">
<!-- Breadcrumbs-->
<ol class="breadcrumb">
<li class="breadcrumb-item">
<a href="#">Dashboard</a>
</li>
<li class="breadcrumb-item active">My Dashboard</li>
</ol>
<!-- Icon Cards-->
<div class="row">
<div class="col-xl-3 col-sm-6 mb-3">
<div class="card text-white bg-primary o-hidden h-100">
<div class="card-body">
<div class="mr-5">Total session <span id="total-sessions">26</span></div>
</div>
<a class="card-footer text-white clearfix small z-1" href="#">
<span class="float-left">View Details</span>
<span class="float-right">
<i class="fa fa-angle-right"></i>
</span>
</a>
</div>
</div>
<div class="col-xl-3 col-sm-6 mb-3">
<div class="card text-white bg-warning o-hidden h-100">
<div class="card-body">
<div class="mr-5">Total visitors <span id="total-visitors">26</span></div>
</div>
<a class="card-footer text-white clearfix small z-1" href="#">
<span class="float-left">View Details</span>
<span class="float-right">
<i class="fa fa-angle-right"></i>
</span>
</a>
</div>
</div>
<div class="col-xl-3 col-sm-6 mb-3">
<div class="card text-white bg-success o-hidden h-100">
<div class="card-body">
<div class="mr-5">Total Page Views <span id="total-pageviews">123</span></div>
</div>
<a class="card-footer text-white clearfix small z-1" href="#">
<span class="float-left">View Details</span>
<span class="float-right">
<i class="fa fa-angle-right"></i>
</span>
</a>
</div>
</div>
<div class="col-xl-3 col-sm-6 mb-3">
<div class="card text-white bg-danger o-hidden h-100">
<div class="card-body">
<div class="mr-5">Unique Page Views <span id="unique-pageviews">123</span></div>
</div>
<a class="card-footer text-white clearfix small z-1" href="#">
<span class="float-left">View Details</span>
<span class="float-right">
<i class="fa fa-angle-right"></i>
</span>
</a>
</div>
</div>
</div>
<!-- Example DataTables Card-->
<div class="card mb-3">
<div class="card-header">
<i class="fa fa-table"></i> User Sessions</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered" id="dataTable" width="100%" cellspacing="0">
<thead>
<tr>
<th>Time</th>
<th>IP Address</th>
<th>Continent</th>
<th>Country</th>
<th>City</th>
<th>Opertating System</th>
<th>Browser</th>
<th></th>
</tr>
</thead>
<tfoot>
<tr>
<th>Time</th>
<th>IP Address</th>
<th>Continent</th>
<th>Country</th>
<th>City</th>
<th>Opertating System</th>
<th>Browser</th>
<th></th>
</tr>
</tfoot>
<tbody id="customer-table">
</tbody>
</table>
</div>
</div>
<div class="card-footer small text-muted">Updated at <span id="session-update-time"></span></div>
</div>
</div>
<!-- /.container-fluid-->
<!-- /.content-wrapper-->
<footer class="sticky-footer">
<div class="container">
<div class="text-center">
<small>Copyright © Your Website 2018</small>
</div>
</div>
</footer>
<!-- Bootstrap core JavaScript-->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
<script src="https://cdn.datatables.net/1.10.16/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.10.16/js/dataTables.bootstrap4.min.js"></script>
<script src="https://js.pusher.com/4.0/pusher.min.js"></script>
<!-- Custom scripts for this page-->
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
<script src="{{ url_for('static', filename='js/sb-admin.js') }}"></script>
</div>
</body>
</html>
Finally, open the ./templates/dashboard-single.html
file and paste the following:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>SB Admin - Start Bootstrap Template</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" type="text/css">
<link href="https://cdn.datatables.net/1.10.16/css/dataTables.bootstrap4.min.css" rel="stylesheet">
<link href="{{ url_for('static', filename='css/sb-admin.css') }}" rel="stylesheet">
</head>
<body class="fixed-nav sticky-footer bg-dark" id="page-top">
<!-- Navigation-->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top" id="mainNav">
<a class="navbar-brand" href="index.html">Start Bootstrap</a>
<button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav navbar-sidenav" id="exampleAccordion">
<li class="nav-item" data-toggle="tooltip" data-placement="right" title="Dashboard">
<a class="nav-link" href="/dashboard">
<i class="fa fa-fw fa-dashboard"></i>
<span class="nav-link-text">Dashboard</span>
</a>
</li>
</ul>
</div>
</nav>
<div class="content-wrapper">
<div class="container-fluid">
<!-- Breadcrumbs-->
<ol class="breadcrumb">
<li class="breadcrumb-item">
<a href="#">Dashboard</a>
</li>
<li class="breadcrumb-item active">My Session</li>
</ol>
<!-- Example DataTables Card-->
<div class="card mb-3">
<div class="card-header">
<i class="fa fa-table"></i> User Sessions</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered" id="userSessions" width="100%" cellspacing="0">
<thead>
<tr>
<th>Time</th>
<th>Page</th>
<th>Session_id</th>
<th>No of Visits</th>
<th></th>
</tr>
</thead>
<tfoot>
<tr>
<th>Time</th>
<th>Page</th>
<th>Session_id</th>
<th>No of Visits</th>
<th></th>
</tr>
</tfoot>
<tbody id="customer-table">
{% for row in data %}
<tr>
<td>{{ row.first_visited }}</td>
<td>{{ row.name }}</td>
<td>{{ row.session }}</td>
<td>{{ row.visits }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="card-footer small text-muted">Updated at <span id="session-update-time"></span></div>
</div>
</div>
<!-- /.container-fluid-->
<!-- /.content-wrapper-->
<footer class="sticky-footer">
<div class="container">
<div class="text-center">
<small>Copyright © Your Website 2018</small>
</div>
</div>
</footer>
<!-- Scroll to Top Button-->
<a class="scroll-to-top rounded" href="#page-top">
<i class="fa fa-angle-up"></i>
</a>
<!-- Logout Modal-->
<div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Ready to Leave?</h5>
<button class="close" type="button" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">Select "Logout" below if you are ready to end your current session.</div>
<div class="modal-footer">
<button class="btn btn-secondary" type="button" data-dismiss="modal">Cancel</button>
<a class="btn btn-primary" href="login.html">Logout</a>
</div>
</div>
</div>
</div>
<!-- Bootstrap core JavaScript-->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
<script src="https://cdn.datatables.net/1.10.16/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.10.16/js/dataTables.bootstrap4.min.js"></script>
<script src="https://js.pusher.com/4.0/pusher.min.js"></script>
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
<script src="{{ url_for('static', filename='js/sb-admin.js') }}"></script>
</div>
</body>
</html>
The markup we have written in all four files define the structure of the elements on the DOM when each page is rendered. We have also loaded the JavaScript Pusher package in the dashboard.html
and dashboard-single.html
files.
Next, we will listen for Pusher updates on the frontend of our application using the Pusher magic.
Listening for realtime updates
In this step, we will listen for Pusher events from the Backend API and use the data to update the traffic monitor dashboard pages in realtime. First, let’s create a new file — app.js
— in the js
folder that is located in the static
directory.
Next, open the ./static/js/app.js
file and paste the following:
// Configure Pusher instance
const pusher = new Pusher('PUSHER_APP_KEY', {
cluster: 'PUSHER_APP_CLUSTER',
encrypted: true
});
var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
$(document).ready(function(){
var dataTable = $("#dataTable").DataTable()
// var userSessions = $("#userSessions").DataTable()
var pages = $("#pages").DataTable()
axios.get('/get-all-sessions')
.then(response => {
response.data.forEach((data) => {
insertDatatable(data)
})
var d = new Date();
var updatedAt = `${d.getFullYear()}/${months[d.getMonth()]}/${d.getDay()} ${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}`
document.getElementById('session-update-time').innerText = updatedAt
})
var sessionChannel = pusher.subscribe('session');
sessionChannel.bind('new', function(data) {
insertDatatable(data)
});
var d = new Date();
var updatedAt = `${d.getFullYear()}/${months[d.getMonth()]}/${d.getDay()} ${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}`
document.getElementById('session-update-time').innerText = updatedAt
});
function insertDatatable(data){
var dataTable = $("#dataTable").DataTable()
dataTable.row.add([
data.time,
data.ip,
data.continent,
data.country,
data.city,
data.os,
data.browser,
`<a href=${"/dashboard/"+data.session}>View pages visited</a>`
]);
dataTable.order([0, 'desc']).draw();
}
Replace the
PUSHER_APP_*
keys with the keys on your Pusher dashboard.
In the code above, first, we register a new instance of Pusher then we use Axios to get all the sessions that have been saved to the database when the document is ready. We also created a new Pusher channel called sessionChannel
and subscribed to a session event.
We defined a function insertDatatable
and it inserts a new user record whenever Pusher sends an update.
That is everything! We can run the application now using this command:
$ flask run
Now we can visit 127.0.0.1:5000 to see the website and 127.0.0.1:5000/dashboard to see the traffic monitor dashboard, here’s a display of what both screens look like when viewed side by side.
Conclusion
In this tutorial, we have learned how to integrate a realtime traffic monitor with our website using Python’s Flask, Pusher and JavaScript.
The source code for this tutorial is available on GitHub.
Source: Scotch.io