2023-07-14 04:00:37 +00:00
import json , requests
2023-07-14 15:21:10 +00:00
from flask import Flask , request , session , render_template , redirect , url_for , Response , send_file
2023-07-14 04:00:37 +00:00
from flask_session import Session
2023-07-25 14:32:47 +00:00
import pandas as pd
import numpy as np
from matplotlib . figure import Figure
import matplotlib . dates as mdates
import base64
2023-07-24 21:10:38 +00:00
from tqdm import tqdm
2023-07-14 15:21:10 +00:00
from io import BytesIO
2023-07-14 04:00:37 +00:00
import logging
logger = logging . getLogger ( )
2023-08-21 03:05:48 +00:00
from functools import wraps
2023-07-14 04:00:37 +00:00
2023-07-20 05:05:29 +00:00
from EagleEyev3 import *
from settings import config
2023-07-14 04:00:37 +00:00
2023-08-21 16:22:17 +00:00
SECRET_KEY = " this needs to be changed to something unique and not checked into git "
2023-07-28 04:57:17 +00:00
# check if it could pull in a config object from settings.py
if config :
# start checking for keys in config object and set sensible defaults
if ' days_of_history ' in config :
DAYS_OF_HISTORY = config [ ' days_of_history ' ]
else :
# fallback to a sane default
DAYS_OF_HISTORY = 1
2023-08-21 03:44:44 +00:00
2023-07-28 04:57:17 +00:00
if ' log_level ' in config :
logger . setLevel ( config [ ' log_level ' ] )
2023-08-21 03:50:13 +00:00
2023-08-21 16:22:17 +00:00
if ' client_secret ' in config :
SECRET_KEY = config [ ' client_secret ' ]
2023-08-21 03:44:44 +00:00
else :
logging . setLevel ( config [ ' INFO ' ] )
2023-07-28 04:57:17 +00:00
2023-08-17 21:31:16 +00:00
logging . info ( f " Using EagleEyev3 version { EagleEyev3 . __version__ } " )
app = Flask ( __name__ )
2023-08-21 16:22:17 +00:00
app . secret_key = SECRET_KEY
2023-08-17 21:31:16 +00:00
SESSION_TYPE = ' filesystem '
2023-08-21 16:22:17 +00:00
SESSION_PERMANENT = False
PERMANENT_SESSION_LIFETIME = 60 * 60 * 24 * 7 # one week in seconds
2023-08-17 21:31:16 +00:00
app . config . from_object ( __name__ )
Session ( app )
2023-08-29 19:51:50 +00:00
# $ flask routes -s rule
# INFO:root:Using EagleEyev3 version 0.0.15
2023-08-17 21:31:16 +00:00
# Endpoint Methods Rule
# --------------------- ------- -------------------------------
2023-08-29 19:51:50 +00:00
# index GET /
2023-08-17 21:31:16 +00:00
# accounts GET /accounts
# camera_detail GET /camera/<esn>/events
2023-08-29 19:51:50 +00:00
# camera_detail GET /camera/<esn>/events/<int:days>
2023-08-17 21:31:16 +00:00
# camera_live_preivew GET /camera/<esn>/preview
2023-08-29 19:51:50 +00:00
# camera__preivew_image GET /camera/<esn>/preview_image
2023-08-17 21:31:16 +00:00
# camera_status_plot GET /camera/<esn>/status_plot
# cameras GET /cameras
# landing GET /landing
# login_callback GET /login_callback
# logout GET /logout
# static GET /static/<path:filename>
# switch_account GET /switch_account
2023-08-21 03:05:48 +00:00
def login_required ( f ) :
@wraps ( f )
def decorated_function ( * args , * * kwargs ) :
2023-08-21 03:44:44 +00:00
logging . debug ( session )
if ' een ' in session and session [ ' een ' ] . access_token :
2023-08-31 11:28:56 +00:00
logging . debug ( f " @login_required access_token: { session [ ' een ' ] . access_token } " )
2023-08-21 03:44:44 +00:00
return f ( * args , * * kwargs )
else :
# failed to find a valid session so redirecting to landing page
2023-08-31 11:28:56 +00:00
if ' een ' in session :
logging . debug ( f " @login_required access_token: { session [ ' een ' ] . access_token } " )
else :
logging . warn ( ' @login_requried failed to find a valid session ' )
2023-08-21 03:05:48 +00:00
return redirect ( url_for ( ' landing ' ) )
return decorated_function
2023-08-17 21:31:16 +00:00
2023-07-28 16:55:08 +00:00
@app.route ( ' /landing ' )
def landing ( ) :
# sometimes you just want to get to the landing page and want to avoid the index redirect logic
if ' een ' in session :
een = session [ ' een ' ]
else :
een = EagleEyev3 ( config )
session [ ' een ' ] = een
base_url = " https://auth.eagleeyenetworks.com/oauth2/authorize "
path_url = f " ?client_id= { een . client_id } &response_type=code&scope=vms.all&redirect_uri= { een . redirect_uri } "
values = { " login_link " : f " { base_url } { path_url } " }
return render_template ( ' cover.html ' , template_values = values )
2023-07-28 04:57:17 +00:00
2023-07-14 04:00:37 +00:00
@app.route ( ' / ' )
2023-08-21 03:05:48 +00:00
@login_required
2023-07-14 04:00:37 +00:00
def index ( ) :
2023-08-21 03:15:33 +00:00
# get een instance from session, @login_required already checked for it
2023-08-21 03:05:48 +00:00
een = session [ ' een ' ]
2023-07-14 04:00:37 +00:00
# using current_user as a proxy for an established valid session
if een . access_token == None :
base_url = " https://auth.eagleeyenetworks.com/oauth2/authorize "
path_url = f " ?client_id= { een . client_id } &response_type=code&scope=vms.all&redirect_uri= { een . redirect_uri } "
2023-07-28 03:32:56 +00:00
values = { " login_link " : f " { base_url } { path_url } " }
return render_template ( ' cover.html ' , template_values = values )
2023-07-14 04:00:37 +00:00
# call this to check if session is actually valid
check = een . get_current_user ( )
if ' success ' not in check or check [ ' success ' ] == False :
base_url = " https://auth.eagleeyenetworks.com/oauth2/authorize "
path_url = f " ?client_id= { een . client_id } &response_type=code&scope=vms.all&redirect_uri= { een . redirect_uri } "
2023-07-28 03:32:56 +00:00
values = { " login_link " : f " { base_url } { path_url } " }
return render_template ( ' cover.html ' , template_values = values )
2023-07-14 04:00:37 +00:00
else :
2023-07-20 05:05:29 +00:00
logging . info ( f " { check [ ' success ' ] } - check get_current_user { een . current_user [ ' email ' ] } { een . access_token } " )
2023-07-14 04:00:37 +00:00
2023-07-14 06:11:14 +00:00
een . get_list_of_cameras ( )
2023-08-10 19:05:23 +00:00
een . get_list_of_accounts ( )
2023-07-14 06:11:14 +00:00
2023-08-17 21:31:16 +00:00
if len ( een . accounts ) > 0 and een . active_account == None :
# they need to pick and account
logging . info ( f " redirecting to accounts.html because they don ' t have an active account { een . active_account } " )
values = {
" current_user " : een . current_user ,
" accounts " : een . accounts ,
" active_account " : een . active_account
}
redirect ( url_for ( ' accounts ' ) )
2023-07-14 04:00:37 +00:00
values = {
2023-07-14 06:11:14 +00:00
" current_user " : een . current_user ,
2023-08-10 19:05:23 +00:00
" cameras " : een . cameras ,
2023-08-14 22:33:19 +00:00
" camera_count " : len ( een . cameras ) ,
" camera_count_online " : len ( [ i for i in een . cameras if i . is_online ( ) ] ) ,
" camera_count_offline " : len ( [ i for i in een . cameras if i . is_offline ( ) ] ) ,
2023-08-10 19:05:23 +00:00
" accounts " : een . accounts ,
" active_account " : een . active_account
2023-07-14 04:00:37 +00:00
}
return render_template ( ' index.html ' , template_values = values )
@app.route ( ' /login_callback ' )
def login_callback ( ) :
2023-08-21 03:15:33 +00:00
2023-07-14 04:00:37 +00:00
# This is getting the ?code= querystring value from the HTTP request.
code = request . args . get ( ' code ' )
2023-08-21 03:05:48 +00:00
# create a new een object and store it in their session
een = EagleEyev3 ( config )
session [ ' een ' ] = een
2023-07-14 04:00:37 +00:00
if ( code ) :
# use the include code parameter to complete login process
oauth_object = een . login_tokens ( code )
2023-08-31 11:28:56 +00:00
logging . debug ( oauth_object )
2023-07-14 06:11:14 +00:00
2023-08-31 11:28:56 +00:00
# let's try resaving this to see if it fixs the missing access_token on first request after login
session [ ' een ' ] = een
2023-07-14 04:00:37 +00:00
return redirect ( url_for ( ' index ' ) )
@app.route ( ' /logout ' )
def logout ( ) :
2023-08-21 03:15:33 +00:00
# logout isn't working APIv3 right now so don't wait for the call to fail before logging out
# if 'een' in session:
# een = session['een']
# een.logout()
2023-07-14 04:00:37 +00:00
2023-07-26 19:21:23 +00:00
session . pop ( ' een ' )
2023-07-14 18:02:26 +00:00
2023-07-14 04:00:37 +00:00
return redirect ( url_for ( ' index ' ) )
2023-07-14 06:11:14 +00:00
@app.route ( ' /cameras ' )
2023-08-21 03:05:48 +00:00
@login_required
2023-07-14 06:11:14 +00:00
def cameras ( ) :
2023-08-21 03:05:48 +00:00
2023-08-21 03:15:33 +00:00
# get een instance from session, @login_required already checked for it
2023-08-21 03:05:48 +00:00
een = session [ ' een ' ]
2023-07-14 06:11:14 +00:00
2023-08-15 16:17:38 +00:00
logging . debug ( een . get_list_of_cameras ( ) )
2023-07-14 06:11:14 +00:00
values = {
" current_user " : een . current_user ,
2023-08-14 22:33:19 +00:00
" cameras " : een . cameras ,
" camera_count " : len ( een . cameras ) ,
" camera_count_online " : len ( [ i for i in een . cameras if i . is_online ( ) ] ) ,
" camera_count_offline " : len ( [ i for i in een . cameras if i . is_offline ( ) ] )
2023-07-14 06:11:14 +00:00
}
return render_template ( ' cameras_partial.html ' , template_values = values )
2023-07-14 15:21:10 +00:00
2023-08-10 19:05:23 +00:00
@app.route ( ' /accounts ' )
2023-08-21 03:05:48 +00:00
@login_required
2023-08-10 19:05:23 +00:00
def accounts ( ) :
2023-08-21 03:05:48 +00:00
2023-08-21 03:15:33 +00:00
# get een instance from session, @login_required already checked for it
2023-08-21 03:05:48 +00:00
een = session [ ' een ' ]
2023-08-10 19:05:23 +00:00
2023-08-21 03:50:13 +00:00
logging . debug ( een . get_list_of_accounts ( ) )
2023-08-10 19:05:23 +00:00
values = {
" current_user " : een . current_user ,
" accounts " : een . accounts ,
" active_account " : een . active_account
}
return render_template ( ' accounts_partial.html ' , template_values = values )
@app.route ( ' /switch_account ' )
2023-08-21 03:05:48 +00:00
@login_required
2023-08-10 19:05:23 +00:00
def switch_account ( ) :
2023-08-21 03:15:33 +00:00
2023-08-10 19:05:23 +00:00
# This is getting the ?account= querystring value from the HTTP request.
account = request . args . get ( ' account ' )
2023-08-21 03:15:33 +00:00
# get een instance from session, @login_required already checked for it
2023-08-21 03:05:48 +00:00
een = session [ ' een ' ]
2023-08-10 19:05:23 +00:00
if ( account ) :
2023-08-17 21:31:16 +00:00
# switch into account
logging . info ( f " attempting to switch into account { account } " )
2023-08-21 03:50:13 +00:00
logging . debug ( een . login_from_reseller ( target_account_id = account ) )
2023-08-10 19:05:23 +00:00
return redirect ( url_for ( ' index ' ) )
2023-07-26 13:34:33 +00:00
@app.route ( ' /camera/<esn>/preview_image ' )
2023-08-21 03:05:48 +00:00
@login_required
def camera__preivew_image ( esn = None ) :
2023-08-21 03:15:33 +00:00
# get een instance from session, @login_required already checked for it
2023-08-21 03:05:48 +00:00
een = session [ ' een ' ]
2023-07-14 15:21:10 +00:00
camera = een . get_camera_by_id ( esn )
res = camera . get_live_preview ( )
if res :
return send_file ( BytesIO ( res . content ) , mimetype = ' image/jpeg ' )
else :
2023-07-14 15:48:14 +00:00
return send_file ( ' static/placeholder.png ' )
2023-07-14 15:21:10 +00:00
2023-07-28 16:55:08 +00:00
2023-07-26 13:34:33 +00:00
@app.route ( ' /camera/<esn>/preview ' )
2023-08-21 03:05:48 +00:00
@login_required
def camera_live_preivew ( esn = None ) :
2023-08-21 03:15:33 +00:00
# get een instance from session, @login_required already checked for it
2023-08-21 03:05:48 +00:00
een = session [ ' een ' ]
2023-07-26 13:34:33 +00:00
camera = een . get_camera_by_id ( esn )
values = {
" current_user " : een . current_user ,
" camera " : camera ,
" events " : camera . events [ ' status ' ]
}
return render_template ( ' camera_preview.html ' , template_values = values )
2023-08-07 20:43:23 +00:00
@app.route ( " /camera/<esn>/events/<int:days> " )
2023-07-26 13:34:33 +00:00
@app.route ( ' /camera/<esn>/events ' )
2023-08-21 03:05:48 +00:00
@login_required
def camera_detail ( esn = None , days = DAYS_OF_HISTORY ) :
2023-08-21 03:15:33 +00:00
# get een instance from session, @login_required already checked for it
2023-08-21 03:05:48 +00:00
een = session [ ' een ' ]
2023-07-14 06:11:14 +00:00
camera = een . get_camera_by_id ( esn )
2023-07-24 21:10:38 +00:00
2023-08-08 14:39:48 +00:00
# because of API limitation, can only query 24 hours max
for i in tqdm ( range ( 0 , days ) ) :
2023-08-30 14:01:40 +00:00
camera . get_list_of_events ( end_timestamp = een . time_before ( hours = 24 * i ) , \
start_timestamp = een . time_before ( hours = 24 * ( i + 1 ) ) )
2023-07-14 06:11:14 +00:00
values = {
" current_user " : een . current_user ,
" camera " : camera ,
2023-07-14 13:26:06 +00:00
" events " : camera . events [ ' status ' ]
2023-07-14 06:11:14 +00:00
}
2023-07-26 13:34:33 +00:00
return render_template ( ' camera_events_partial.html ' , template_values = values )
2023-07-14 06:11:14 +00:00
2023-07-28 16:55:08 +00:00
2023-07-24 21:10:38 +00:00
@app.route ( ' /camera/<esn>/status_plot ' )
2023-08-21 03:05:48 +00:00
@login_required
def camera_status_plot ( esn = None ) :
2023-07-14 06:11:14 +00:00
2023-08-21 03:15:33 +00:00
# get een instance from session, @login_required already checked for it
2023-08-21 03:05:48 +00:00
een = session [ ' een ' ]
2023-08-21 03:15:33 +00:00
2023-07-24 21:10:38 +00:00
cam = een . get_camera_by_id ( esn )
2023-08-15 16:17:38 +00:00
logging . debug ( cam . events [ ' status ' ] [ 0 ] )
if cam . events [ ' status ' ] [ 0 ] [ ' endTimestamp ' ] == None :
logging . debug ( ' found empty end_timestamp ' )
cam . events [ ' status ' ] [ 0 ] [ ' endTimestamp ' ] = str ( pd . Timestamp . utcnow ( ) )
2023-07-24 21:10:38 +00:00
atm_df = pd . DataFrame ( cam . events [ ' status ' ] [ : : - 1 ] , columns = [ ' id ' , ' startTimestamp ' , ' actorId ' , ' data ' ] )
atm_df [ ' ts ' ] = pd . to_datetime ( atm_df . startTimestamp )
atm_df [ ' status_desc ' ] = atm_df [ ' data ' ] . apply ( lambda x : x [ 0 ] [ ' newStatus ' ] [ ' connectionStatus ' ] )
2023-08-15 16:17:38 +00:00
atm_df [ ' status ' ] = atm_df [ ' status_desc ' ] . replace ( to_replace = [ ' online ' , ' offline ' , ' error ' , ' deviceOffline ' , ' deviceOnline ' , ' off ' , ' bridgeOffline ' , ' unknown ' ] , value = [ 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ] )
2023-07-24 21:10:38 +00:00
imp = atm_df . set_index ( [ ' ts ' ] )
imp [ ' startTimestamp ' ] = pd . to_datetime ( imp [ ' startTimestamp ' ] )
imp = imp . drop ( [ ' id ' , ' actorId ' , ' data ' , ' status_desc ' ] , axis = 1 )
2023-08-15 13:56:44 +00:00
imp [ ' status ' ] = imp [ ' status ' ] . ffill ( )
data = imp . resample ( ' S ' ) . ffill ( )
2023-08-15 14:05:39 +00:00
# logging.debug(data.tail(200))
# data['status'] = data['status'].astype('int64')
2023-07-24 21:10:38 +00:00
data = data . drop ( [ ' startTimestamp ' ] , axis = 1 )
2023-07-25 14:32:47 +00:00
# Generate the figure **without using pyplot**.
fig = Figure ( figsize = ( 6 , 5 ) , dpi = 160 )
ax = fig . subplots ( )
2023-07-24 21:10:38 +00:00
2023-07-25 14:32:47 +00:00
ax . step ( data . index , data [ ' status ' ] , lw = 2 , color = ' blue ' )
ax . set_title ( cam . name )
2023-07-24 21:10:38 +00:00
# ax.fill_between(data['startTimestamp'], data['status'])
ax . axhline ( 1 , color = ' green ' , lw = 2 , alpha = 0.3 )
ax . axhline ( 0 , color = ' red ' , lw = 2 , alpha = 0.3 )
2023-07-25 14:32:47 +00:00
2023-07-24 21:10:38 +00:00
for label in ax . get_xticklabels ( which = ' major ' ) :
label . set ( rotation = 30 , horizontalalignment = ' right ' )
# Save it to a temporary buffer.
buf = BytesIO ( )
2023-07-28 16:41:05 +00:00
fig . savefig ( buf , format = " png " , transparent = True )
2023-07-24 21:10:38 +00:00
# Embed the result in the html output.
data = base64 . b64encode ( buf . getbuffer ( ) ) . decode ( " ascii " )
2023-08-07 18:55:58 +00:00
return f " <h3>Graph of Status Events <i class= ' bi bi-bar-chart ' ></i></h3><div class= ' col-md-12 ' ><img src= ' data:image/png;base64, { data } ' style= ' max-width:100% ' /></div> "
2023-07-14 06:11:14 +00:00
2023-07-14 15:21:10 +00:00
2023-07-28 16:55:08 +00:00
2023-07-14 04:00:37 +00:00
if __name__ == ' __main__ ' :
2023-08-24 19:51:17 +00:00
app . run ( host = config . server_host , port = config . server_port )