451 lines
14 KiB
Python
451 lines
14 KiB
Python
|
|
import json, requests
|
|
from flask import Flask, request, session, render_template, redirect, url_for, Response, send_file
|
|
from flask_session import Session
|
|
|
|
import pandas as pd
|
|
import numpy as np
|
|
from matplotlib.figure import Figure
|
|
import matplotlib.dates as mdates
|
|
import base64
|
|
from tqdm import tqdm
|
|
import datetime
|
|
from io import BytesIO
|
|
|
|
import logging
|
|
logger = logging.getLogger()
|
|
|
|
from functools import wraps
|
|
|
|
from EagleEyev3 import *
|
|
from settings import config
|
|
|
|
|
|
SECRET_KEY = "this needs to be changed to something unique and not checked into git"
|
|
|
|
|
|
# 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
|
|
|
|
if 'log_level' in config:
|
|
logger.setLevel(config['log_level'])
|
|
else:
|
|
logging.setLevel('INFO')
|
|
|
|
if 'client_secret' in config:
|
|
SECRET_KEY = config['client_secret']
|
|
|
|
else:
|
|
logging.setLevel('INFO')
|
|
|
|
|
|
|
|
|
|
logging.info(f"Using EagleEyev3 version {EagleEyev3.__version__}")
|
|
|
|
|
|
app = Flask(__name__)
|
|
|
|
app.secret_key = SECRET_KEY
|
|
SESSION_TYPE = 'filesystem'
|
|
SESSION_PERMANENT = False
|
|
PERMANENT_SESSION_LIFETIME = 60 * 60 * 24 * 7 # one week in seconds
|
|
app.config.from_object(__name__)
|
|
Session(app)
|
|
|
|
|
|
|
|
|
|
from flask_sqlalchemy import SQLAlchemy
|
|
from models import *
|
|
|
|
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///project.db"
|
|
db.init_app(app)
|
|
|
|
with app.app_context():
|
|
db.create_all()
|
|
|
|
|
|
|
|
|
|
|
|
# $ flask routes -s rule
|
|
# INFO:root:Using EagleEyev3 version 0.0.17
|
|
# Endpoint Methods Rule
|
|
# --------------------- ------- --------------------------------------------
|
|
# index GET /
|
|
# accounts GET /accounts
|
|
# camera_live_preivew GET /camera/<esn>/preview
|
|
# camera__preivew_image GET /camera/<esn>/preview_image
|
|
# camera_video_player GET /camera/<esn>/video_player/<start_timestamp>
|
|
# camera_list_of_videos GET /camera/<esn>/videos
|
|
# cameras GET /cameras
|
|
# landing GET /landing
|
|
# login_callback GET /login_callback
|
|
# logout GET /logout
|
|
# static GET /static/<path:filename>
|
|
# switch_account GET /switch_account
|
|
|
|
|
|
def login_required(f):
|
|
@wraps(f)
|
|
def decorated_function(*args, **kwargs):
|
|
logging.debug(session)
|
|
if 'een' in session and session['een'].access_token:
|
|
logging.debug(f"@login_required access_token: {session['een'].access_token}")
|
|
return f(*args, **kwargs)
|
|
else:
|
|
# failed to find a valid session so redirecting to landing page
|
|
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')
|
|
|
|
return redirect(url_for('landing'))
|
|
return decorated_function
|
|
|
|
|
|
|
|
@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)
|
|
|
|
|
|
@app.route('/')
|
|
@login_required
|
|
def index():
|
|
|
|
# get een instance from session, @login_required already checked for it
|
|
een = session['een']
|
|
|
|
# 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}"
|
|
values = { "login_link": f"{base_url}{path_url}" }
|
|
return render_template('cover.html', template_values=values)
|
|
|
|
# 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}"
|
|
|
|
values = { "login_link": f"{base_url}{path_url}" }
|
|
return render_template('cover.html', template_values=values)
|
|
else:
|
|
logging.info(f"{check['success']} - check get_current_user {een.current_user['email']} {een.access_token}")
|
|
|
|
|
|
een.get_list_of_cameras()
|
|
een.get_list_of_accounts()
|
|
|
|
save_list_of_cameras(een=een)
|
|
|
|
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,
|
|
"user_base_url": een.user_base_url,
|
|
"access_token": een.access_token
|
|
}
|
|
redirect(url_for('accounts'))
|
|
|
|
values = {
|
|
"current_user": een.current_user,
|
|
"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()]),
|
|
"accounts": een.accounts,
|
|
"active_account": een.active_account,
|
|
"user_base_url": een.user_base_url,
|
|
"access_token": een.access_token
|
|
}
|
|
|
|
return render_template('index.html', template_values=values)
|
|
|
|
|
|
@app.route('/login_callback')
|
|
def login_callback():
|
|
|
|
# This is getting the ?code= querystring value from the HTTP request.
|
|
code = request.args.get('code')
|
|
|
|
# create a new een object and store it in their session
|
|
een = EagleEyev3(config)
|
|
session['een'] = een
|
|
|
|
|
|
if (code):
|
|
# use the include code parameter to complete login process
|
|
oauth_object = een.login_tokens(code)
|
|
logging.debug(oauth_object)
|
|
|
|
|
|
|
|
|
|
# let's try resaving this to see if it fixs the missing access_token on first request after login
|
|
session['een'] = een
|
|
|
|
een.get_current_user()
|
|
|
|
try:
|
|
user = User.query.filter(User.email == een.current_user['email']).first()
|
|
if user is None:
|
|
logging.debug(f"no user found with email {een.current_user['email']}, creating new user")
|
|
user = User(account_id=een.current_user['accountId'],\
|
|
user_id=een.current_user['id'],\
|
|
email=een.current_user['email'],\
|
|
timezone=een.current_user['timeZone']['timeZone'],\
|
|
access_token=een.access_token,\
|
|
refresh_token=een.refresh_token,\
|
|
last_login=datetime.now())
|
|
else:
|
|
logging.debug(f"found user with {een.current_user['email']}, updating")
|
|
user.last_login = datetime.now()
|
|
user.access_token = een.access_token
|
|
user.refresh_token = een.refresh_token
|
|
|
|
logging.debug(f"saving user object")
|
|
user.save()
|
|
except Exception as e:
|
|
logging.warn(e)
|
|
|
|
|
|
|
|
return redirect(url_for('index'))
|
|
|
|
|
|
@app.route('/logout')
|
|
def logout():
|
|
|
|
if 'een' in session:
|
|
een = session['een']
|
|
logging.debug(f"calling logout { een.access_token = }")
|
|
een.logout()
|
|
|
|
session.pop('een')
|
|
|
|
return redirect(url_for('index'))
|
|
|
|
|
|
def save_list_of_cameras(een=None):
|
|
user = User.query.filter(User.email == een.current_user['email']).first()
|
|
if user:
|
|
user_id = user.id
|
|
else:
|
|
user_id = None
|
|
|
|
for c in een.cameras:
|
|
try:
|
|
check_camera = Camera.query.filter(Camera.device_id == c.id).first()
|
|
if check_camera is None:
|
|
logging.debug(f"no camera found for {c.id}, creating new camera")
|
|
check_camera = Camera(device_id=c.id,\
|
|
timezone=None, \
|
|
name=c.name,
|
|
user_id=user_id)
|
|
else:
|
|
logging.debug(f"found camera for {c.id}")
|
|
|
|
logging.debug(f"saving camera object")
|
|
check_camera.save()
|
|
except Exception as e:
|
|
logging.warn(e)
|
|
|
|
|
|
@app.route('/cameras')
|
|
@login_required
|
|
def cameras():
|
|
|
|
# get een instance from session, @login_required already checked for it
|
|
een = session['een']
|
|
|
|
logging.debug(een.get_list_of_cameras())
|
|
|
|
|
|
save_list_of_cameras(een=een)
|
|
|
|
values = {
|
|
"current_user": een.current_user,
|
|
"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()])
|
|
}
|
|
|
|
return render_template('cameras_partial.html', template_values=values)
|
|
|
|
|
|
@app.route('/accounts')
|
|
@login_required
|
|
def accounts():
|
|
|
|
# get een instance from session, @login_required already checked for it
|
|
een = session['een']
|
|
|
|
logging.debug(een.get_list_of_accounts())
|
|
|
|
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')
|
|
@login_required
|
|
def switch_account():
|
|
|
|
# This is getting the ?account= querystring value from the HTTP request.
|
|
account = request.args.get('account')
|
|
|
|
# get een instance from session, @login_required already checked for it
|
|
een = session['een']
|
|
|
|
if (account):
|
|
# switch into account
|
|
logging.info(f"attempting to switch into account {account}")
|
|
logging.debug(een.login_from_reseller(target_account_id=account))
|
|
|
|
|
|
return redirect(url_for('index'))
|
|
|
|
|
|
@app.route('/camera/<esn>/preview_image')
|
|
@login_required
|
|
def camera__preivew_image(esn=None):
|
|
|
|
# get een instance from session, @login_required already checked for it
|
|
een = session['een']
|
|
|
|
camera = een.get_camera_by_id(esn)
|
|
res = camera.get_live_preview()
|
|
|
|
if res:
|
|
return send_file(BytesIO(res.content), mimetype='image/jpeg')
|
|
else:
|
|
return send_file('static/placeholder.png')
|
|
|
|
|
|
@app.route('/camera/<esn>/preview')
|
|
@login_required
|
|
def camera_live_preivew(esn=None):
|
|
|
|
# get een instance from session, @login_required already checked for it
|
|
een = session['een']
|
|
|
|
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)
|
|
|
|
|
|
@app.route('/camera/<esn>/video_player/<start_timestamp>')
|
|
@login_required
|
|
def camera_video_player(esn=None, start_timestamp=None):
|
|
|
|
# get een instance from session, @login_required already checked for it
|
|
een = session['een']
|
|
|
|
camera = een.get_camera_by_id(esn)
|
|
|
|
try:
|
|
video_url = next(x['mp4Url'] for x in camera.videos if x['startTimestamp'] == start_timestamp)
|
|
except StopIteration:
|
|
logging.warn(f"couldn't find video with { start_timestamp } for camera { camera.id }")
|
|
|
|
values = {
|
|
"current_user": een.current_user,
|
|
"camera_name": camera.name,
|
|
"start_timestamp": start_timestamp,
|
|
"video_url": video_url
|
|
}
|
|
|
|
return render_template('camera_video_player.html', template_values=values)
|
|
|
|
|
|
@app.route('/camera/<esn>/videos')
|
|
@login_required
|
|
def camera_list_of_videos(esn=None):
|
|
|
|
# get een instance from session, @login_required already checked for it
|
|
een = session['een']
|
|
|
|
camera = een.get_camera_by_id(esn)
|
|
logging.debug(camera.get_list_of_videos(start_timestamp=een.time_before(hours=24 * DAYS_OF_HISTORY), end_timestamp=een.time_now()))
|
|
|
|
db_camera = Camera.query.filter(Camera.device_id == esn).first()
|
|
if db_camera:
|
|
|
|
for v in camera.videos:
|
|
try:
|
|
|
|
check_video = Download.query.filter(Download.camera_id == db_camera.id)\
|
|
.filter(Download.start_timestamp == datetime.fromisoformat(v['startTimestamp']))\
|
|
.first()
|
|
|
|
|
|
if check_video is None:
|
|
logging.debug(f"creating a new record for {db_camera.id} {v['startTimestamp']}")
|
|
new_video = Download(url=v['mp4Url'],\
|
|
camera_id=db_camera.id,\
|
|
start_timestamp=datetime.fromisoformat(v['startTimestamp']),\
|
|
end_timestamp=datetime.fromisoformat(v['endTimestamp']),\
|
|
status='new',
|
|
error=None)
|
|
new_video.save()
|
|
except Exception as e:
|
|
logging.warn(f"Exception saving videos to db: {e}")
|
|
|
|
values = {
|
|
"current_user": een.current_user,
|
|
"camera": { 'id': camera.id, 'name': camera.name },
|
|
"videos": camera.videos
|
|
}
|
|
|
|
|
|
if 'HX-Request' in request.headers:
|
|
return render_template('camera_videos_partial.html', template_values=values)
|
|
else:
|
|
return values
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
app.run(host=config['server_host'], port=config['server_port'])
|